[CalendarServer-changes] [8906] CalendarServer/branches/users/gaya/ldapdirectorybacker

source_changes at macosforge.org source_changes at macosforge.org
Fri Mar 16 20:37:16 PDT 2012


Revision: 8906
          http://trac.macosforge.org/projects/calendarserver/changeset/8906
Author:   gaya at apple.com
Date:     2012-03-16 20:37:14 -0700 (Fri, 16 Mar 2012)
Log Message:
-----------
add xmldirectorybacker.py

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py

Added Paths:
-----------
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmldirectorybacker.py

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist	2012-03-17 01:43:01 UTC (rev 8905)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist	2012-03-17 03:37:14 UTC (rev 8906)
@@ -547,7 +547,7 @@
 
     <!-- Log levels -->
     <key>DefaultLogLevel</key>
-    <string>info</string> <!-- debug, info, warn, error -->
+    <string>debug</string> <!-- debug, info, warn, error -->
 
     <!-- Log level overrides for specific functionality -->
     <key>LogLevels</key>
@@ -994,6 +994,30 @@
       <string>English</string>
     </dict>
 
-
+    <!--
+        Directory Address Book
+      -->
+    
+    <!--  Disable Directory Address Book -->
+    <!--
+      <key>DirectoryAddressBook</key>
+      <false/>
+    -->
+    
+    <!-- XML Directory-backed Directory Address Book -->
+    <key>EnableSearchAddressBook</key>
+    <true/>
+    <key>DirectoryAddressBook</key>
+    <dict>
+        <key>Enabled</key>
+        <true/>
+      <key>type</key>
+      <string>twistedcaldav.directory.xmldirectorybacker.XMLDirectoryBackingService</string>
+      <key>params</key>
+      <dict>
+        <key>xmlFile</key>
+        <string>./conf/auth/accounts-test.xml</string>
+      </dict>
+   </dict>
   </dict>
 </plist>

