[CalendarServer-changes] [358] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Nov 2 17:40:54 PST 2006


Revision: 358
          http://trac.macosforge.org/projects/calendarserver/changeset/358
Author:   wsanchez at apple.com
Date:     2006-11-02 17:40:54 -0800 (Thu, 02 Nov 2006)

Log Message:
-----------
Reorg: split directory.py into a module and break it up into parts.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-dev.plist
    CalendarServer/trunk/conf/repository-proxy.xml
    CalendarServer/trunk/conf/repository.xml
    CalendarServer/trunk/twistedcaldav/repository.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/directory/
    CalendarServer/trunk/twistedcaldav/directory/__init__.py
    CalendarServer/trunk/twistedcaldav/directory/cred.py
    CalendarServer/trunk/twistedcaldav/directory/directory.py
    CalendarServer/trunk/twistedcaldav/directory/idirectory.py
    CalendarServer/trunk/twistedcaldav/directory/opendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/resource.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/directory.py

Modified: CalendarServer/trunk/conf/caldavd-dev.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-dev.plist	2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/caldavd-dev.plist	2006-11-03 01:40:54 UTC (rev 358)
@@ -27,7 +27,7 @@
   <true/>
 
   <key>DocumentRoot</key>
-  <string>twistedcaldav/test/data/</string>
+  <string>/Library/CalendarServer/Documents</string>
 
   <key>Port</key>
   <integer>8008</integer>
@@ -42,25 +42,22 @@
   <integer>8443</integer>
 
   <key>SSLPrivateKey</key>
-  <string>conf/server.pem</string>
+  <string>/etc/certificates/Default.key</string>
 
   <key>SSLCertificate</key>
-  <string>conf/server.pem</string>
+  <string>/etc/certificates/Default.crt</string>
 
-  <key>ManholePort</key>
-  <integer>8000</integer>
-
   <key>ServerLogFile</key>
-  <string>bin/server.log</string>
+  <string>/var/log/caldavd/server.log</string>
 
   <key>ErrorLogFile</key>
-  <string>bin/error.log</string>
+  <string>/var/log/caldavd/error.log</string>
 
   <key>PIDFile</key>
-  <string>bin/twistd-caldavd.pid</string>
+  <string>/var/log/caldavd/caldavd.pid</string>
 
   <key>Repository</key>
-  <string>conf/repository-dev.xml</string>
+  <string>/etc/caldavd/repository.xml</string>
 
   <key>CreateAccounts</key>
   <true/>
@@ -69,11 +66,8 @@
   <true/>
 
   <key>twistdLocation</key>
-  <string>../Twisted/bin/twistd</string>
+  <string>/usr/share/caldavd/bin/twistd</string>
 
-  <key>EventExpirationDays</key>
-  <integer>0</integer>
-
   <key>MaximumAttachmentSizeBytes</key>
   <integer>1048576</integer><!-- 1Mb -->
 

Modified: CalendarServer/trunk/conf/repository-proxy.xml
===================================================================
--- CalendarServer/trunk/conf/repository-proxy.xml	2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/repository-proxy.xml	2006-11-03 01:40:54 UTC (rev 358)
@@ -118,7 +118,7 @@
           </members>
         </collection>
         <collection name="principals" initialize="yes">
-          <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
+          <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
           <params>
             <param>
               <key>DirectoryNode</key>
@@ -136,7 +136,7 @@
           </properties>
           <members>
             <collection name="users" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>
@@ -149,7 +149,7 @@
               <members/>
             </collection>
             <collection name="groups" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>
@@ -162,7 +162,7 @@
               <members/>
             </collection>
             <collection name="resources" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>

Modified: CalendarServer/trunk/conf/repository.xml
===================================================================
--- CalendarServer/trunk/conf/repository.xml	2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/conf/repository.xml	2006-11-03 01:40:54 UTC (rev 358)
@@ -118,7 +118,7 @@
           </members>
         </collection>
         <collection name="principals" initialize="yes">
-          <pytype>twistedcaldav.directory.DirectoryPrincipalProvisioningResource</pytype>
+          <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
           <params>
             <param>
               <key>DirectoryNode</key>
@@ -136,7 +136,7 @@
           </properties>
           <members>
             <collection name="users" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryUserPrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>
@@ -149,7 +149,7 @@
               <members/>
             </collection>
             <collection name="groups" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryGroupPrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>
@@ -162,7 +162,7 @@
               <members/>
             </collection>
             <collection name="resources" tag="principals">
-              <pytype>twistedcaldav.directory.DirectoryResourcePrincipalProvisioningResource</pytype>
+              <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
               <properties>
                 <acl>
                   <ace>

Added: CalendarServer/trunk/twistedcaldav/directory/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/__init__.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,28 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Calendar server directory service module.
+"""
+
+__all__ = [
+    "directory",
+    "idirectory",
+    "opendirectory",
+    "resource",
+]

Copied: CalendarServer/trunk/twistedcaldav/directory/cred.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/cred.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/cred.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,57 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+    "DirectoryCredentialsChecker",
+]
+
+from twisted.internet.defer import succeed
+from twisted.cred.error import UnauthorizedLogin
+from twisted.web2.dav.auth import IPrincipalCredentials
+from twisted.web2.dav.auth import TwistedPropertyChecker
+
+from twistedcaldav import customxml
+
+class DirectoryCredentialsChecker (TwistedPropertyChecker):
+    def __init__(self, service):
+        """
+        @param service: an L{IDirectoryService} provider.
+        """
+        self.service = service
+
+    def requestAvatarId(self, credentials):
+        # If there is no calendar principal URI then the calendar user is disabled.
+        credentials = IPrincipalCredentials(credentials)
+        if not credentials.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+            # Try regular password check
+            return TwistedPropertyChecker.requestAvatarId(self, credentials)
+
+        user = self.service.findUserWithShortName(credentials.credentials.username)
+        raise UnauthorizedLogin("Unknown credentials type for principal: %s" % (credentials.authnURI,))
+
+        if not user:
+            raise UnauthorizedLogin("No such user: %s" % (user,))
+
+        if user.authenticate(credentials.credentials):
+            return succeed((credentials.authnURI, credentials.authzURI))
+        else:
+            raise UnauthorizedLogin("Incorrect credentials for user: %s" % (user,)) 

Copied: CalendarServer/trunk/twistedcaldav/directory/directory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,46 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Generic directory service classes.
+"""
+
+__all__ = [
+    "DirectoryRecord",
+]
+
+from zope.interface import implements
+
+from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
+
+class DirectoryService(object):
+    implements(IDirectoryService)
+
+class DirectoryRecord(object):
+    implements(IDirectoryRecord)
+
+    def __init__(self, directory, recordType, guid, shortName, fullName=None, calendarUserAddresses=()):
+        self.directory             = directory
+        self.recordType            = recordType
+        self.guid                  = guid
+        self.shortName             = shortName
+        self.fullName              = fullName
+        self.calendarUserAddresses = calendarUserAddresses
+
+    def authenticate(credentials):
+        return False

Copied: CalendarServer/trunk/twistedcaldav/directory/idirectory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/idirectory.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/idirectory.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,64 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Directory service interfaces.
+"""
+
+__all__ = [
+    "IDirectoryService",
+    "IDirectoryRecord",
+]
+
+from zope.interface import Attribute, Interface
+
+class IDirectoryService(Interface):
+    """
+    Directory Service
+    """
+    def recordTypes():
+        """
+        @return: a sequence of strings denoting the record types that are kept
+            in the directory.  For example: C{["users", "groups", "resources"]}.
+        """
+
+    def listRecords(type):
+        """
+        @param type: the type of records to retrieve.
+        @return: an iterable of records of the given type.
+        """
+
+class IDirectoryRecord(Interface):
+    """
+    Directory Record
+    """
+    directory             = Attribute("The L{IDirectoryService} this record exists in.")
+    recordType            = Attribute("The type of this record.")
+    guid                  = Attribute("The GUID of this record.")
+    shortName             = Attribute("The name of this record.")
+    fullName              = Attribute("The full name of this record.")
+    calendarUserAddresses = Attribute("A sequence of calendar user addresses of this record.")
+
+    def authenticate(credentials):
+        """
+        Verify that the given credentials can authenticate the principal
+        represented by this record.
+        @param credentials: the credentials to authenticate with.
+        @return: C{True} if the given credentials match this record,
+            C{False} otherwise.
+        """

Copied: CalendarServer/trunk/twistedcaldav/directory/opendirectory.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectory.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectory.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,92 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Apple Open Directory implementation.
+"""
+
+__all__ = [
+    "OpenDirectoryService",
+    "OpenDirectoryRecord",
+]
+
+import opendirectory
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+
+class OpenDirectoryService(DirectoryService):
+    """
+    Open Directory implementation of L{IDirectoryService}.
+    """
+    def __init__(self, node="/Search"):
+        directory = opendirectory.odInit(node)
+        if directory is None:
+            raise ValueError("Failed to open Open Directory Node: %s" % (node,))
+
+        self._directory = directory
+
+    def recordTypes(self):
+        return ("users", "groups", "resources")
+
+    def listRecords(self, recordType):
+        def makeRecord(shortName, guid, lastModified, principalURI):
+            if not guid:
+                return None
+
+            ##
+            # FIXME: Also verify that principalURI is on this server
+            # Which probably means that the host information needs to be on
+            # the site object, and that we need the site object passed to
+            # __init__() here.
+            ##
+
+            return OpenDirectoryRecord(
+                directory = self,
+                recordType = recordType,
+                guid = guid,
+                shortName = shortName,
+                fullName = None,
+                calendarUserAddresses = (),
+            )
+
+        if recordType == "users":
+            for data in opendirectory.listUsers(self._directory):
+                yield makeRecord(*data)
+            return
+
+        if recordType == "groups":
+            for data in opendirectory.listGroups(self._directory):
+                yield makeRecord(*data)
+            return
+
+        if recordType == "resources":
+            for data in opendirectory.listResources(self._directory):
+                yield makeRecord(*data)
+            return
+
+        raise AssertionError("Unknown Open Directory record type: %s" % (recordType,))
+
+class OpenDirectoryRecord(DirectoryRecord):
+    """
+    Open Directory implementation of L{IDirectoryRecord}.
+    """
+    def authenticate(self, credentials):
+        if isinstance(credentials, credentials.UsernamePassword):
+            return opendirectory.authenticateUser(self.directory, self.shortName, credentials.password)
+
+        return False