Added: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmldirectorybacker.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmldirectorybacker.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmldirectorybacker.py	2012-03-17 03:37:14 UTC (rev 8906)
@@ -0,0 +1,290 @@
+##
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+"""
+Apple Open Directory directory service implementation for backing up directory-backed address books
+"""
+
+__all__ = [
+    "XMLDirectoryBackingService",
+]
+
+import traceback
+import hashlib
+
+import os
+import sys
+import time
+
+from socket import getfqdn
+
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from twistedcaldav.directory.directory import DirectoryRecord
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+
+from twistedcaldav.directory.opendirectorybacker import VCardRecord, dsFilterFromAddressBookFilter
+from calendarserver.platform.darwin.od import dsattributes, dsquery
+
+
+class XMLDirectoryBackingService(XMLDirectoryService):
+    """
+    """
+    
+    node="/Search"
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.realmName, )
+
+    def __init__(self, params):
+        self._actuallyConfigure(**params)
+
+    def _actuallyConfigure(self, **params):
+        
+        self.log_debug("_actuallyConfigure: params=%s" % (params,))
+        defaults = {
+            "recordTypes": (self.recordType_users,), 
+            "rdnSchema": {
+                self.recordType_users : { 
+                    "vcardPropToDirRecordAttrMap" : { 
+                        "FN" : (
+                                "fullName",
+                                "shortNames",
+                                "firstName",
+                                "lastName",
+                                ),
+                        "N" : (
+                                "fullName",
+                                "shortNames",
+                                "firstName",
+                                "lastName",
+                                ),
+                        "EMAIL" : "emailAddresses",
+                        "UID" : "guid",
+                     },
+                     "dirRecordAttrToDSAttrMap" : { 
+                        "guid" :            dsattributes.kDS1AttrGeneratedUID,
+                        "fullName" :        dsattributes.kDS1AttrDistinguishedName,
+                        "shortNames" :      dsattributes.kDSNAttrRecordName,
+                        "firstName" :       dsattributes.kDS1AttrFirstName,
+                        "lastName" :        dsattributes.kDS1AttrLastName,
+                        "emailAddresses" :  dsattributes.kDSNAttrEMailAddress,
+                     },
+                },
+            },
+            "maxQueryRecords":0,            # max records returned
+       }
+
+        #params = self.getParams(params, defaults, ignored)
+        def addDefaults(params, defaults, remove=None):
+            keys = set(params.keys())
+
+            for key in defaults:
+                if not key in params:
+                    params[key] = defaults[key]
+            return params
+            
+        params = addDefaults(params, defaults)
+        self.log_debug("_actuallyConfigure after addDefaults: params=%s" % (params,))
+        
+        # super does not like these extra params
+        directoryBackedAddressBook=params["directoryBackedAddressBook"]
+        #del params["directoryBackedAddressBook"]
+        maxQueryRecords=params["maxQueryRecords"]
+        del params["maxQueryRecords"]
+        rdnSchema=params["rdnSchema"]
+        del params["rdnSchema"]
+
+        
+        assert directoryBackedAddressBook is not None
+        self.directoryBackedAddressBook = directoryBackedAddressBook
+        
+        self.maxQueryRecords = maxQueryRecords
+        self.rdnSchema = rdnSchema
+
+                
+        self.realmName = None # needed for super        
+        
+        super(XMLDirectoryBackingService, self).__init__(params)
+        
+         ### self.defaultNodeName used by VCardRecord.
+        # get this now once
+        hostname = getfqdn()
+        if hostname:
+            self.defaultNodeName = "/LDAPv3/" + hostname
+        else:
+            self.defaultNodeName = None
+        
+ 
+    def __cmp__(self, other):
+        if not isinstance(other, DirectoryRecord):
+            return super(DirectoryRecord, self).__eq__(other)
+
+        for attr in ("directory", "node"):
+            diff = cmp(getattr(self, attr), getattr(other, attr))
+            if diff != 0:
+                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 createCache(self):
+         succeed(None)
+                        
+
+    @inlineCallbacks
+    def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults ):
+        """
+        Get vCards for a given addressBookFilter and addressBookQuery
+        """
+    
+        queryRecords = []
+        limited = False
+
+         #calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
+        maxRecords = int(maxResults * 1.2)
+        if self.maxQueryRecords and maxRecords > self.maxQueryRecords:
+            maxRecords = self.maxQueryRecords
+
+        for queryType in self.recordTypes():
+
+            queryMap = self.rdnSchema[queryType]
+            vcardPropToDirRecordAttrMap = queryMap["vcardPropToDirRecordAttrMap"]
+            dirRecordAttrToDSAttrMap = queryMap["dirRecordAttrToDSAttrMap"]
+
+            allRecords, filterAttributes, dsFilter  = dsFilterFromAddressBookFilter( addressBookFilter, vcardPropToDirRecordAttrMap );
+            self.log_debug("vCardRecordsForAddressBookQuery: queryType=\"%s\" LDAP allRecords=%s, filterAttributes=%s, query=%s" % (queryType, allRecords, filterAttributes, "None" if dsFilter is None else dsFilter.generate(),))
+    
+            
+            if allRecords:
+                dsFilter = None #  None expression == all Records
+                
+            # stop query for all
+            clear = not allRecords and not dsFilter
+            
+            if not clear:
+                
+                @inlineCallbacks
+                def recordsForDSFilter(dsFilter, recordType):
+                    
+                    """
+                        Whist this tests the dsFilter expression tree and recordsMatchingFields() it make little difference to the resutl of
+                        a addressbook query because of post filtering.
+                    """
+                    self.log_debug("recordsForDSFilter:  dsFilter=%s" % (dsFilter.generate(), ))
+                    if dsFilter.operator == dsquery.expression.NOT:
+                        self.log_debug("recordsForDSFilter:  dsFilter-%s NOT NONE" % (dsFilter.generate(), ))
+                        returnValue(None) # dsquery.expression.NOT not supported by recordsMatchingFields()
+                    else:
+                        self.log_debug("recordsForDSFilter: #subs %s" % (len(dsFilter.subexpressions), ))
+                        
+                        # evaluate matches
+                        matches = [match for match in dsFilter.subexpressions if isinstance(match, dsquery.match)]
+                        fields = []
+                        for match in matches:
+                            self.log_debug("recordsForDSFilter: match=%s" % (match.generate(), ))
+                            xmlMatchType = {
+                                dsattributes.eDSExact :        "exact",
+                                dsattributes.eDSStartsWith :   "start-with",
+                                dsattributes.eDSContains :     "contains",
+                            }.get(match.matchType)
+                            if not xmlMatchType:
+                                self.log_debug("recordsForDSFilter: match=%s match type not supported" % (match.generate(), ))
+                                returnValue(None) # match type not supported by recordsMatchingFields()
+                            
+                            fields += ((match.attribute, match.value, True, xmlMatchType,),)
+                            self.log_debug("recordsForDSFilter: fields=%s" % (fields,))
+                        
+                        # if there were matches, call get records that match
+                        result = set()
+                        if len(fields):
+                            operand = "and" if dsFilter.operator == dsquery.expression.AND else "or"
+                            self.log_debug("recordsForDSFilter: recordsMatchingFields(fields=%s, operand=%s, recordType=%s)" % (fields, operand, recordType,))
+                            result = set((yield self.recordsMatchingFields(fields, operand=operand, recordType=recordType)))
+                            self.log_debug("recordsForDSFilter: result=%s" % (result,))
+
+                        # evaluate subexpressions
+                        subexpressions = [subexpression for subexpression in dsFilter.subexpressions if isinstance(subexpression, dsquery.expression)]
+                        for subexpression in subexpressions:
+                            self.log_debug("recordsForDSFilter: subexpression=%s" % (subexpression.generate(), ))
+                            subresult = (yield recordsForDSFilter(subexpression, recordType))
+                            self.log_debug("recordsForDSFilter: subresult=%s" % (subresult,))
+                            if subresult is None:
+                                returnValue(None)
+                            
+                            if dsFilter.operator == dsquery.expression.OR:
+                                result = result.union(subresult)
+                            else:
+                                result = result.intersection(subresult)
+
+                    self.log_debug("recordsForDSFilter:  dsFilter=%s returning %s" % (dsFilter.generate(), result, ))
+                    returnValue(result)
+                                
+                # walk the expression tree
+                xmlDirectoryRecords = (yield recordsForDSFilter(dsFilter, queryType))
+                self.log_debug("vCardRecordsForAddressBookQuery: #xmlDirectoryRecords %s" % (len(xmlDirectoryRecords) if xmlDirectoryRecords is not None else xmlDirectoryRecords, ))
+                
+                if xmlDirectoryRecords is None:
+                    xmlDirectoryRecords = (yield self.listRecords(queryType))
+                    self.log_debug("vCardRecordsForAddressBookQuery: all #xmlDirectoryRecords %s" % (len(xmlDirectoryRecords), ))
+                
+                # apply limit
+                if len(xmlDirectoryRecords) > maxRecords:
+                    xmlDirectoryRecords = set(list(xmlDirectoryRecords)[:maxRecords])
+                self.log_debug("vCardRecordsForAddressBookQuery: #xmlDirectoryRecords after max %s" % (len(xmlDirectoryRecords), ))
+                   
+                for xmlDirectoryRecord in xmlDirectoryRecords:
+                    
+                    def dsRecordAttributesFromDirectoryRecord( xmlDirectoryRecord ):
+                        dsRecordAttributes = {}
+                        for attr in dirRecordAttrToDSAttrMap:
+                            try:
+                                value = getattr(xmlDirectoryRecord, attr)
+                                dsRecordAttributes[dirRecordAttrToDSAttrMap[attr]] = value
+                            except AttributeError:
+                                # No value
+                                pass
+                        return dsRecordAttributes
+
+                    dsRecord = None
+                    dsRecordAttributes = dsRecordAttributesFromDirectoryRecord( xmlDirectoryRecord )
+                    try:
+                        dsRecord = VCardRecord(self, dsRecordAttributes,)
+                        vCardText = dsRecord.vCardText()
+                   
+                    except:
+                        traceback.print_exc()
+                        self.log_info("Could not get vcard for ds record %s" % (dsRecord,))
+                    else:
+                        self.log_debug("vCardRecordsForAddressBookQuery: VCard text =\n%s" % (vCardText, ))
+                        queryRecords.append(dsRecord)
+                
+                
+                # only get requested number of record results
+                maxRecords -= len(xmlDirectoryRecords)
+                if maxRecords <= 0:
+                    limited = True
+                    break
+
+                         
+        self.log_info("limited  %s len(queryRecords) %s" % (limited,len(queryRecords),))
+        returnValue((queryRecords, limited,))        
+

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py	2012-03-17 01:43:01 UTC (rev 8905)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py	2012-03-17 03:37:14 UTC (rev 8906)
@@ -38,6 +38,7 @@
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.xmlutil import addSubElement, createElement, elementToXML
 from uuid import uuid4
+from twisted.internet.defer import succeed
 
 
 class XMLDirectoryService(DirectoryService):
@@ -337,7 +338,8 @@
             recordTypes = list(self.recordTypes())
         else:
             recordTypes = (recordType,)
-
+        
+        records = []
         for recordType in recordTypes:
             for xmlPrincipal in self._accounts()[recordType].itervalues():
                 if xmlPrincipalMatches(xmlPrincipal):
@@ -345,9 +347,9 @@
                     # Load/cache record from its GUID
                     record = self.recordWithGUID(xmlPrincipal.guid)
                     if record:
-                        yield record
+                        records.append(record)
+        return succeed(records) 
 
-
     def _addElement(self, parent, principal):
         """
         Create an XML element from principal and add it as a child of parent

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py	2012-03-17 01:43:01 UTC (rev 8905)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py	2012-03-17 03:37:14 UTC (rev 8906)
@@ -207,8 +207,10 @@
 
 
 directoryAddressBookBackingServiceDefaultParams = {
-    "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
-        "xmlFile": "/etc/carddavd/accounts.xml",
+    "twistedcaldav.directory.xmldirectorybacker.XMLDirectoryBackingService": {
+        "xmlFile": "accounts.xml",
+        "recordTypes": ("users",),
+        "statSeconds" : 15,
     },
     "twistedcaldav.directory.opendirectorybacker.OpenDirectoryBackingService": {
         "queryPeopleRecords": True,
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120316/104f3cf8/attachment-0001.html>


More information about the calendarserver-changes mailing list