Copied: CalendarServer/trunk/twistedcaldav/directory/resource.py (from rev 355, CalendarServer/trunk/twistedcaldav/directory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/resource.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -0,0 +1,982 @@
+##
+# Copyright (c) 2006 Apple Computer, 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+    "DirectoryPrincipalFile",
+    "DirectoryUserPrincipalProvisioningResource",
+    "DirectoryGroupPrincipalProvisioningResource",
+    "DirectoryResourcePrincipalProvisioningResource",
+    "DirectoryPrincipalProvisioningResource",
+]
+
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.internet import task
+from twisted.internet.defer import succeed
+from twisted.cred import credentials
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.static import DAVFile
+from twisted.web2.dav.util import joinURL
+from twisted.web2.http import HTTPError
+from twisted.web2.http import StatusResponse
+
+from twistedcaldav import caldavxml
+from twistedcaldav import customxml
+from twistedcaldav.principalindex import GroupIndex
+from twistedcaldav.principalindex import ResourceIndex
+from twistedcaldav.principalindex import UserIndex
+from twistedcaldav.resource import CalendarPrincipalCollectionResource
+from twistedcaldav.static import CalendarPrincipalFile
+
+import dsattributes
+import opendirectory
+import os
+import unicodedata
+
+class DirectoryPrincipalFile (CalendarPrincipalFile):
+    """
+    Directory principal resource.
+    """
+    def __init__(self, parent, path, url):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  This is the url which
+            will be returned by L{principalURL}.
+        """
+        super(DirectoryPrincipalFile, self).__init__(path, url)
+
+        self._parent = parent
+
+    def checkCredentials(self, creds):
+        """
+        Check whether the provided credentials can be used to authenticate this prinicpal.
+        
+        @param creds: the L{ICredentials} for testing.
+        @return:      True if the credentials match, False otherwise
+        """
+
+        # If there is no calendar principal URI then the calendar user is disabled.
+        if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+            return False
+
+        if isinstance(creds, credentials.UsernamePassword):
+            return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
+        else:
+            return False
+
+    def directory(self):
+        """
+        Get the directory object used for directory operations.
+        
+        @return:      C{object} for the directory instance
+        """
+
+        return self._parent.directory
+
+    def groupMembers(self):
+        """
+        See L{IDAVPrincipalResource.groupMembers}.
+        """
+        
+        # Check for the list of group member GUIDs
+        if self.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
+            # Get the list of GUIDs from the WebDAV private property
+            memberguids = self.readDeadProperty(customxml.TwistedGroupMemberGUIDs())
+            guids = [str(e) for e in memberguids.children]
+            
+            # Try to find each GUID in collections
+            result = []
+            for guid in guids:
+                uri = DirectoryTypePrincipalProvisioningResource.findAnyGUID(guid)
+                if uri is not None:
+                    result.append(uri)
+            
+            return result            
+
+        return ()
+
+    def groupMemberships(self):
+        """
+        See L{IDAVPrincipalResource.groupMemberships}.
+        """
+        
+        # Find any groups that match this user's GUID
+        guid = self.getGUID()
+        return DirectoryTypePrincipalProvisioningResource.findAnyGroupGUID(guid)
+
+    def getPropertyValue(self, cls):
+        """
+        Get the requested proeprty value or return empty string.
+        """
+        
+        if self.hasDeadProperty(cls()):
+            prop = self.readDeadProperty(cls())
+            return str(prop)
+        else:
+            return ""
+    
+    def setPropertyValue(self, str, cls):
+        """
+        Set the requested property value or remove it if the value is empty.
+        """
+
+        if str:
+            self.writeDeadProperty(cls.fromString(str))
+        else:
+            self.removeDeadProperty(cls())
+
+    def getGUID(self):
+        return self.getPropertyValue(customxml.TwistedGUIDProperty)
+    
+    def readProperty(self, property, request):
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        namespace, name = qname
+
+        if namespace == caldavxml.caldav_namespace:
+            if name == "calendar-user-address-set":
+                return succeed(caldavxml.CalendarUserAddressSet(
+                    *[davxml.HRef().fromString(uri) for uri in self.calendarUserAddresses()]
+                ))
+
+        return super(DirectoryPrincipalFile, self).readProperty(qname, request)
+
+    def writeProperty(self, property, request):
+        # This resource is read-only.
+        raise HTTPError(StatusResponse(
+            responsecode.FORBIDDEN,
+            "Protected property %s may not be set." % (property.sname(),)
+        ))
+
+    def calendarUserAddresses(self):
+        # Must have a valid calendar principal uri
+        if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
+            return (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI),)
+        else:
+            # If there is no calendar principal URI then the calendar user is disabled so do not provide
+            # a valid calendar address.
+            return ()
+
+    def matchesCalendarUserAddress(self, request, address):
+        # By default we will always allow either a relative or absolute URI to the principal to
+        # be supplied as a valid calendar user address.
+
+        # Try calendar principal URI
+        return self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI) and (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI) == address)
+
+    def enable(self, calhome, enable):
+        """
+        Enable or disable this principal and access to any calendars owned by it.
+        
+        @param calhome: L{DAVFile} for the container of the calendar home of this user.
+        @param enable: C{True} to enable, C{False} to disable.
+        """
+        # Get home collection resource
+        calrsrc = calhome.getChild(self.principalUID())
+
+        # Handle access for the calendar home
+        if enable:
+            calrsrc.disable(False)
+        else:
+            calrsrc.disable(True)
+
+    def remove(self, calhome):
+        """
+        Remove this principal by hiding (archiving) any calendars owned by it. This is done
+        by turning on the disabling and renaming the calendar home to ensure a future user
+        with the same id won't see the old calendars.
+        
+        @param calhome: L{DAVFile} for the container of the calendar home of this user.
+        """
+        
+        # Get home collection resource
+        calrsrc = calhome.getChild(self.principalUID())
+
+        # Disable access for the calendar home
+        calrsrc.disable(True)
+        
+        # Rename the calendar home to the original name with the GUID appended
+        newname = self.principalUID() + "-" + self.getPropertyValue(customxml.TwistedGUIDProperty)
+        
+        try:
+            # Make sure the new name is not already in use
+            if os.path.exists(newname):
+                count = 1
+                tempname = newname + "-%d"
+                while(os.path.exists(tempname % count)):
+                    count += 1
+                newname = tempname % count 
+            os.rename(calrsrc.fp.path, calrsrc.fp.sibling(newname).path)
+        except OSError:
+            log.msg("Directory: Failed to rename %s to %s when deleting a principal" %
+                    (calrsrc.fp.path, calrsrc.fp.sibling(newname).path))
+            
+            # Remove the disabled property to prevent lock out in the future
+            calrsrc.disable(False)
+
+class DirectoryTypePrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
+    """
+    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+    as needed.
+    
+    This includes a periodic task for refreshing the cached data.
+    """
+    periodicSyncIntervalSeconds = 60.0
+    
+    typeUnknown  = 0
+    typeUser     = 1
+    typeGroup    = 2
+    typeResource = 3
+
+    def __init__(self, path, url):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  Provisioned child
+            resources will use a URL based on C{url} as their primary URLs.
+        @param directory: the reference to the directory to use
+        """
+        CalendarPrincipalCollectionResource.__init__(self, url)
+        DAVFile.__init__(self, path)
+        self.directory = None
+        self.calendarhomeroot = None
+        self.index = None
+        self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
+
+    def setup(self, directory):
+        self.directory = directory
+
+    def initialize(self, homeuri, home):
+        """
+        May be called during repository account initialization.
+         
+        @param homeuri: C{str} uri of the calendar home root.
+        @param home: L{DAVFile} of the calendar home root.
+        """
+        
+        # Make sure index is valid and sync with directory
+        self.index.check()
+        self.calendarhomeroot = (homeuri, home)
+
+        #
+        # There is a problem with the interaction of Directory Services and the
+        # fork/fork process the server goes through to daemonize itself. For some
+        # resaon, if DS is used before the fork, then calls to it afterwards all
+        # return eServerSendError (-14740) errors.
+        # 
+        # To get around this we must not use opendirectory module calls here, as this
+        # method gets run before the fork/fork. So instead, we schedule a sync
+        # operation to occur one second after the reactor starts up - which is after
+        # the fork/fork. The problem with this is that the http server is already up
+        # and running at that point BEFORE any initially provisioning is done.
+        #
+
+        # Create a periodic sync operation to keep the cached user list
+        # in sync with the directory server.
+        def periodicSync(self):
+            self.syncNames()
+        
+        # Add initial sync operation
+        reactor.callLater(1.0, periodicSync, self) #@UndefinedVariable
+
+        # Add periodic sync operations
+        runner = task.LoopingCall(periodicSync, self)
+        runner.start(DirectoryTypePrincipalProvisioningResource.periodicSyncIntervalSeconds, now=False)
+    
+    def listNames(self):
+        """
+        List all the names currently in the directory.
+
+        @return: C{list} containing C{str}'s for each name found, or C{None} if failed.
+        """
+        raise NotImplementedError
+
+    def listIndexAttributes(self):
+        """
+        List all the names currently in the directory with specific attributes needed for indexing.
+
+        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+            The C{tuple} elements are: uid, guid, last-modified.
+        """
+        raise NotImplementedError
+
+    def listCommonAttributes(self, names):
+        """
+        List specified names currently in the directory returning useful attributes.
+
+        @param names: C{list} of record entries to list attributes for.
+        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+            or C{None} on failure.
+        """
+        raise NotImplementedError
+
+    def validName(self, name):
+        """
+        Verify that the supplied name exists as an entry in the directory and that the
+        name corresponds to one that can use the calendar.
+
+        @param name: C{str} of the name to check.
+        @return: C{True} if the name exists, C{False} otherwise.
+        """
+        raise NotImplementedError
+
+    def directoryAttributes(self, name):
+        """
+        Return the attributes relevant to the directory entry.
+        
+        @param name: C{str} containing the name for the directory entry.
+        @return: C{dict} containing the attribute key/value map.
+        """
+        raise NotImplementedError
+
+    def syncNames(self):
+        """
+        Synchronize the data in the directory with the local cache of resources in the file system.
+        """
+        #log.msg("Directory: Synchronizing cache for %s" % (self.getTitle(),))
+
+        # Get index entries from directory and from cache
+        remoteindex = self.listIndexAttributes()
+        localindex = self.index.listIndex()
+
+        # Create dicts indexed by GUID for each
+        remotedict = {}
+        for entry in remoteindex:
+            remotedict[entry[dsattributes.indexGUID]] = entry
+        localdict = {}
+        for entry in localindex:
+            localdict[entry[dsattributes.indexGUID]] = entry
+
+        # Extract list of GUIDs in each
+        remoteguids = [entry[dsattributes.indexGUID] for entry in remoteindex]
+        localguids = [entry[dsattributes.indexGUID] for entry in localindex]
+
+        remoteguidset = set(remoteguids)
+        remoteguids = None
+        localguidset = set(localguids)
+        localguids = None
+        
+        new_remote = list(remoteguidset.difference(localguidset))
+        removed_remote = list(localguidset.difference(remoteguidset))
+        
+        # Remove old principals
+        old_names = [localdict[guid][dsattributes.indexUID] for guid in removed_remote]
+        old_names.sort()
+        for name in old_names:
+            self.removePrincipal(name, True)
+
+        # Get new ones (but only those with a CalendarPrincipalURI attribute)
+        new_names = [remotedict[guid][dsattributes.indexUID] for guid in new_remote if remotedict[guid][dsattributes.indexCalendarPrincipalURI]]
+
+        # Get all the directory entries for the new ones in one go for better performance
+        if new_names:
+            new_entries = self.listCommonAttributes(new_names)
+            new_names = [n for n in new_entries.iterkeys()]
+            new_names.sort()
+            for name in new_names:
+                self.addPrincipal(name, attrs=new_entries[name], fast=True)
+            
+        # Look for changes in entries
+        common_entries = list(remoteguidset.intersection(localguidset))
+        for guid in common_entries:
+            old = localdict[guid]
+            new = remotedict[guid]
+            if old != new:
+                # Special case issue with unicode normalization of names
+                if ((old[dsattributes.indexUID] != new [dsattributes.indexUID]) and
+                    (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
+                     unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8"))) and
+                    (old[1:] == new[1:])):
+                    continue
+                
+                self.changedPrincipal(old, new)
+        
+        # Commit index after all changes are done
+        self.index.commit()
+
+    def addPrincipal(self, name, attrs=None, fast=False):
+        """
+        Add a new principal resource to the server.
+        
+        @param name: C{str} containing the name of the resource to add.
+        @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
+        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+        """
+        # This will create it
+        child_fp = self.fp.child(name)
+        assert not child_fp.exists()
+
+        assert self.exists()
+        assert self.isCollection()
+
+        child_fp.open("w").close()
+        
+        # Now update the principal's cached data
+        self.updatePrincipal(name, attrs=attrs, fast=fast, new=True, nolog=True)
+        
+        log.msg("Directory: Add %s to %s" % (name, self.getTitle()))
+    
+    def changedPrincipal(self, old, new, fast=False):
+        """
+        Change a new principal resource to sync with directory.
+        
+        @param old: C{str} containing the name of the original resource.
+        @param new: C{str} containing the name of the new resource.
+        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+        """
+        # Look for change to calendar enabled state
+        
+        # See if the name changed because that is a real pain!
+        if ((old[dsattributes.indexUID] != new[dsattributes.indexUID]) and
+            (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
+             unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8")))):
+            self.renamePrincipal(old[dsattributes.indexUID], new[dsattributes.indexUID])
+        
+        # See if change in enable state
+        enable_state = old[dsattributes.indexCalendarPrincipalURI] != new[dsattributes.indexCalendarPrincipalURI]
+        
+        # Do update (no log message if enable state is being changed as that will generate a log message itself)
+        self.updatePrincipal(new[dsattributes.indexUID], nolog=enable_state)
+
+        # Look for change in calendar enable state
+        if enable_state:
+            self.enablePrincipal(new[dsattributes.indexUID], len(new[dsattributes.indexCalendarPrincipalURI]) != 0)
+
+    def renamePrincipal(self, old, new):
+        """
+        Change a principal resource name to sync with directory.
+        
+        @param old: C{str} containing the name of the original resource.
+        @param new: C{str} containing the name of the new resource.
+        """
+        log.msg("Directory: Renamed Principal %s to %s in %s" % (old, new, self.getTitle()))
+        raise NotImplementedError
+    
+    def updatePrincipal(self, name, attrs = None, fast=False, new=False, nolog=False):
+        """
+        Update details about the named principal in the principal's own property store
+        and the principal collection index.
+        
+        @param name: C{str} containing the principal name to update.
+        @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
+        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+        @param new: C{True} when this update is the result of adding a new principal,
+            C{False} otherwise.
+        """
+        # Get attributes from directory
+        if attrs is None:
+            attrs = self.directoryAttributes(name)
+        realname = attrs.get(dsattributes.attrRealName, None)
+        guid = attrs.get(dsattributes.attrGUID, None)
+        lastModified = attrs.get(dsattributes.attrLastModified, None)
+        principalurl = attrs.get(dsattributes.attrCalendarPrincipalURI, None)
+
+        # Do provisioning (if the principal is a resource we will turn on the auto-provisioning option)
+        principal = self.getChild(name)
+        if principal is None:
+            log.msg("Directory: Failed to update missing principal: %s in %s" % (name, self.getTitle()))
+            return
+
+        if (new):
+            principal.provisionCalendarAccount(name, None, True, None, self.calendarhomeroot,
+                                               None, ["calendar"], self.type == DirectoryTypePrincipalProvisioningResource.typeResource)
+        
+        # Add directory specific attributes to principal
+        principal.setPropertyValue(realname, davxml.DisplayName)
+        principal.setPropertyValue(guid, customxml.TwistedGUIDProperty)
+        principal.setPropertyValue(lastModified, customxml.TwistedLastModifiedProperty)
+        principal.setPropertyValue(principalurl, customxml.TwistedCalendarPrincipalURI)
+        
+        # Special for group
+        if self.type == DirectoryTypePrincipalProvisioningResource.typeGroup:
+            # Get comma separated list of members and split into a list
+            groupmembers = attrs.get(dsattributes.attrGroupMembers, None)
+            if isinstance(groupmembers, list):
+                members = groupmembers
+            elif isinstance(groupmembers, str):
+                members = [groupmembers]
+            else:
+                members = []
+            
+            # Create and write the group property
+            children = [customxml.TwistedGUIDProperty.fromString(s) for s in members]
+            principal.writeDeadProperty(customxml.TwistedGroupMemberGUIDs(*children))
+        
+        # Do index
+        self.index.addPrincipal(name, principal, fast)
+
+        if not nolog:
+            log.msg("Directory: Updated %s in %s" % (name, self.getTitle()))
+    
+    def enablePrincipal(self, name, enable):
+        """
+        Enable or disable calendar access for this principal.
+        
+        @param enable: C{True} to enable, C{False} to disable
+        """
+        principal = self.getChild(name)
+        if principal is None:
+            log.msg("Directory: Failed to enable/disable missing principal: %s in %s" % (name, self.getTitle()))
+            return
+
+        if enable:
+            principal.enable(self.calendarhomeroot[1], True)
+            log.msg("Directory: Enabled %s in %s" % (name, self.getTitle()))
+        else:
+            principal.enable(self.calendarhomeroot[1], False)
+            log.msg("Directory: Disabled %s in %s" % (name, self.getTitle()))
+
+    def removePrincipal(self, name, fast=False):
+        """
+        Remove a principal from the cached resources.
+        
+        @param name: C{str} containing the name of the principal to remove.
+        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
+        """
+        
+        # Get the principal to 'hide' its calendars
+        principal = self.getChild(name)
+        if principal is None:
+            log.msg("Directory: Failed to remove missing principal: %s in %s" % (name, self.getTitle()))
+        else:
+            principal.remove(self.calendarhomeroot[1])
+
+        # Now remove the principal resource itself
+        child_fp = self.fp.child(name)
+        if child_fp.exists():
+            os.remove(child_fp.path)
+
+        # Do index
+        self.index.deleteName(name, fast)
+
+        log.msg("Directory: Delete %s from %s" % (name, self.getTitle()))
+    
+    @classmethod
+    def findAnyGUID(clazz, guid):
+        """
+        Find the principal associated with the specified GUID.
+
+        @param guid: the C{str} containing the GUID to match.
+        @return: C{str} with matching principal URI, or C{None}
+        """
+        for url in clazz._principleCollectionSet.keys():
+            try:
+                pcollection = clazz._principleCollectionSet[url]
+                if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
+                    principal = pcollection.findGUID(guid)
+                    if principal is not None:
+                        return principal
+            except ReferenceError:
+                pass
+
+        return None
+
+    def findGUID(self, guid):
+        """
+        See if a principal with the specified GUID exists and if so return its principal URI.
+        
+        @param guid: the C{str} containing the GUID to match.
+        @return: C{str} with matching principal URI, or C{None}
+        """
+        name = self.index.nameFromGUID(guid)
+        if name is not None:
+            principal = self.getChild(name)
+            return principal.principalURL()
+        else:
+            return None
+
+    @classmethod
+    def findAnyGroupGUID(clazz, guid):
+        """
+        Find the principals containing the specified GUID as a group member.
+
+        @param guid: the C{str} containing the GUID to match.
+        @return: C{list} with matching principal URIs
+        """
+        
+        result = []
+        for url in clazz._principleCollectionSet.keys():
+            try:
+                pcollection = clazz._principleCollectionSet[url]
+                if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
+                    result.extend(pcollection.findGroupGUID(guid))
+            except ReferenceError:
+                pass
+
+        return result
+
+    def findGroupGUID(self, guid):
+        """
+        Find principals with the specified GUID as a group member.
+        
+        @param guid: the C{str} containing the GUID to match.
+        @return: C{list} with matching principal URIs
+        """
+        # Only both for group collections
+        if self.type != DirectoryTypePrincipalProvisioningResource.typeGroup:
+            return []
+        
+        result = []
+        for name in self.listChildren():
+            principal = self.getChild(name)
+            if principal.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
+                guids = principal.readDeadProperty(customxml.TwistedGroupMemberGUIDs)
+                for g in guids.children:
+                    if str(g) == guid:
+                        result.append(principal.principalURL())
+                        break
+
+        return result
+
+    def isCollection(self):
+        """
+        See L{IDAVResource.isCollection}.
+        """
+        return True
+
+    def getChild(self, name):
+        """
+        Look up a child resource.
+        @return: the child of this resource with the given name.
+        """
+        if name == "":
+            return self
+
+        child = self.putChildren.get(name, None)
+        if child: return child
+
+        child_fp = self.fp.child(name)
+        if child_fp.exists():
+            return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
+        else:
+            return None
+
+    def principalSearchPropertySet(self):
+        """
+        See L{IDAVResource.principalSearchPropertySet}.        
+        """
+        return davxml.PrincipalSearchPropertySet(
+            davxml.PrincipalSearchProperty(
+                davxml.PropertyContainer(
+                    davxml.DisplayName()
+                ),
+                davxml.Description(
+                    davxml.PCDATAElement("Display Name"),
+                    **{"xml:lang":"en"}
+                ),
+            ),
+            davxml.PrincipalSearchProperty(
+                davxml.PropertyContainer(
+                    caldavxml.CalendarUserAddressSet()
+                ),
+                davxml.Description(
+                    davxml.PCDATAElement("Calendar User Addresses"),
+                    **{"xml:lang":"en"}
+                ),
+            ),
+        )
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            # TODO: Fix this - not sure how to get URI for second argument of __init__
+            return CalendarPrincipalFile(path, "")
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+    def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+
+class DirectoryUserPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+    """
+    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+    as needed.
+    """
+    def __init__(self, path, url):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  Provisioned child
+            resources will use a URL based on C{url} as their primary URLs.
+        @param directory: the reference to the directory to use
+        """
+        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+        self.index = UserIndex(self)
+        self.type = DirectoryTypePrincipalProvisioningResource.typeUser
+
+    def listNames(self):
+        """
+        List all the names currently in the directory.
+
+        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+        """
+
+        # Lookup all users
+        return [i[0] for i in opendirectory.listUsers(self.directory)]
+ 
+    def listIndexAttributes(self):
+        """
+        List all the names currently in the directory with specific attributes needed for indexing.
+
+        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+            The C{tuple} elements are: uid, guid, last-modified.
+        """
+        return opendirectory.listUsers(self.directory)
+
+    def listCommonAttributes(self, names):
+        """
+        List specified names currently in the directory returning useful attributes.
+
+        @param names: C{list} of record entries to list attributes for.
+        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+            or C{None} on failure.
+        """
+        return opendirectory.listUsersWithAttributes(self.directory, names)
+
+    def validName(self, name):
+        """
+        Verify that the supplied name exists as an entry in the directory.
+
+        @param name: C{str} of the namer to check.
+        @return: C{True} if the name exists, C{False} otherwise.
+        """
+        return opendirectory.checkUser(self.directory, name)
+
+    def directoryAttributes(self, name):
+        """
+        Return the attributes relevant to the directory entry.
+        
+        @param name: C{str} containing the name for the directory entry.
+        @return: C{dict} containing the attribute key/value map.
+        """
+        result = opendirectory.listUsersWithAttributes(self.directory, [name])
+        if result:
+            return result[name]
+        else:
+            return None
+
+    def getTitle(self):
+        return "User Principals"
+
+class DirectoryGroupPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+    """
+    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+    as needed.
+    """
+    def __init__(self, path, url):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  Provisioned child
+            resources will use a URL based on C{url} as their primary URLs.
+        @param directory: the reference to the directory to use
+        """
+        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+        self.index = GroupIndex(self)
+        self.type = DirectoryTypePrincipalProvisioningResource.typeGroup
+
+    def listNames(self):
+        """
+        List all the names currently in the directory.
+
+        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+        """
+
+        # Lookup all users
+        return [i[0] for i in opendirectory.listGroups(self.directory)]
+ 
+    def listIndexAttributes(self):
+        """
+        List all the names currently in the directory with specific attributes needed for indexing.
+
+        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+            The C{tuple} elements are: uid, guid, last-modified.
+        """
+        return opendirectory.listGroups(self.directory)
+
+    def listCommonAttributes(self, names):
+        """
+        List specified names currently in the directory returning useful attributes.
+
+        @param names: C{list} of record entries to list attributes for.
+        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+            or C{None} on failure.
+        """
+        return opendirectory.listGroupsWithAttributes(self.directory, names)
+
+    def validName(self, name):
+        """
+        Verify that the supplied name exists as an entry in the directory.
+
+        @param name: C{str} of the namer to check
+        @return: C{True} if the name exists, C{False} otherwise
+        """
+        return opendirectory.checkGroup(self.directory, name)
+
+    def directoryAttributes(self, name):
+        """
+        Return the attributes relevant to the directory entry.
+        
+        @param name: C{str} containing the name for the directory entry.
+        @return: C{dict} containing the attribute key/value map.
+        """
+        result = opendirectory.listGroupsWithAttributes(self.directory, [name])
+        if result:
+            return result[name]
+        else:
+            return None
+
+    def getTitle(self):
+        return "Group Principals"
+
+class DirectoryResourcePrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
+    """
+    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
+    as needed.
+    """
+    def __init__(self, path, url):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  Provisioned child
+            resources will use a URL based on C{url} as their primary URLs.
+        @param directory: the reference to the directory to use
+        """
+        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
+        self.index = ResourceIndex(self)
+        self.type = DirectoryTypePrincipalProvisioningResource.typeResource
+
+    def listNames(self):
+        """
+        List all the names currently in the directory.
+
+        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
+        """
+
+        # Lookup all users
+        return [i[0] for i in opendirectory.listResources(self.directory)]
+ 
+    def listIndexAttributes(self):
+        """
+        List all the names currently in the directory with specific attributes needed for indexing.
+
+        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
+            The C{tuple} elements are: uid, guid, last-modified.
+        """
+        return opendirectory.listResources(self.directory)
+
+    def listCommonAttributes(self, names):
+        """
+        List specified names currently in the directory returning useful attributes.
+
+        @param names: C{list} of record entries to list attributes for.
+        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
+            or C{None} on failure.
+        """
+        return opendirectory.listResourcesWithAttributes(self.directory, names)
+
+    def validName(self, name):
+        """
+        Verify that the supplied name exists as an entry in the directory.
+
+        @param name: C{str} of the namer to check
+        @return: C{True} if the name exists, C{False} otherwise
+        """
+        return opendirectory.checkResource(self.directory, name)
+
+    def directoryAttributes(self, name):
+        """
+        Return the attributes relevant to the directory entry.
+        
+        @param name: C{str} containing the name for the directory entry.
+        @return: C{dict} containing the attribute key/value map.
+        """
+        result = opendirectory.listResourcesWithAttributes(self.directory, [name])
+        if result:
+            return result[name]
+        else:
+            return None
+
+    def getTitle(self):
+        return "Resource Principals"
+
+class DirectoryPrincipalProvisioningResource (DAVFile):
+    """
+    L{DAVFile} resource which provisions calendar principal resources as needed.
+    """
+    def __init__(self, path, url, params={}):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the primary URL for the resource.  Provisioned child
+            resources will use a URL based on C{url} as their primary URLs.
+        """
+        super(DirectoryPrincipalProvisioningResource, self).__init__(path)
+
+        assert self.exists(), "%s should exist" % (self,)
+        assert self.isCollection(), "%s should be a collection" % (self,)
+
+        # Extract parameters
+        if (params.has_key("DirectoryNode")):
+            self.directory = opendirectory.odInit(params["DirectoryNode"])
+            if self.directory is None:
+                raise ValueError("Failed to open Open Directory Node: %s" % (params["DirectoryNode"],))
+        else:
+            raise ValueError("DirectoryPrincipalProvisioningResource must be configured with an Open Directory Node")
+
+        # Create children
+        for name, clazz in (
+            ("users" , DirectoryUserPrincipalProvisioningResource),
+            ("groups" , DirectoryGroupPrincipalProvisioningResource),
+            ("resources" , DirectoryResourcePrincipalProvisioningResource),
+        ):
+            child_fp = self.fp.child(name)
+            if not child_fp.exists(): child_fp.makedirs()
+            principalCollection = clazz(child_fp.path, joinURL(url, name) + "/")
+            principalCollection.setup(self.directory)
+            self.putChild(name, principalCollection)
+
+    def isCollection(self):
+        """
+        See L{IDAVResource.isCollection}.
+        """
+        return True
+
+    def initialize(self, homeuri, home):
+        """
+        May be called during repository account initialization.
+        This implementation does nothing.
+        
+        @param homeuri: C{str} uri of the calendar home root.
+        @param home: L{DAVFile} of the calendar home root.
+        """
+        for name in ("users", "groups", "resources",):
+            self.getChild(name).initialize(joinURL(homeuri, name), home.getChild(name))
+    
+    def createSimilarFile(self, path):
+        return DAVFile(path)
+
+    def render(self, request):
+        return StatusResponse(
+            responsecode.OK,
+            "This collection contains principal resources",
+            title=self.displayName()
+        )

Deleted: CalendarServer/trunk/twistedcaldav/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory.py	2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/twistedcaldav/directory.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -1,1122 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, 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.
-#
-# DRI: Cyrus Daboo, cdaboo at apple.com
-##
-
-"""
-Implements a directory-backed principal hierarchy.
-"""
-
-__all__ = [
-    "DirectoryPrincipalFile",
-    "DirectoryUserPrincipalProvisioningResource",
-    "DirectoryGroupPrincipalProvisioningResource",
-    "DirectoryResourcePrincipalProvisioningResource",
-    "DirectoryPrincipalProvisioningResource",
-]
-
-from zope.interface import implements, Attribute, Interface
-
-from twisted.cred import checkers, credentials, error
-from twisted.cred.credentials import UsernamePassword
-from twisted.internet import reactor
-from twisted.internet import task
-from twisted.internet.defer import succeed
-from twisted.python import log
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.auth import IPrincipalCredentials
-from twisted.web2.dav.auth import TwistedPropertyChecker
-from twisted.web2.dav.static import DAVFile
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import StatusResponse
-
-from twistedcaldav import caldavxml
-from twistedcaldav import customxml
-from twistedcaldav.principalindex import GroupIndex
-from twistedcaldav.principalindex import ResourceIndex
-from twistedcaldav.principalindex import UserIndex
-from twistedcaldav.resource import CalendarPrincipalCollectionResource
-from twistedcaldav.static import CalendarPrincipalFile
-
-import dsattributes
-import opendirectory
-import os
-import unicodedata
-
-class IDirectoryService(Interface):
-    """
-    Directory Service
-    """
-    def recordTypes():
-        """
-        @return: a sequence of strings denoting the record types that are kept
-            in the directory.  For example: C{["users", "groups", "resources"]}.
-        """
-
-    def listRecords(type):
-        """
-        @param type: the type of records to retrieve.
-        @return: an iterable of records of the given type.
-        """
-
-class IDirectoryRecord(Interface):
-    """
-    Directory Record
-    """
-    directory             = Attribute("The L{IDirectoryService} this record exists in.")
-    recordType            = Attribute("The type of this record.")
-    guid                  = Attribute("The GUID of this record.")
-    shortName             = Attribute("The name of this record.")
-    fullName              = Attribute("The full name of this record.")
-    calendarUserAddresses = Attribute("A sequence of calendar user addresses of this record.")
-
-    def authenticate(credentials):
-        """
-        Verify that the given credentials can authenticate the principal
-        represented by this record.
-        @param credentials: the credentials to authenticate with.
-        @return: C{True} if the given credentials match this record,
-            C{False} otherwise.
-        """
-
-class DirectoryRecord(object):
-    implements(IDirectoryRecord)
-
-    def __init__(self, directory, recordType, guid, shortName, fullName=None, calendarUserAddresses=()):
-        self.directory             = directory
-        self.recordType            = recordType
-        self.guid                  = guid
-        self.shortName             = shortName
-        self.fullName              = fullName
-        self.calendarUserAddresses = calendarUserAddresses
-
-    def authenticate(credentials):
-        return False
-
-class OpenDirectoryService(object):
-    """
-    Open Directory implementation of L{IDirectoryService}.
-    """
-    implements(IDirectoryService)
-
-    def __init__(self, node="/Search"):
-        directory = opendirectory.odInit(node)
-        if directory is None:
-            raise ValueError("Failed to open Open Directory Node: %s" % (node,))
-
-        self._directory = directory
-
-    def recordTypes(self):
-        return ("users", "groups", "resources")
-
-    def listRecords(self, recordType):
-        def makeRecord(shortName, guid, lastModified, principalURI):
-            if not guid:
-                return None
-
-            ##
-            # FIXME: Also verify that principalURI is on this server
-            # Which probably means that the host information needs to be on
-            # the site object, and that we need the site object passed to
-            # __init__() here.
-            ##
-
-            return OpenDirectoryRecord(
-                directory = self,
-                recordType = recordType,
-                guid = guid,
-                shortName = shortName,
-                fullName = None,
-                calendarUserAddresses = (),
-            )
-
-        if recordType == "users":
-            for data in opendirectory.listUsers(self._directory):
-                yield makeRecord(*data)
-            return
-
-        if recordType == "groups":
-            for data in opendirectory.listGroups(self._directory):
-                yield makeRecord(*data)
-            return
-
-        if recordType == "resources":
-            for data in opendirectory.listResources(self._directory):
-                yield makeRecord(*data)
-            return
-
-        raise AssertionError("Unknown Open Directory record type: %s" % (recordType,))
-
-class OpenDirectoryRecord(DirectoryRecord):
-    """
-    Open Directory implementation of L{IDirectoryRecord}.
-    """
-    def authenticate(self, credentials):
-        if isinstance(credentials, credentials.UsernamePassword):
-            return opendirectory.authenticateUser(self.directory, self.shortName, credentials.password)
-
-        return False
-
-######################
-
-class DirectoryCredentialsChecker (TwistedPropertyChecker):
-
-    def requestAvatarId(self, credentials):
-
-        # If there is no calendar principal URI then the calendar user is disabled.
-        pcreds = IPrincipalCredentials(credentials)
-        if not pcreds.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-            # Try regular password check
-            return TwistedPropertyChecker.requestAvatarId(self, credentials)
-
-        creds = pcreds.credentials
-        if isinstance(creds, UsernamePassword):
-            user = creds.username
-            pswd = creds.password
-            if opendirectory.authenticateUser(pcreds.authnPrincipal.directory(), user, pswd):
-                return succeed((pcreds.authnURI, pcreds.authzURI,))
-        
-        raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
-
-class DirectoryPrincipalFile (CalendarPrincipalFile):
-    """
-    Directory principal resource.
-    """
-    def __init__(self, parent, path, url):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  This is the url which
-            will be returned by L{principalURL}.
-        """
-        super(DirectoryPrincipalFile, self).__init__(path, url)
-
-        self._parent = parent
-
-    def checkCredentials(self, creds):
-        """
-        Check whether the provided credentials can be used to authenticate this prinicpal.
-        
-        @param creds: the L{ICredentials} for testing.
-        @return:      True if the credentials match, False otherwise
-        """
-
-        # If there is no calendar principal URI then the calendar user is disabled.
-        if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-            return False
-
-        if isinstance(creds, credentials.UsernamePassword):
-            return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
-        else:
-            return False
-
-    def directory(self):
-        """
-        Get the directory object used for directory operations.
-        
-        @return:      C{object} for the directory instance
-        """
-
-        return self._parent.directory
-
-    def groupMembers(self):
-        """
-        See L{IDAVPrincipalResource.groupMembers}.
-        """
-        
-        # Check for the list of group member GUIDs
-        if self.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
-            # Get the list of GUIDs from the WebDAV private property
-            memberguids = self.readDeadProperty(customxml.TwistedGroupMemberGUIDs())
-            guids = [str(e) for e in memberguids.children]
-            
-            # Try to find each GUID in collections
-            result = []
-            for guid in guids:
-                uri = DirectoryTypePrincipalProvisioningResource.findAnyGUID(guid)
-                if uri is not None:
-                    result.append(uri)
-            
-            return result            
-
-        return ()
-
-    def groupMemberships(self):
-        """
-        See L{IDAVPrincipalResource.groupMemberships}.
-        """
-        
-        # Find any groups that match this user's GUID
-        guid = self.getGUID()
-        return DirectoryTypePrincipalProvisioningResource.findAnyGroupGUID(guid)
-
-    def getPropertyValue(self, cls):
-        """
-        Get the requested proeprty value or return empty string.
-        """
-        
-        if self.hasDeadProperty(cls()):
-            prop = self.readDeadProperty(cls())
-            return str(prop)
-        else:
-            return ""
-    
-    def setPropertyValue(self, str, cls):
-        """
-        Set the requested property value or remove it if the value is empty.
-        """
-
-        if str:
-            self.writeDeadProperty(cls.fromString(str))
-        else:
-            self.removeDeadProperty(cls())
-
-    def getGUID(self):
-        return self.getPropertyValue(customxml.TwistedGUIDProperty)
-    
-    def readProperty(self, property, request):
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
-
-        namespace, name = qname
-
-        if namespace == caldavxml.caldav_namespace:
-            if name == "calendar-user-address-set":
-                return succeed(caldavxml.CalendarUserAddressSet(
-                    *[davxml.HRef().fromString(uri) for uri in self.calendarUserAddresses()]
-                ))
-
-        return super(DirectoryPrincipalFile, self).readProperty(qname, request)
-
-    def writeProperty(self, property, request):
-        # This resource is read-only.
-        raise HTTPError(StatusResponse(
-            responsecode.FORBIDDEN,
-            "Protected property %s may not be set." % (property.sname(),)
-        ))
-
-    def calendarUserAddresses(self):
-        # Must have a valid calendar principal uri
-        if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-            return (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI),)
-        else:
-            # If there is no calendar principal URI then the calendar user is disabled so do not provide
-            # a valid calendar address.
-            return ()
-
-    def matchesCalendarUserAddress(self, request, address):
-        # By default we will always allow either a relative or absolute URI to the principal to
-        # be supplied as a valid calendar user address.
-
-        # Try calendar principal URI
-        return self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI) and (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI) == address)
-
-    def enable(self, calhome, enable):
-        """
-        Enable or disable this principal and access to any calendars owned by it.
-        
-        @param calhome: L{DAVFile} for the container of the calendar home of this user.
-        @param enable: C{True} to enable, C{False} to disable.
-        """
-        # Get home collection resource
-        calrsrc = calhome.getChild(self.principalUID())
-
-        # Handle access for the calendar home
-        if enable:
-            calrsrc.disable(False)
-        else:
-            calrsrc.disable(True)
-
-    def remove(self, calhome):
-        """
-        Remove this principal by hiding (archiving) any calendars owned by it. This is done
-        by turning on the disabling and renaming the calendar home to ensure a future user
-        with the same id won't see the old calendars.
-        
-        @param calhome: L{DAVFile} for the container of the calendar home of this user.
-        """
-        
-        # Get home collection resource
-        calrsrc = calhome.getChild(self.principalUID())
-
-        # Disable access for the calendar home
-        calrsrc.disable(True)
-        
-        # Rename the calendar home to the original name with the GUID appended
-        newname = self.principalUID() + "-" + self.getPropertyValue(customxml.TwistedGUIDProperty)
-        
-        try:
-            # Make sure the new name is not already in use
-            if os.path.exists(newname):
-                count = 1
-                tempname = newname + "-%d"
-                while(os.path.exists(tempname % count)):
-                    count += 1
-                newname = tempname % count 
-            os.rename(calrsrc.fp.path, calrsrc.fp.sibling(newname).path)
-        except OSError:
-            log.msg("Directory: Failed to rename %s to %s when deleting a principal" %
-                    (calrsrc.fp.path, calrsrc.fp.sibling(newname).path))
-            
-            # Remove the disabled property to prevent lock out in the future
-            calrsrc.disable(False)
-
-class DirectoryTypePrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
-    """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
-    
-    This includes a periodic task for refreshing the cached data.
-    """
-    periodicSyncIntervalSeconds = 60.0
-    
-    typeUnknown  = 0
-    typeUser     = 1
-    typeGroup    = 2
-    typeResource = 3
-
-    def __init__(self, path, url):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  Provisioned child
-            resources will use a URL based on C{url} as their primary URLs.
-        @param directory: the reference to the directory to use
-        """
-        CalendarPrincipalCollectionResource.__init__(self, url)
-        DAVFile.__init__(self, path)
-        self.directory = None
-        self.calendarhomeroot = None
-        self.index = None
-        self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
-
-    def setup(self, directory):
-        self.directory = directory
-
-    def initialize(self, homeuri, home):
-        """
-        May be called during repository account initialization.
-         
-        @param homeuri: C{str} uri of the calendar home root.
-        @param home: L{DAVFile} of the calendar home root.
-        """
-        
-        # Make sure index is valid and sync with directory
-        self.index.check()
-        self.calendarhomeroot = (homeuri, home)
-
-        #
-        # There is a problem with the interaction of Directory Services and the
-        # fork/fork process the server goes through to daemonize itself. For some
-        # resaon, if DS is used before the fork, then calls to it afterwards all
-        # return eServerSendError (-14740) errors.
-        # 
-        # To get around this we must not use opendirectory module calls here, as this
-        # method gets run before the fork/fork. So instead, we schedule a sync
-        # operation to occur one second after the reactor starts up - which is after
-        # the fork/fork. The problem with this is that the http server is already up
-        # and running at that point BEFORE any initially provisioning is done.
-        #
-
-        # Create a periodic sync operation to keep the cached user list
-        # in sync with the directory server.
-        def periodicSync(self):
-            self.syncNames()
-        
-        # Add initial sync operation
-        reactor.callLater(1.0, periodicSync, self) #@UndefinedVariable
-
-        # Add periodic sync operations
-        runner = task.LoopingCall(periodicSync, self)
-        runner.start(DirectoryTypePrincipalProvisioningResource.periodicSyncIntervalSeconds, now=False)
-    
-    def listNames(self):
-        """
-        List all the names currently in the directory.
-
-        @return: C{list} containing C{str}'s for each name found, or C{None} if failed.
-        """
-        raise NotImplementedError
-
-    def listIndexAttributes(self):
-        """
-        List all the names currently in the directory with specific attributes needed for indexing.
-
-        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
-            The C{tuple} elements are: uid, guid, last-modified.
-        """
-        raise NotImplementedError
-
-    def listCommonAttributes(self, names):
-        """
-        List specified names currently in the directory returning useful attributes.
-
-        @param names: C{list} of record entries to list attributes for.
-        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
-            or C{None} on failure.
-        """
-        raise NotImplementedError
-
-    def validName(self, name):
-        """
-        Verify that the supplied name exists as an entry in the directory and that the
-        name corresponds to one that can use the calendar.
-
-        @param name: C{str} of the name to check.
-        @return: C{True} if the name exists, C{False} otherwise.
-        """
-        raise NotImplementedError
-
-    def directoryAttributes(self, name):
-        """
-        Return the attributes relevant to the directory entry.
-        
-        @param name: C{str} containing the name for the directory entry.
-        @return: C{dict} containing the attribute key/value map.
-        """
-        raise NotImplementedError
-
-    def syncNames(self):
-        """
-        Synchronize the data in the directory with the local cache of resources in the file system.
-        """
-        #log.msg("Directory: Synchronizing cache for %s" % (self.getTitle(),))
-
-        # Get index entries from directory and from cache
-        remoteindex = self.listIndexAttributes()
-        localindex = self.index.listIndex()
-
-        # Create dicts indexed by GUID for each
-        remotedict = {}
-        for entry in remoteindex:
-            remotedict[entry[dsattributes.indexGUID]] = entry
-        localdict = {}
-        for entry in localindex:
-            localdict[entry[dsattributes.indexGUID]] = entry
-
-        # Extract list of GUIDs in each
-        remoteguids = [entry[dsattributes.indexGUID] for entry in remoteindex]
-        localguids = [entry[dsattributes.indexGUID] for entry in localindex]
-
-        remoteguidset = set(remoteguids)
-        remoteguids = None
-        localguidset = set(localguids)
-        localguids = None
-        
-        new_remote = list(remoteguidset.difference(localguidset))
-        removed_remote = list(localguidset.difference(remoteguidset))
-        
-        # Remove old principals
-        old_names = [localdict[guid][dsattributes.indexUID] for guid in removed_remote]
-        old_names.sort()
-        for name in old_names:
-            self.removePrincipal(name, True)
-
-        # Get new ones (but only those with a CalendarPrincipalURI attribute)
-        new_names = [remotedict[guid][dsattributes.indexUID] for guid in new_remote if remotedict[guid][dsattributes.indexCalendarPrincipalURI]]
-
-        # Get all the directory entries for the new ones in one go for better performance
-        if new_names:
-            new_entries = self.listCommonAttributes(new_names)
-            new_names = [n for n in new_entries.iterkeys()]
-            new_names.sort()
-            for name in new_names:
-                self.addPrincipal(name, attrs=new_entries[name], fast=True)
-            
-        # Look for changes in entries
-        common_entries = list(remoteguidset.intersection(localguidset))
-        for guid in common_entries:
-            old = localdict[guid]
-            new = remotedict[guid]
-            if old != new:
-                # Special case issue with unicode normalization of names
-                if ((old[dsattributes.indexUID] != new [dsattributes.indexUID]) and
-                    (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
-                     unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8"))) and
-                    (old[1:] == new[1:])):
-                    continue
-                
-                self.changedPrincipal(old, new)
-        
-        # Commit index after all changes are done
-        self.index.commit()
-
-    def addPrincipal(self, name, attrs=None, fast=False):
-        """
-        Add a new principal resource to the server.
-        
-        @param name: C{str} containing the name of the resource to add.
-        @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
-        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
-        """
-        # This will create it
-        child_fp = self.fp.child(name)
-        assert not child_fp.exists()
-
-        assert self.exists()
-        assert self.isCollection()
-
-        child_fp.open("w").close()
-        
-        # Now update the principal's cached data
-        self.updatePrincipal(name, attrs=attrs, fast=fast, new=True, nolog=True)
-        
-        log.msg("Directory: Add %s to %s" % (name, self.getTitle()))
-    
-    def changedPrincipal(self, old, new, fast=False):
-        """
-        Change a new principal resource to sync with directory.
-        
-        @param old: C{str} containing the name of the original resource.
-        @param new: C{str} containing the name of the new resource.
-        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
-        """
-        # Look for change to calendar enabled state
-        
-        # See if the name changed because that is a real pain!
-        if ((old[dsattributes.indexUID] != new[dsattributes.indexUID]) and
-            (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
-             unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8")))):
-            self.renamePrincipal(old[dsattributes.indexUID], new[dsattributes.indexUID])
-        
-        # See if change in enable state
-        enable_state = old[dsattributes.indexCalendarPrincipalURI] != new[dsattributes.indexCalendarPrincipalURI]
-        
-        # Do update (no log message if enable state is being changed as that will generate a log message itself)
-        self.updatePrincipal(new[dsattributes.indexUID], nolog=enable_state)
-
-        # Look for change in calendar enable state
-        if enable_state:
-            self.enablePrincipal(new[dsattributes.indexUID], len(new[dsattributes.indexCalendarPrincipalURI]) != 0)
-
-    def renamePrincipal(self, old, new):
-        """
-        Change a principal resource name to sync with directory.
-        
-        @param old: C{str} containing the name of the original resource.
-        @param new: C{str} containing the name of the new resource.
-        """
-        log.msg("Directory: Renamed Principal %s to %s in %s" % (old, new, self.getTitle()))
-        raise NotImplementedError
-    
-    def updatePrincipal(self, name, attrs = None, fast=False, new=False, nolog=False):
-        """
-        Update details about the named principal in the principal's own property store
-        and the principal collection index.
-        
-        @param name: C{str} containing the principal name to update.
-        @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
-        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
-        @param new: C{True} when this update is the result of adding a new principal,
-            C{False} otherwise.
-        """
-        # Get attributes from directory
-        if attrs is None:
-            attrs = self.directoryAttributes(name)
-        realname = attrs.get(dsattributes.attrRealName, None)
-        guid = attrs.get(dsattributes.attrGUID, None)
-        lastModified = attrs.get(dsattributes.attrLastModified, None)
-        principalurl = attrs.get(dsattributes.attrCalendarPrincipalURI, None)
-
-        # Do provisioning (if the principal is a resource we will turn on the auto-provisioning option)
-        principal = self.getChild(name)
-        if principal is None:
-            log.msg("Directory: Failed to update missing principal: %s in %s" % (name, self.getTitle()))
-            return
-
-        if (new):
-            principal.provisionCalendarAccount(name, None, True, None, self.calendarhomeroot,
-                                               None, ["calendar"], self.type == DirectoryTypePrincipalProvisioningResource.typeResource)
-        
-        # Add directory specific attributes to principal
-        principal.setPropertyValue(realname, davxml.DisplayName)
-        principal.setPropertyValue(guid, customxml.TwistedGUIDProperty)
-        principal.setPropertyValue(lastModified, customxml.TwistedLastModifiedProperty)
-        principal.setPropertyValue(principalurl, customxml.TwistedCalendarPrincipalURI)
-        
-        # Special for group
-        if self.type == DirectoryTypePrincipalProvisioningResource.typeGroup:
-            # Get comma separated list of members and split into a list
-            groupmembers = attrs.get(dsattributes.attrGroupMembers, None)
-            if isinstance(groupmembers, list):
-                members = groupmembers
-            elif isinstance(groupmembers, str):
-                members = [groupmembers]
-            else:
-                members = []
-            
-            # Create and write the group property
-            children = [customxml.TwistedGUIDProperty.fromString(s) for s in members]
-            principal.writeDeadProperty(customxml.TwistedGroupMemberGUIDs(*children))
-        
-        # Do index
-        self.index.addPrincipal(name, principal, fast)
-
-        if not nolog:
-            log.msg("Directory: Updated %s in %s" % (name, self.getTitle()))
-    
-    def enablePrincipal(self, name, enable):
-        """
-        Enable or disable calendar access for this principal.
-        
-        @param enable: C{True} to enable, C{False} to disable
-        """
-        principal = self.getChild(name)
-        if principal is None:
-            log.msg("Directory: Failed to enable/disable missing principal: %s in %s" % (name, self.getTitle()))
-            return
-
-        if enable:
-            principal.enable(self.calendarhomeroot[1], True)
-            log.msg("Directory: Enabled %s in %s" % (name, self.getTitle()))
-        else:
-            principal.enable(self.calendarhomeroot[1], False)
-            log.msg("Directory: Disabled %s in %s" % (name, self.getTitle()))
-
-    def removePrincipal(self, name, fast=False):
-        """
-        Remove a principal from the cached resources.
-        
-        @param name: C{str} containing the name of the principal to remove.
-        @param fast: if C{True} then final commit is not done, if C{False} commit is done.
-        """
-        
-        # Get the principal to 'hide' its calendars
-        principal = self.getChild(name)
-        if principal is None:
-            log.msg("Directory: Failed to remove missing principal: %s in %s" % (name, self.getTitle()))
-        else:
-            principal.remove(self.calendarhomeroot[1])
-
-        # Now remove the principal resource itself
-        child_fp = self.fp.child(name)
-        if child_fp.exists():
-            os.remove(child_fp.path)
-
-        # Do index
-        self.index.deleteName(name, fast)
-
-        log.msg("Directory: Delete %s from %s" % (name, self.getTitle()))
-    
-    @classmethod
-    def findAnyGUID(clazz, guid):
-        """
-        Find the principal associated with the specified GUID.
-
-        @param guid: the C{str} containing the GUID to match.
-        @return: C{str} with matching principal URI, or C{None}
-        """
-        for url in clazz._principleCollectionSet.keys():
-            try:
-                pcollection = clazz._principleCollectionSet[url]
-                if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
-                    principal = pcollection.findGUID(guid)
-                    if principal is not None:
-                        return principal
-            except ReferenceError:
-                pass
-
-        return None
-
-    def findGUID(self, guid):
-        """
-        See if a principal with the specified GUID exists and if so return its principal URI.
-        
-        @param guid: the C{str} containing the GUID to match.
-        @return: C{str} with matching principal URI, or C{None}
-        """
-        name = self.index.nameFromGUID(guid)
-        if name is not None:
-            principal = self.getChild(name)
-            return principal.principalURL()
-        else:
-            return None
-
-    @classmethod
-    def findAnyGroupGUID(clazz, guid):
-        """
-        Find the principals containing the specified GUID as a group member.
-
-        @param guid: the C{str} containing the GUID to match.
-        @return: C{list} with matching principal URIs
-        """
-        
-        result = []
-        for url in clazz._principleCollectionSet.keys():
-            try:
-                pcollection = clazz._principleCollectionSet[url]
-                if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
-                    result.extend(pcollection.findGroupGUID(guid))
-            except ReferenceError:
-                pass
-
-        return result
-
-    def findGroupGUID(self, guid):
-        """
-        Find principals with the specified GUID as a group member.
-        
-        @param guid: the C{str} containing the GUID to match.
-        @return: C{list} with matching principal URIs
-        """
-        # Only both for group collections
-        if self.type != DirectoryTypePrincipalProvisioningResource.typeGroup:
-            return []
-        
-        result = []
-        for name in self.listChildren():
-            principal = self.getChild(name)
-            if principal.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
-                guids = principal.readDeadProperty(customxml.TwistedGroupMemberGUIDs)
-                for g in guids.children:
-                    if str(g) == guid:
-                        result.append(principal.principalURL())
-                        break
-
-        return result
-
-    def isCollection(self):
-        """
-        See L{IDAVResource.isCollection}.
-        """
-        return True
-
-    def getChild(self, name):
-        """
-        Look up a child resource.
-        @return: the child of this resource with the given name.
-        """
-        if name == "":
-            return self
-
-        child = self.putChildren.get(name, None)
-        if child: return child
-
-        child_fp = self.fp.child(name)
-        if child_fp.exists():
-            return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
-        else:
-            return None
-
-    def principalSearchPropertySet(self):
-        """
-        See L{IDAVResource.principalSearchPropertySet}.        
-        """
-        return davxml.PrincipalSearchPropertySet(
-            davxml.PrincipalSearchProperty(
-                davxml.PropertyContainer(
-                    davxml.DisplayName()
-                ),
-                davxml.Description(
-                    davxml.PCDATAElement("Display Name"),
-                    **{"xml:lang":"en"}
-                ),
-            ),
-            davxml.PrincipalSearchProperty(
-                davxml.PropertyContainer(
-                    caldavxml.CalendarUserAddressSet()
-                ),
-                davxml.Description(
-                    davxml.PCDATAElement("Calendar User Addresses"),
-                    **{"xml:lang":"en"}
-                ),
-            ),
-        )
-
-    def createSimilarFile(self, path):
-        if path == self.fp.path:
-            return self
-        else:
-            # TODO: Fix this - not sure how to get URI for second argument of __init__
-            return CalendarPrincipalFile(path, "")
-
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-    def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
-
-class DirectoryUserPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
-    """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
-    """
-    def __init__(self, path, url):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  Provisioned child
-            resources will use a URL based on C{url} as their primary URLs.
-        @param directory: the reference to the directory to use
-        """
-        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
-        self.index = UserIndex(self)
-        self.type = DirectoryTypePrincipalProvisioningResource.typeUser
-
-    def listNames(self):
-        """
-        List all the names currently in the directory.
-
-        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
-        """
-
-        # Lookup all users
-        return [i[0] for i in opendirectory.listUsers(self.directory)]
- 
-    def listIndexAttributes(self):
-        """
-        List all the names currently in the directory with specific attributes needed for indexing.
-
-        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
-            The C{tuple} elements are: uid, guid, last-modified.
-        """
-        return opendirectory.listUsers(self.directory)
-
-    def listCommonAttributes(self, names):
-        """
-        List specified names currently in the directory returning useful attributes.
-
-        @param names: C{list} of record entries to list attributes for.
-        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
-            or C{None} on failure.
-        """
-        return opendirectory.listUsersWithAttributes(self.directory, names)
-
-    def validName(self, name):
-        """
-        Verify that the supplied name exists as an entry in the directory.
-
-        @param name: C{str} of the namer to check.
-        @return: C{True} if the name exists, C{False} otherwise.
-        """
-        return opendirectory.checkUser(self.directory, name)
-
-    def directoryAttributes(self, name):
-        """
-        Return the attributes relevant to the directory entry.
-        
-        @param name: C{str} containing the name for the directory entry.
-        @return: C{dict} containing the attribute key/value map.
-        """
-        result = opendirectory.listUsersWithAttributes(self.directory, [name])
-        if result:
-            return result[name]
-        else:
-            return None
-
-    def getTitle(self):
-        return "User Principals"
-
-class DirectoryGroupPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
-    """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
-    """
-    def __init__(self, path, url):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  Provisioned child
-            resources will use a URL based on C{url} as their primary URLs.
-        @param directory: the reference to the directory to use
-        """
-        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
-        self.index = GroupIndex(self)
-        self.type = DirectoryTypePrincipalProvisioningResource.typeGroup
-
-    def listNames(self):
-        """
-        List all the names currently in the directory.
-
-        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
-        """
-
-        # Lookup all users
-        return [i[0] for i in opendirectory.listGroups(self.directory)]
- 
-    def listIndexAttributes(self):
-        """
-        List all the names currently in the directory with specific attributes needed for indexing.
-
-        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
-            The C{tuple} elements are: uid, guid, last-modified.
-        """
-        return opendirectory.listGroups(self.directory)
-
-    def listCommonAttributes(self, names):
-        """
-        List specified names currently in the directory returning useful attributes.
-
-        @param names: C{list} of record entries to list attributes for.
-        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
-            or C{None} on failure.
-        """
-        return opendirectory.listGroupsWithAttributes(self.directory, names)
-
-    def validName(self, name):
-        """
-        Verify that the supplied name exists as an entry in the directory.
-
-        @param name: C{str} of the namer to check
-        @return: C{True} if the name exists, C{False} otherwise
-        """
-        return opendirectory.checkGroup(self.directory, name)
-
-    def directoryAttributes(self, name):
-        """
-        Return the attributes relevant to the directory entry.
-        
-        @param name: C{str} containing the name for the directory entry.
-        @return: C{dict} containing the attribute key/value map.
-        """
-        result = opendirectory.listGroupsWithAttributes(self.directory, [name])
-        if result:
-            return result[name]
-        else:
-            return None
-
-    def getTitle(self):
-        return "Group Principals"
-
-class DirectoryResourcePrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
-    """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
-    """
-    def __init__(self, path, url):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  Provisioned child
-            resources will use a URL based on C{url} as their primary URLs.
-        @param directory: the reference to the directory to use
-        """
-        DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
-        self.index = ResourceIndex(self)
-        self.type = DirectoryTypePrincipalProvisioningResource.typeResource
-
-    def listNames(self):
-        """
-        List all the names currently in the directory.
-
-        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
-        """
-
-        # Lookup all users
-        return [i[0] for i in opendirectory.listResources(self.directory)]
- 
-    def listIndexAttributes(self):
-        """
-        List all the names currently in the directory with specific attributes needed for indexing.
-
-        @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
-            The C{tuple} elements are: uid, guid, last-modified.
-        """
-        return opendirectory.listResources(self.directory)
-
-    def listCommonAttributes(self, names):
-        """
-        List specified names currently in the directory returning useful attributes.
-
-        @param names: C{list} of record entries to list attributes for.
-        @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
-            or C{None} on failure.
-        """
-        return opendirectory.listResourcesWithAttributes(self.directory, names)
-
-    def validName(self, name):
-        """
-        Verify that the supplied name exists as an entry in the directory.
-
-        @param name: C{str} of the namer to check
-        @return: C{True} if the name exists, C{False} otherwise
-        """
-        return opendirectory.checkResource(self.directory, name)
-
-    def directoryAttributes(self, name):
-        """
-        Return the attributes relevant to the directory entry.
-        
-        @param name: C{str} containing the name for the directory entry.
-        @return: C{dict} containing the attribute key/value map.
-        """
-        result = opendirectory.listResourcesWithAttributes(self.directory, [name])
-        if result:
-            return result[name]
-        else:
-            return None
-
-    def getTitle(self):
-        return "Resource Principals"
-
-class DirectoryPrincipalProvisioningResource (DAVFile):
-    """
-    L{DAVFile} resource which provisions calendar principal resources as needed.
-    """
-    def __init__(self, path, url, params={}):
-        """
-        @param path: the path to the file which will back the resource.
-        @param url: the primary URL for the resource.  Provisioned child
-            resources will use a URL based on C{url} as their primary URLs.
-        """
-        super(DirectoryPrincipalProvisioningResource, self).__init__(path)
-
-        assert self.exists(), "%s should exist" % (self,)
-        assert self.isCollection(), "%s should be a collection" % (self,)
-
-        # Extract parameters
-        if (params.has_key("DirectoryNode")):
-            self.directory = opendirectory.odInit(params["DirectoryNode"])
-            if self.directory is None:
-                raise ValueError("Failed to open Open Directory Node: %s" % (params["DirectoryNode"],))
-        else:
-            raise ValueError("DirectoryPrincipalProvisioningResource must be configured with an Open Directory Node")
-
-        # Create children
-        for name, clazz in (
-            ("users" , DirectoryUserPrincipalProvisioningResource),
-            ("groups" , DirectoryGroupPrincipalProvisioningResource),
-            ("resources" , DirectoryResourcePrincipalProvisioningResource),
-        ):
-            child_fp = self.fp.child(name)
-            if not child_fp.exists(): child_fp.makedirs()
-            principalCollection = clazz(child_fp.path, joinURL(url, name) + "/")
-            principalCollection.setup(self.directory)
-            self.putChild(name, principalCollection)
-
-    def isCollection(self):
-        """
-        See L{IDAVResource.isCollection}.
-        """
-        return True
-
-    def initialize(self, homeuri, home):
-        """
-        May be called during repository account initialization.
-        This implementation does nothing.
-        
-        @param homeuri: C{str} uri of the calendar home root.
-        @param home: L{DAVFile} of the calendar home root.
-        """
-        for name in ("users", "groups", "resources",):
-            self.getChild(name).initialize(joinURL(homeuri, name), home.getChild(name))
-    
-    def createSimilarFile(self, path):
-        return DAVFile(path)
-
-    def render(self, request):
-        return StatusResponse(
-            responsecode.OK,
-            "This collection contains principal resources",
-            title=self.displayName()
-        )

Modified: CalendarServer/trunk/twistedcaldav/repository.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/repository.py	2006-11-03 01:40:15 UTC (rev 357)
+++ CalendarServer/trunk/twistedcaldav/repository.py	2006-11-03 01:40:54 UTC (rev 358)
@@ -42,10 +42,11 @@
 from twisted.web2.server import Site
 
 from twistedcaldav import caldavxml, customxml
+from twistedcaldav import authkerb
 from twistedcaldav.logging import RotatingFileAccessLoggingObserver
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.static import CalDAVFile, CalendarHomeFile, CalendarPrincipalFile
-from twistedcaldav import authkerb, directory
+from twistedcaldav.directory.cred import DirectoryCredentialsChecker
 
 import os
 
@@ -191,7 +192,7 @@
         portal.registerChecker(auth.TwistedPropertyChecker())
         print "Using property-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_DIRECTORY:
-        portal.registerChecker(directory.DirectoryCredentialsChecker())
+        portal.registerChecker(DirectoryCredentialsChecker())
         print "Using directory-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_KERBEROS:
         if authenticator.type == "basic":

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061102/0fd56568/attachment.html


More information about the calendarserver-changes mailing list