[CalendarServer-changes] [637] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Dec 1 11:18:20 PST 2006


Revision: 637
          http://trac.macosforge.org/projects/calendarserver/changeset/637
Author:   wsanchez at apple.com
Date:     2006-12-01 11:18:19 -0800 (Fri, 01 Dec 2006)

Log Message:
-----------
Merge branches/users/wsanchez/provisioning-2.

Modified Paths:
--------------
    CalendarServer/trunk/bin/caldavd
    CalendarServer/trunk/conf/caldavd.plist
    CalendarServer/trunk/conf/repository.dtd
    CalendarServer/trunk/conf/repository.xml
    CalendarServer/trunk/doc/Repository/Directory Schema.graffle
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/trunk/run
    CalendarServer/trunk/support/patchmaker.py
    CalendarServer/trunk/support/submit
    CalendarServer/trunk/test
    CalendarServer/trunk/twistedcaldav/__init__.py
    CalendarServer/trunk/twistedcaldav/directory/__init__.py
    CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/directory.py
    CalendarServer/trunk/twistedcaldav/directory/idirectory.py
    CalendarServer/trunk/twistedcaldav/dropbox.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
    CalendarServer/trunk/twistedcaldav/repository.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py

Added Paths:
-----------
    CalendarServer/trunk/conf/accounts.dtd
    CalendarServer/trunk/conf/accounts.xml
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch
    CalendarServer/trunk/support/CalendarServer.tmproj
    CalendarServer/trunk/support/CalendarServer.xcodeproj/
    CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
    CalendarServer/trunk/twistedcaldav/directory/apache.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/sqldb.py
    CalendarServer/trunk/twistedcaldav/directory/test/
    CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
    CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
    CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
    CalendarServer/trunk/twistedcaldav/directory/test/basic
    CalendarServer/trunk/twistedcaldav/directory/test/digest
    CalendarServer/trunk/twistedcaldav/directory/test/groups
    CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/trunk/twistedcaldav/directory/test/util.py
    CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
    CalendarServer/trunk/twistedcaldav/sql.py

Removed Paths:
-------------
    CalendarServer/trunk/conf/caldavd-dev.plist
    CalendarServer/trunk/conf/repository-proxy.xml
    CalendarServer/trunk/conf/repository-static.xml
    CalendarServer/trunk/support/CalDAV.xcodeproj/
    CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
    CalendarServer/trunk/twistedcaldav/directory/cred.py
    CalendarServer/trunk/twistedcaldav/directory/resource.py
    CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
    CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
    CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
    CalendarServer/trunk/twistedcaldav/directory/test/basic
    CalendarServer/trunk/twistedcaldav/directory/test/digest
    CalendarServer/trunk/twistedcaldav/directory/test/groups
    CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/trunk/twistedcaldav/directory/test/util.py

Property Changed:
----------------
    CalendarServer/trunk/bin/
    CalendarServer/trunk/conf/


Property changes on: CalendarServer/trunk/bin
___________________________________________________________________
Name: svn:ignore
   + server.log.*


Modified: CalendarServer/trunk/bin/caldavd
===================================================================
--- CalendarServer/trunk/bin/caldavd	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/bin/caldavd	2006-12-01 19:18:19 UTC (rev 637)
@@ -65,11 +65,16 @@
         self.certfile = "/etc/certificates/Default.crt"
         self.manhole = 0
 
+        self.directoryservice = {
+            "type": "OpenDirectoryService",
+            "params": { "node": "/Search" },
+        }
+
         self.dropbox = True
         self.dropboxName = "dropbox"
         self.dropboxACLs = True
         self.notifications = False
-        self.notifcationName = "notifications"
+        self.notificationName = "notifications"
 
         self.serverlogfile = "/var/log/caldavd/server.log"
         self.errorlogfile = "/var/log/caldavd/error.log"
@@ -102,11 +107,13 @@
         print "Only Use SSL:                     %s" % (self.onlyssl,)
         print "SSL Private Key File:             %s" % (self.keyfile,)
         print "SSL Certificate File:             %s" % (self.certfile,)
+        print "Directory Service:                %s" % (self.directoryservice["type"],)
+        print "Directory Service Parameters:     %r" % (self.directoryservice["params"],)
         print "Drop Box Enabled:                 %s" % (self.dropbox,)
         print "Drop Box Name:                    %s" % (self.dropboxName,)
         print "Drop Box ACLs are Inherited       %s" % (self.dropboxACLs,)
         print "Notifications Enabled:            %s" % (self.notifications,)
-        print "Notification Collection Name:     %s" % (self.notifcationName,)
+        print "Notification Collection Name:     %s" % (self.notificationName,)
         print "Server Log File:                  %s" % (self.serverlogfile,)
         print "Error Log File:                   %s" % (self.errorlogfile,)
         print "PID File:                         %s" % (self.pidfile,)
@@ -270,7 +277,7 @@
         self.action = args[0]
     
     def parsePlist(self):
-    	print "Reading configuration file %s." % (self.plistfile,)
+        print "Reading configuration file %s." % (self.plistfile,)
 
         root = readPlist(self.plistfile)
         
@@ -286,11 +293,12 @@
                    "SSLPrivateKey":              "keyfile",
                    "SSLCertificate":             "certfile",
                    "ManholePort":                "manhole",
+                   "DirectoryService":           "directoryservice",
                    "DropBoxEnabled":             "dropbox",
                    "DropBoxName":                "dropboxName",
                    "DropBoxInheritedACLs":       "dropboxACLs",
                    "NotificationsEnabled":       "notifications",
-                   "NotificationCollectionName": "notifcationName",
+                   "NotificationCollectionName": "notificationName",
                    "ServerLogFile":              "serverlogfile",
                    "ErrorLogFile":               "errorlogfile",
                    "PIDFile":                    "pidfile",
@@ -354,73 +362,54 @@
     
     def generateTAC(self):
         return """
-docroot         = "%(docroot)s"
-repo            = "%(repo)s"
-doacct          =  %(doacct)s
-doacl           =  %(doacl)s
-dossl           =  %(dossl)s
-keyfile         = "%(keyfile)s"
-certfile        = "%(certfile)s"
-onlyssl         =  %(onlyssl)s
-port            =  %(port)d
-sslport         =  %(sslport)d
-maxsize         =  %(maxsize)d
-quota           =  %(quota)d
-serverlogfile   = "%(serverlogfile)s"
-dropbox         = "%(dropbox)s"
-dropboxName     = "%(dropboxName)s"
-dropboxACLs     = "%(dropboxACLs)s"
-notifications   = "%(notifications)s"
-notifcationName = "%(notifcationName)s"
-manhole         =  %(manhole)d
-
 from twistedcaldav.repository import startServer
 
-application, site = startServer(docroot,
-                                repo,
-                                doacct,
-                                doacl,
-                                dossl,
-                                keyfile,
-                                certfile,
-                                onlyssl,
-                                port,
-                                sslport,
-                                maxsize,
-                                quota,
-                                serverlogfile,
-                                dropbox,
-                                dropboxName,
-                                dropboxACLs,
-                                notifications,
-                                notifcationName,
-                                manhole)
-
+application, site = startServer(
+    %(docroot)r,
+    %(repo)r,
+    %(doacct)s,
+    %(doacl)s,
+    %(dossl)s,
+    %(keyfile)r,
+    %(certfile)r,
+    %(onlyssl)s,
+    %(port)d,
+    %(sslport)d,
+    %(maxsize)d,
+    %(quota)d,
+    %(serverlogfile)r,
+    %(directoryservice)r,
+    %(dropbox)r,
+    %(dropboxName)r,
+    %(dropboxACLs)r,
+    %(notifications)r,
+    %(notificationName)r,
+    %(manhole)d,
+)
 """ % {
-    "docroot":         self.docroot,
-    "repo":            self.repo,
-    "doacct":          self.doacct,
-    "doacl":           self.doacl,
-    "dossl":           self.dossl,
-    "keyfile":         self.keyfile,
-    "certfile":        self.certfile,
-    "onlyssl":         self.onlyssl,
-    "port":            self.port,
-    "sslport":         self.sslport,
-    "maxsize":         self.maxsize,
-    "quota":           self.quota,
-    "serverlogfile":   self.serverlogfile,
-    "dropbox":         self.dropbox,
-    "dropboxName":     self.dropboxName,
-    "dropboxACLs":     self.dropboxACLs,
-    "notifications":   self.notifications,
-    "notifcationName": self.notifcationName,
-    "manhole":         self.manhole,
-    
+    "docroot":          self.docroot,
+    "repo":             self.repo,
+    "doacct":           self.doacct,
+    "doacl":            self.doacl,
+    "dossl":            self.dossl,
+    "keyfile":          self.keyfile,
+    "certfile":         self.certfile,
+    "onlyssl":          self.onlyssl,
+    "port":             self.port,
+    "sslport":          self.sslport,
+    "maxsize":          self.maxsize,
+    "quota":            self.quota,
+    "serverlogfile":    self.serverlogfile,
+    "directoryservice": self.directoryservice,
+    "dropbox":          self.dropbox,
+    "dropboxName":      self.dropboxName,
+    "dropboxACLs":      self.dropboxACLs,
+    "notifications":    self.notifications,
+    "notificationName": self.notificationName,
+    "manhole":          self.manhole,
 }
 
 if __name__ == "__main__":
-
     try:
         caldavd().run()
     except Exception, e:


Property changes on: CalendarServer/trunk/conf
___________________________________________________________________
Name: svn:ignore
   - repository-dev.xml

   + caldavd-dev.plist


Copied: CalendarServer/trunk/conf/accounts.dtd (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/accounts.dtd)
===================================================================
--- CalendarServer/trunk/conf/accounts.dtd	                        (rev 0)
+++ CalendarServer/trunk/conf/accounts.dtd	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,40 @@
+<!--
+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
+ -->
+
+<!ELEMENT accounts (user*, group*, resource*) >
+  <!ATTLIST accounts realm CDATA "">
+
+  <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+    <!ATTLIST user repeat CDATA "1">
+
+  <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
+    <!ATTLIST group repeat CDATA "1">
+
+  <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+    <!ATTLIST resource repeat CDATA "1">
+
+    <!ELEMENT uid         (#PCDATA)>
+    <!ELEMENT pswd        (#PCDATA)>
+    <!ELEMENT name        (#PCDATA)>
+    <!ELEMENT cuaddr      (#PCDATA)>
+    <!ELEMENT calendar    (#PCDATA)>
+    <!ELEMENT quota       (#PCDATA)>
+    <!ELEMENT autorespond EMPTY>
+    <!ELEMENT canproxy    EMPTY>
+    <!ELEMENT members     (uid*)>
+    
\ No newline at end of file

Copied: CalendarServer/trunk/conf/accounts.xml (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/accounts.xml)
===================================================================
--- CalendarServer/trunk/conf/accounts.xml	                        (rev 0)
+++ CalendarServer/trunk/conf/accounts.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <user>
+    <uid>admin</uid>
+    <pswd>admin</pswd>
+    <name>Super User</name>
+  </user>
+  <user>
+    <uid>proxy</uid>
+    <pswd>proxy</pswd>
+    <name>User who can authorize as someone else</name>
+    <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
+  </user>
+  <user repeat="99">
+    <uid>user%02d</uid>
+    <pswd>user%02d</pswd>
+    <name>User %02d</name>
+    <cuaddr>mailto:user%02d at example.com</cuaddr>
+  </user>
+  <user repeat="10">
+    <uid>public%02d</uid>
+    <pswd>public%02d</pswd>
+    <name>Public %02d</name>
+    <cuaddr>mailto:public%02d at example.com</cuaddr>
+  </user>
+  <resource repeat="10">
+    <uid>resource%02d</uid>
+    <pswd>resource%02d</pswd>
+    <name>Room %02d</name>
+  </resource>
+</accounts>

Deleted: CalendarServer/trunk/conf/caldavd-dev.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-dev.plist	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/caldavd-dev.plist	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-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.
- -->
-
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-
-  <key>Verbose</key>
-  <false/>
-
-  <key>RunStandalone</key>
-  <true/>
-
-  <key>DocumentRoot</key>
-  <string>twistedcaldav/test/data/</string>
-
-  <key>Port</key>
-  <integer>8008</integer>
-
-  <key>SSLEnable</key>
-  <true/>
-
-  <key>SSLOnly</key>
-  <false/>
-
-  <key>SSLPort</key>
-  <integer>8443</integer>
-
-  <key>SSLPrivateKey</key>
-  <string>conf/server.pem</string>
-
-  <key>SSLCertificate</key>
-  <string>conf/server.pem</string>
-
-  <key>ServerLogFile</key>
-  <string>server.log</string>
-
-  <key>ErrorLogFile</key>
-  <string>error.log</string>
-
-  <key>PIDFile</key>
-  <string>caldavd.pid</string>
-
-  <key>Repository</key>
-  <string>conf/repository-dev.xml</string>
-
-  <key>CreateAccounts</key>
-  <true/>
-
-  <key>ResetAccountACLs</key>
-  <true/>
-
-  <key>DropBoxEnabled</key>
-  <true/>
-
-  <key>DropBoxName</key>
-  <string>dropbox</string>
-
-  <key>DropBoxInheritedACLs</key>
-  <true/>
-
-  <key>NotificationsEnabled</key>
-  <true/>
-
-  <key>NotificationCollectionName</key>
-  <string>notifications</string>
-
-  <key>twistdLocation</key>
-  <string>../Twisted/bin/twistd</string>
-
-  <key>MaximumAttachmentSizeBytes</key>
-  <integer>1048576</integer><!-- 1Mb -->
-
-  <key>UserQuotaBytes</key>
-  <integer>104857600</integer><!-- 100Mb -->
-
-</dict>
-</plist>

Copied: CalendarServer/trunk/conf/caldavd-test.plist (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/caldavd-test.plist)
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	                        (rev 0)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+
+  <key>Verbose</key>
+  <false/>
+
+  <key>RunStandalone</key>
+  <true/>
+
+  <key>DocumentRoot</key>
+  <string>twistedcaldav/test/data/</string>
+
+  <key>Port</key>
+  <integer>8008</integer>
+
+  <key>SSLPort</key>
+  <integer>8443</integer>
+
+  <key>SSLEnable</key>
+  <true/>
+
+  <key>SSLOnly</key>
+  <false/>
+
+  <key>SSLPrivateKey</key>
+  <string>conf/server.pem</string>
+
+  <key>SSLCertificate</key>
+  <string>conf/server.pem</string>
+
+  <key>ServerLogFile</key>
+  <string>server.log</string>
+
+  <key>ErrorLogFile</key>
+  <string>error.log</string>
+
+  <key>PIDFile</key>
+  <string>caldavd.pid</string>
+
+  <key>CreateAccounts</key>
+  <true/>
+
+  <key>ResetAccountACLs</key>
+  <true/>
+
+  <!--  XML File Directory Service -->
+  <key>DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>xmlFile</key>
+	  <string>conf/accounts.xml</string>
+	</dict>
+  </dict>
+  
+  <!--  SQL Directory Service -->
+  <!--
+  <key>twistedcaldav.directory.sqldb.DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>SQLDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>dbParentPath</key>
+	  <string>twistedcaldav/test/data/</string>
+	  <key>xmlFile</key>
+	  <string>conf/accounts.xml</string>
+	</dict>
+  </dict>
+  -->
+  
+  <!--  Open Directory Service -->
+  <!--
+  <key>DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>node</key>
+	  <string>/Search</string>
+	</dict>
+  </dict>
+  -->
+  
+  <key>MaximumAttachmentSizeBytes</key>
+  <integer>1048576</integer><!-- 1Mb -->
+
+  <key>UserQuotaBytes</key>
+  <integer>104857600</integer><!-- 100Mb -->
+
+  <key>DropBoxEnabled</key>
+  <true/>
+
+  <key>DropBoxName</key>
+  <string>dropbox</string>
+
+  <key>DropBoxInheritedACLs</key>
+  <true/>
+
+  <key>NotificationsEnabled</key>
+  <true/>
+
+  <key>NotificationCollectionName</key>
+  <string>notifications</string>
+
+  <key>Repository</key>
+  <string>conf/repository.xml</string>
+
+  <key>twistdLocation</key>
+  <string>../Twisted/bin/twistd</string>
+
+</dict>
+</plist>

Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/caldavd.plist	2006-12-01 19:18:19 UTC (rev 637)
@@ -32,15 +32,15 @@
   <key>Port</key>
   <integer>8008</integer>
 
+  <key>SSLPort</key>
+  <integer>8443</integer>
+
   <key>SSLEnable</key>
   <true/>
 
   <key>SSLOnly</key>
   <false/>
 
-  <key>SSLPort</key>
-  <integer>8443</integer>
-
   <key>SSLPrivateKey</key>
   <string>/etc/certificates/Default.key</string>
 
@@ -56,15 +56,63 @@
   <key>PIDFile</key>
   <string>/var/log/caldavd/caldavd.pid</string>
 
-  <key>Repository</key>
-  <string>/etc/caldavd/repository.xml</string>
-
   <key>CreateAccounts</key>
   <true/>
 
   <key>ResetAccountACLs</key>
   <true/>
 
+  <!--  XML File Directory Service -->
+  <!--
+  <key>DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>xmlFile</key>
+	  <string>/etc/caldavd/accounts.xml</string>
+	</dict>
+  </dict>
+  -->
+  
+  <!--  SQL Directory Service -->
+  <!--
+  <key>twistedcaldav.directory.sqldb.DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>SQLDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>dbParentPath</key>
+	  <string>/Library/CalendarServer/Documents</string>
+	  <key>xmlFile</key>
+	  <string>/etc/caldavd/accounts.xml</string>
+	</dict>
+  </dict>
+  -->
+  
+  <!--  Open Directory Service -->
+  <key>DirectoryService</key>
+  <dict>
+    <key>type</key>
+    <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+  
+    <key>params</key>
+	<dict>
+	  <key>node</key>
+	  <string>/Search</string>
+	</dict>
+  </dict>
+  
+  <key>MaximumAttachmentSizeBytes</key>
+  <integer>1048576</integer><!-- 1Mb -->
+
+  <key>UserQuotaBytes</key>
+  <integer>104857600</integer><!-- 100Mb -->
+
   <key>DropBoxEnabled</key>
   <true/>
 
@@ -80,14 +128,11 @@
   <key>NotificationCollectionName</key>
   <string>notifications</string>
 
+  <key>Repository</key>
+  <string>/etc/caldavd/repository.xml</string>
+
   <key>twistdLocation</key>
   <string>/usr/share/caldavd/bin/twistd</string>
 
-  <key>MaximumAttachmentSizeBytes</key>
-  <integer>1048576</integer><!-- 1Mb -->
-
-  <key>UserQuotaBytes</key>
-  <integer>104857600</integer><!-- 100Mb -->
-
 </dict>
 </plist>

Deleted: CalendarServer/trunk/conf/repository-proxy.xml
===================================================================
--- CalendarServer/trunk/conf/repository-proxy.xml	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository-proxy.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,217 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-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.
- -->
-
-<!DOCTYPE repository SYSTEM "repository.dtd">
-
-<repository>
-
-  <docroot auto-principal-collection-set="yes">
-    <collection>
-      <pytype>twisted.web2.dav.static.DAVFile</pytype>
-      <properties>
-        <acl>
-          <ace>
-            <principal><authenticated/></principal>
-            <grant><privilege><read/></privilege></grant>
-            <protected/>
-          </ace>
-          <ace>
-            <principal><href>/principals/users/admin</href></principal>
-            <grant><privilege><all/></privilege></grant>
-            <protected/>
-            <inheritable/>
-          </ace>
-        </acl>
-      </properties>
-      <members>
-        <!--
-          We must define the calendar home location before the
-          principals as auto-provisioning of accounts occurs when the
-          principal collections are created and we need to have the
-          calendar home path setup by then.
-         -->
-        <collection name="calendars" tag="calendars">
-          <pytype>twistedcaldav.static.CalDAVFile</pytype>
-          <properties>
-            <acl>
-              <ace>
-                <principal><authenticated/></principal>
-                <grant><privilege><read/></privilege></grant>
-                <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="groups">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="resources">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="public">
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><unauthenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-          </members>
-        </collection>
-        <collection name="principals" initialize="yes">
-          <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
-          <params>
-            <param>
-              <key>DirectoryNode</key>
-              <value>/Search</value>
-            </param>
-          </params>
-          <properties>
-            <acl>
-              <ace>
-                <principal><authenticated/></principal>
-                <grant><privilege><read/></privilege></grant>
-                <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users" tag="principals">
-              <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="groups" tag="principals">
-              <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="resources" tag="principals">
-              <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="localusers" tag="principals" account="yes">
-              <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
-                <properties>
-                  <acl>
-                    <ace>
-                      <principal><href>/principals/users/admin</href></principal>
-                      <grant><privilege><read/></privilege></grant>
-                      <protected/>
-                    </ace>
-                  </acl>
-                </properties>
-              <members/>
-            </collection>
-          </members>
-        </collection>
-      </members>
-    </collection>
-  </docroot>
-
-  <authentication>
-    <basic enable="yes" onlyssl="yes" credentials="directory">
-      <realm></realm>
-    </basic>
-    <digest enable="no" onlyssl="no" credentials="property">
-      <realm></realm>
-    </digest>
-    <kerberos enable="no" onlyssl="no">
-      <service></service>
-    </kerberos>
-  </authentication>
-
-<accounts>
-  <user>
-    <uid>proxy</uid>
-    <pswd>proxy</pswd>
-    <name>User who can authorize as someone else</name>
-    <canproxy/>
-  </user>
-</accounts>
-
-</repository>

Deleted: CalendarServer/trunk/conf/repository-static.xml
===================================================================
--- CalendarServer/trunk/conf/repository-static.xml	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository-static.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,180 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-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.
- -->
-
-<!DOCTYPE repository SYSTEM "repository.dtd">
-
-<repository>
-
-  <docroot>
-    <collection>
-      <pytype>twisted.web2.dav.static.DAVFile</pytype>
-      <properties>
-        <acl>
-          <ace>
-            <principal><authenticated/></principal>
-            <grant><privilege><read/></privilege></grant>
-            <protected/>
-          </ace>
-          <ace>
-            <principal><href>/principals/users/admin</href></principal>
-            <grant><privilege><all/></privilege></grant>
-            <protected/>
-            <inheritable/>
-          </ace>
-        </acl>
-      </properties>
-      <members>
-        <collection name="principals">
-          <pytype>twistedcaldav.static.CalDAVFile</pytype>
-          <properties>
-            <acl>
-              <ace>
-                <principal><authenticated/></principal>
-                <grant><privilege><read/></privilege></grant>
-                <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users" tag="principals" account="yes">
-              <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-          </members>
-        </collection>
-        <collection name="calendars">
-          <pytype>twistedcaldav.static.CalDAVFile</pytype>
-          <properties>
-            <acl>
-              <ace>
-              <principal><authenticated/></principal>
-              <grant><privilege><read/></privilege></grant>
-              <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users" tag="calendars">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="shared">
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><unauthenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-          </members>
-        </collection>
-      </members>
-    </collection>
-  </docroot>
-
-  <authentication>
-  	<basic enable="yes" onlyssl="yes" credentials="property">
-  	  <realm></realm>
-  	</basic>
-  	<digest enable="no" onlyssl="no" credentials="property">
-  	  <realm></realm>
-  	</digest>
-  	<kerberos enable="no" onlyssl="no">
-  	  <service></service>
-  	</kerberos>
-  </authentication>
-
-  <accounts>
-    <user>
-      <uid>admin</uid>
-      <pswd>admin</pswd>
-      <name>Super User</name>
-    </user>
-    <user>
-      <uid>proxy</uid>
-      <pswd>proxy</pswd>
-      <name>User who can authorize as someone else</name>
-      <canproxy/>
-    </user>
-    <user repeat='99'>
-      <uid>user%02d</uid>
-      <pswd>user%02d</pswd>
-      <name>User %02d</name>
-      <calendar>calendar</calendar>
-    </user>
-    <user repeat='10'>
-      <uid>public%02d</uid>
-      <pswd/>
-      <name>Public %02d</name>
-      <calendar>calendar</calendar>
-      <acl>
-        <ace>
-          <principal><all/></principal>
-          <grant><privilege><all/></privilege></grant>
-          <protected/>
-          <inheritable/>
-        </ace>
-      </acl>
-    </user>
-    <user repeat='10'>
-      <uid>resource%02d</uid>
-      <pswd/>
-      <name>Room %02d</name>
-      <calendar>calendar</calendar>
-      <acl>
-        <ace>
-          <principal><all/></principal>
-          <grant><privilege><all/></privilege></grant>
-          <protected/>
-          <inheritable/>
-        </ace>
-      </acl>
-      <autorespond/>
-    </user>
-  </accounts>
-
-</repository>

Modified: CalendarServer/trunk/conf/repository.dtd
===================================================================
--- CalendarServer/trunk/conf/repository.dtd	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository.dtd	2006-12-01 19:18:19 UTC (rev 637)
@@ -21,7 +21,7 @@
   <!ELEMENT docroot (collection) >
 	<!ATTLIST docroot auto-principal-collection-set (yes|no) "yes">
 
-    <!ELEMENT collection (pytype?, params?, properties, members)>
+    <!ELEMENT collection (pytype, params?, properties, members)>
       <!ATTLIST collection name CDATA ""
                              tag (none|principals|calendars) "none"
                              account (yes|no) "no"
@@ -54,7 +54,8 @@
   	<!ELEMENT basic (realm, service?)>
 	  <!ATTLIST basic enable  (yes|no) "yes"
 	                  onlyssl (yes|no) "yes"
-	                  credentials (property|directory|kerberos) "property">
+	                  credentials (property|directory|kerberos) "property"
+	                  node CDATA "">
   	<!ELEMENT digest (realm)>
 	  <!ATTLIST digest enable  (yes|no) "no"
 	                   onlyssl (yes|no) "no"

Modified: CalendarServer/trunk/conf/repository.xml
===================================================================
--- CalendarServer/trunk/conf/repository.xml	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -26,12 +26,11 @@
       <properties>
         <acl>
           <ace>
-            <principal><authenticated/></principal>
+            <principal><all/></principal>
             <grant><privilege><read/></privilege></grant>
-            <protected/>
           </ace>
           <ace>
-            <principal><href>/principals/users/admin</href></principal>
+            <principal><href>/principals/user/admin</href></principal>
             <grant><privilege><all/></privilege></grant>
             <protected/>
             <inheritable/>
@@ -39,157 +38,30 @@
         </acl>
       </properties>
       <members>
-        <!--
-          We must define the calendar home location before the
-          principals as auto-provisioning of accounts occurs when the
-          principal collections are created and we need to have the
-          calendar home path setup by then.
-         -->
-        <collection name="calendars" tag="calendars">
-          <pytype>twistedcaldav.static.CalDAVFile</pytype>
-          <properties>
-            <acl>
-              <ace>
-                <principal><authenticated/></principal>
-                <grant><privilege><read/></privilege></grant>
-                <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="groups">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="resources">
-              <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="public">
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><unauthenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                    <inheritable/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-          </members>
+        <collection name="principals">
+          <pytype>twistedcaldav.directory.principal.DirectoryPrincipalProvisioningResource</pytype>
+          <properties/>
+          <members/>
         </collection>
-        <collection name="principals" initialize="yes">
-          <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
-          <params>
-            <param>
-              <key>DirectoryNode</key>
-              <value>/Search</value>
-            </param>
-          </params>
-          <properties>
-            <acl>
-              <ace>
-                <principal><authenticated/></principal>
-                <grant><privilege><read/></privilege></grant>
-                <protected/>
-              </ace>
-            </acl>
-          </properties>
-          <members>
-            <collection name="users" tag="principals" account="yes">
-              <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="groups" tag="principals">
-              <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-            <collection name="resources" tag="principals">
-              <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
-              <properties>
-                <acl>
-                  <ace>
-                    <principal><authenticated/></principal>
-                    <grant><privilege><read/></privilege></grant>
-                    <protected/>
-                  </ace>
-                </acl>
-              </properties>
-              <members/>
-            </collection>
-          </members>
+        <collection name="calendars">
+          <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
+          <properties/>
+          <members/>
         </collection>
       </members>
     </collection>
   </docroot>
 
   <authentication>
-  	<basic enable="yes" onlyssl="yes" credentials="directory">
-  	  <realm></realm>
-  	</basic>
-  	<digest enable="no" onlyssl="no" credentials="property">
-  	  <realm></realm>
-  	</digest>
-  	<kerberos enable="no" onlyssl="no">
-  	  <service></service>
-  	</kerberos>
+    <basic enable="yes" onlyssl="yes" credentials="directory" node="/Search">
+      <realm></realm>
+    </basic>
+    <digest enable="no" onlyssl="no" credentials="property">
+      <realm></realm>
+    </digest>
+    <kerberos enable="no" onlyssl="no">
+      <service></service>
+    </kerberos>
   </authentication>
 
   <accounts/>

Modified: CalendarServer/trunk/doc/Repository/Directory Schema.graffle
===================================================================
--- CalendarServer/trunk/doc/Repository/Directory Schema.graffle	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/doc/Repository/Directory Schema.graffle	2006-12-01 19:18:19 UTC (rev 637)
@@ -35,8 +35,240 @@
 			<key>Head</key>
 			<dict>
 				<key>ID</key>
+				<integer>92</integer>
+			</dict>
+			<key>ID</key>
+			<integer>93</integer>
+			<key>Points</key>
+			<array>
+				<string>{334.488, 329.167}</string>
+				<string>{386.334, 421.667}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>LineType</key>
+					<integer>1</integer>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
 				<integer>53</integer>
 				<key>Info</key>
+				<integer>5</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{386.334, 413.167}, {22, 17}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>Flow</key>
+			<string>Resize</string>
+			<key>ID</key>
+			<integer>92</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{-0.5, 0}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\b\fs28 \cf0 ...}</string>
+			</dict>
+			<key>Wrap</key>
+			<string>NO</string>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>33</integer>
+			</dict>
+			<key>ID</key>
+			<integer>90</integer>
+			<key>Points</key>
+			<array>
+				<string>{334.488, 329.167}</string>
+				<string>{385.512, 351.811}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>LineType</key>
+					<integer>1</integer>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>53</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>TableGroup</string>
+			<key>Graphics</key>
+			<array>
+				<dict>
+					<key>Bounds</key>
+					<string>{{385.512, 329.512}, {90, 14}}</string>
+					<key>Class</key>
+					<string>ShapedGraphic</string>
+					<key>FitText</key>
+					<string>Vertical</string>
+					<key>Flow</key>
+					<string>Resize</string>
+					<key>ID</key>
+					<integer>34</integer>
+					<key>Shape</key>
+					<string>Rectangle</string>
+					<key>Style</key>
+					<dict>
+						<key>fill</key>
+						<dict>
+							<key>Color</key>
+							<dict>
+								<key>b</key>
+								<string>0.263696</string>
+								<key>g</key>
+								<string>0.941637</string>
+								<key>r</key>
+								<string>1</string>
+							</dict>
+							<key>GradientAngle</key>
+							<real>304</real>
+							<key>GradientCenter</key>
+							<string>{-0.294118, -0.264706}</string>
+						</dict>
+					</dict>
+					<key>Text</key>
+					<dict>
+						<key>Text</key>
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Machine}</string>
+					</dict>
+					<key>TextPlacement</key>
+					<integer>0</integer>
+				</dict>
+				<dict>
+					<key>Bounds</key>
+					<string>{{385.512, 343.512}, {90, 56}}</string>
+					<key>Class</key>
+					<string>ShapedGraphic</string>
+					<key>FitText</key>
+					<string>Vertical</string>
+					<key>Flow</key>
+					<string>Resize</string>
+					<key>ID</key>
+					<integer>35</integer>
+					<key>Shape</key>
+					<string>Rectangle</string>
+					<key>Style</key>
+					<dict>
+						<key>fill</key>
+						<dict>
+							<key>Color</key>
+							<dict>
+								<key>b</key>
+								<string>0.263696</string>
+								<key>g</key>
+								<string>0.941637</string>
+								<key>r</key>
+								<string>1</string>
+							</dict>
+							<key>GradientAngle</key>
+							<real>304</real>
+							<key>GradientCenter</key>
+							<string>{-0.294118, -0.264706}</string>
+						</dict>
+					</dict>
+					<key>Text</key>
+					<dict>
+						<key>Align</key>
+						<integer>0</integer>
+						<key>Text</key>
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
+
+\f0\fs24 \cf0 guid\
+name\
+ip address\
+...}</string>
+					</dict>
+					<key>TextPlacement</key>
+					<integer>0</integer>
+				</dict>
+			</array>
+			<key>GridH</key>
+			<array>
+				<integer>34</integer>
+				<integer>35</integer>
+				<array/>
+			</array>
+			<key>GroupConnect</key>
+			<string>YES</string>
+			<key>ID</key>
+			<integer>33</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{-0.5, -0.181438}</string>
+			</array>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>53</integer>
+				<key>Info</key>
 				<integer>1</integer>
 			</dict>
 			<key>ID</key>
@@ -44,7 +276,7 @@
 			<key>Points</key>
 			<array>
 				<string>{147.402, 361.309}</string>
-				<string>{192.756, 259.825}</string>
+				<string>{192.756, 257.948}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -77,7 +309,7 @@
 			<key>Points</key>
 			<array>
 				<string>{147.402, 259.008}</string>
-				<string>{192.756, 259.825}</string>
+				<string>{192.756, 257.948}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -137,8 +369,8 @@
 					<key>Text</key>
 					<dict>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
 
@@ -184,7 +416,7 @@
 						<key>Align</key>
 						<integer>0</integer>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -254,8 +486,8 @@
 					<key>Text</key>
 					<dict>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
 
@@ -301,7 +533,7 @@
 						<key>Align</key>
 						<integer>0</integer>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -344,8 +576,8 @@
 			<integer>80</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 300.336}</string>
-				<string>{385.512, 300.472}</string>
+				<string>{334.488, 315.833}</string>
+				<string>{385.512, 221.103}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -379,8 +611,8 @@
 			<integer>79</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 300.336}</string>
-				<string>{385.512, 328.819}</string>
+				<string>{334.488, 315.833}</string>
+				<string>{385.512, 249.449}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -414,8 +646,8 @@
 			<integer>78</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 300.336}</string>
-				<string>{385.512, 357.165}</string>
+				<string>{334.488, 315.833}</string>
+				<string>{385.512, 277.796}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -449,8 +681,8 @@
 			<integer>77</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 286.047}</string>
-				<string>{385.512, 266.457}</string>
+				<string>{334.488, 301.667}</string>
+				<string>{385.512, 175.748}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -484,8 +716,8 @@
 			<integer>76</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 300.336}</string>
-				<string>{385.512, 385.512}</string>
+				<string>{334.488, 315.833}</string>
+				<string>{385.512, 306.142}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -509,7 +741,7 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{385.512, 374.173}, {204.094, 22.6772}}</string>
+			<string>{{385.512, 294.803}, {204.094, 22.6772}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>ID</key>
@@ -538,7 +770,7 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -548,7 +780,7 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{385.512, 345.827}, {204.094, 22.6772}}</string>
+			<string>{{385.512, 266.457}, {204.094, 22.6772}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>ID</key>
@@ -577,7 +809,7 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -587,7 +819,7 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{385.512, 317.48}, {204.094, 22.6772}}</string>
+			<string>{{385.512, 238.11}, {204.094, 22.6772}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>ID</key>
@@ -616,7 +848,7 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -626,87 +858,10 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{516.307, 209.764}, {22, 17}}</string>
+			<string>{{385.512, 209.764}, {204.094, 22.6772}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
-			<key>FitText</key>
-			<string>YES</string>
-			<key>Flow</key>
-			<string>Resize</string>
 			<key>ID</key>
-			<integer>72</integer>
-			<key>Shape</key>
-			<string>Rectangle</string>
-			<key>Style</key>
-			<dict>
-				<key>fill</key>
-				<dict>
-					<key>Draws</key>
-					<string>NO</string>
-				</dict>
-				<key>shadow</key>
-				<dict>
-					<key>Draws</key>
-					<string>NO</string>
-				</dict>
-				<key>stroke</key>
-				<dict>
-					<key>Draws</key>
-					<string>NO</string>
-				</dict>
-			</dict>
-			<key>Text</key>
-			<dict>
-				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
-
-\f0\b\fs28 \cf0 ...}</string>
-			</dict>
-			<key>Wrap</key>
-			<string>NO</string>
-		</dict>
-		<dict>
-			<key>Class</key>
-			<string>LineGraphic</string>
-			<key>Head</key>
-			<dict>
-				<key>ID</key>
-				<integer>72</integer>
-			</dict>
-			<key>ID</key>
-			<integer>71</integer>
-			<key>Points</key>
-			<array>
-				<string>{476.22, 159.963}</string>
-				<string>{519.859, 209.764}</string>
-			</array>
-			<key>Style</key>
-			<dict>
-				<key>stroke</key>
-				<dict>
-					<key>HeadArrow</key>
-					<string>FilledArrow</string>
-					<key>LineType</key>
-					<integer>1</integer>
-					<key>TailArrow</key>
-					<string>0</string>
-				</dict>
-			</dict>
-			<key>Tail</key>
-			<dict>
-				<key>ID</key>
-				<integer>65</integer>
-			</dict>
-		</dict>
-		<dict>
-			<key>Bounds</key>
-			<string>{{385.512, 289.134}, {204.094, 22.6772}}</string>
-			<key>Class</key>
-			<string>ShapedGraphic</string>
-			<key>ID</key>
 			<integer>70</integer>
 			<key>Magnets</key>
 			<array>
@@ -732,7 +887,7 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -742,7 +897,7 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{385.512, 255.118}, {204.095, 22.6771}}</string>
+			<string>{{385.512, 164.41}, {204.095, 22.6771}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>ID</key>
@@ -771,7 +926,7 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -785,51 +940,14 @@
 			<key>Head</key>
 			<dict>
 				<key>ID</key>
-				<integer>33</integer>
-				<key>Info</key>
-				<integer>1</integer>
-			</dict>
-			<key>ID</key>
-			<integer>67</integer>
-			<key>Points</key>
-			<array>
-				<string>{476.22, 159.963}</string>
-				<string>{521.575, 159.922}</string>
-			</array>
-			<key>Style</key>
-			<dict>
-				<key>stroke</key>
-				<dict>
-					<key>HeadArrow</key>
-					<string>FilledArrow</string>
-					<key>LineType</key>
-					<integer>1</integer>
-					<key>TailArrow</key>
-					<string>0</string>
-				</dict>
-			</dict>
-			<key>Tail</key>
-			<dict>
-				<key>ID</key>
 				<integer>65</integer>
-				<key>Info</key>
-				<integer>1</integer>
 			</dict>
-		</dict>
-		<dict>
-			<key>Class</key>
-			<string>LineGraphic</string>
-			<key>Head</key>
-			<dict>
-				<key>ID</key>
-				<integer>65</integer>
-			</dict>
 			<key>ID</key>
 			<integer>66</integer>
 			<key>Points</key>
 			<array>
-				<string>{334.488, 273.11}</string>
-				<string>{385.512, 172.913}</string>
+				<string>{334.488, 287.11}</string>
+				<string>{385.512, 104.882}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -851,7 +969,7 @@
 		</dict>
 		<dict>
 			<key>Bounds</key>
-			<string>{{385.512, 119.055}, {90.7086, 107.716}}</string>
+			<string>{{385.512, 68.0315}, {181.417, 73.7008}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>ID</key>
@@ -883,138 +1001,21 @@
 				<key>Align</key>
 				<integer>0</integer>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
 
-\f0\fs24 \cf0 \{\
-  scheme = (\
-    (host, port),\
-    ...\
-  ),\
-  ...\
-\}}</string>
+\f0\fs24 \cf0 (\
+  caldav:http:hostname:8008,\
+  caldav:https:hostname:8443,\
+  \'c9\
+)}</string>
 			</dict>
 		</dict>
 		<dict>
-			<key>Class</key>
-			<string>TableGroup</string>
-			<key>Graphics</key>
-			<array>
-				<dict>
-					<key>Bounds</key>
-					<string>{{521.575, 137.756}, {90, 14}}</string>
-					<key>Class</key>
-					<string>ShapedGraphic</string>
-					<key>FitText</key>
-					<string>Vertical</string>
-					<key>Flow</key>
-					<string>Resize</string>
-					<key>ID</key>
-					<integer>34</integer>
-					<key>Shape</key>
-					<string>Rectangle</string>
-					<key>Style</key>
-					<dict>
-						<key>fill</key>
-						<dict>
-							<key>Color</key>
-							<dict>
-								<key>b</key>
-								<string>0.263696</string>
-								<key>g</key>
-								<string>0.941637</string>
-								<key>r</key>
-								<string>1</string>
-							</dict>
-							<key>GradientAngle</key>
-							<real>304</real>
-							<key>GradientCenter</key>
-							<string>{-0.294118, -0.264706}</string>
-						</dict>
-					</dict>
-					<key>Text</key>
-					<dict>
-						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
-
-\f0\b\fs24 \cf0 Machine}</string>
-					</dict>
-					<key>TextPlacement</key>
-					<integer>0</integer>
-				</dict>
-				<dict>
-					<key>Bounds</key>
-					<string>{{521.575, 151.756}, {90, 42}}</string>
-					<key>Class</key>
-					<string>ShapedGraphic</string>
-					<key>FitText</key>
-					<string>Vertical</string>
-					<key>Flow</key>
-					<string>Resize</string>
-					<key>ID</key>
-					<integer>35</integer>
-					<key>Shape</key>
-					<string>Rectangle</string>
-					<key>Style</key>
-					<dict>
-						<key>fill</key>
-						<dict>
-							<key>Color</key>
-							<dict>
-								<key>b</key>
-								<string>0.263696</string>
-								<key>g</key>
-								<string>0.941637</string>
-								<key>r</key>
-								<string>1</string>
-							</dict>
-							<key>GradientAngle</key>
-							<real>304</real>
-							<key>GradientCenter</key>
-							<string>{-0.294118, -0.264706}</string>
-						</dict>
-					</dict>
-					<key>Text</key>
-					<dict>
-						<key>Align</key>
-						<integer>0</integer>
-						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\fs24 \cf0 name\
-ip address\
-...}</string>
-					</dict>
-					<key>TextPlacement</key>
-					<integer>0</integer>
-				</dict>
-			</array>
-			<key>GridH</key>
-			<array>
-				<integer>34</integer>
-				<integer>35</integer>
-				<array/>
-			</array>
-			<key>GroupConnect</key>
-			<string>YES</string>
-			<key>ID</key>
-			<integer>33</integer>
-			<key>Magnets</key>
-			<array>
-				<string>{-0.5, -0.104178}</string>
-			</array>
-		</dict>
-		<dict>
 			<key>Bounds</key>
-			<string>{{193.157, 181.417}, {22, 17}}</string>
+			<string>{{183.333, 137.271}, {22, 17}}</string>
 			<key>Class</key>
 			<string>ShapedGraphic</string>
 			<key>FitText</key>
@@ -1046,8 +1047,8 @@
 			<key>Text</key>
 			<dict>
 				<key>Text</key>
-				<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+				<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
 
@@ -1069,7 +1070,7 @@
 			<key>Points</key>
 			<array>
 				<string>{147.402, 145.622}</string>
-				<string>{193.266, 181.417}</string>
+				<string>{183.333, 145.736}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -1102,7 +1103,7 @@
 			<key>Points</key>
 			<array>
 				<string>{147.402, 145.622}</string>
-				<string>{192.756, 259.825}</string>
+				<string>{192.756, 257.948}</string>
 			</array>
 			<key>Style</key>
 			<dict>
@@ -1164,8 +1165,8 @@
 					<key>Text</key>
 					<dict>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
 
@@ -1176,7 +1177,7 @@
 				</dict>
 				<dict>
 					<key>Bounds</key>
-					<string>{{192.756, 252.11}, {141.732, 56}}</string>
+					<string>{{192.756, 252.11}, {141.732, 84}}</string>
 					<key>Class</key>
 					<string>ShapedGraphic</string>
 					<key>FitText</key>
@@ -1211,15 +1212,17 @@
 						<key>Align</key>
 						<integer>0</integer>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
 
-\f0\fs24 \cf0 name\
-location\
+\f0\fs24 \cf0 guid\
+name\
+services\
 principal path template\
-user address template}</string>
+user address template\
+machines}</string>
 					</dict>
 					<key>TextPlacement</key>
 					<integer>0</integer>
@@ -1237,10 +1240,11 @@
 			<integer>53</integer>
 			<key>Magnets</key>
 			<array>
-				<string>{-0.5, -0.189787}</string>
-				<string>{0.5, -2.38419e-07}</string>
-				<string>{0.5, 0.388945}</string>
-				<string>{0.5, 0.184814}</string>
+				<string>{-0.5, -0.297571}</string>
+				<string>{0.5, 0}</string>
+				<string>{0.5, 0.293093}</string>
+				<string>{0.5, 0.148535}</string>
+				<string>{0.5, 0.429147}</string>
 			</array>
 		</dict>
 		<dict>
@@ -1283,8 +1287,8 @@
 					<key>Text</key>
 					<dict>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
 
@@ -1330,7 +1334,7 @@
 						<key>Align</key>
 						<integer>0</integer>
 						<key>Text</key>
-						<string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+						<string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
 {\fonttbl\f0\fswiss\fcharset77 Helvetica;}
 {\colortbl;\red255\green255\blue255;}
 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -1370,8 +1374,6 @@
 		<integer>10</integer>
 		<key>ShowsGrid</key>
 		<string>YES</string>
-		<key>SnapsToGrid</key>
-		<string>YES</string>
 	</dict>
 	<key>GuidesLocked</key>
 	<string>NO</string>
@@ -1479,7 +1481,7 @@
 		</dict>
 	</array>
 	<key>ModificationDate</key>
-	<string>2006-10-02 17:36:29 -0700</string>
+	<string>2006-11-16 19:02:35 -0800</string>
 	<key>Modifier</key>
 	<string>Wilfredo Sánchez</string>
 	<key>NotesVisible</key>
@@ -1496,13 +1498,13 @@
 	<dict>
 		<key>NSBottomMargin</key>
 		<array>
-			<string>float</string>
-			<string>0</string>
+			<string>coded</string>
+			<string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
 		</array>
 		<key>NSLeftMargin</key>
 		<array>
-			<string>float</string>
-			<string>0</string>
+			<string>coded</string>
+			<string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
 		</array>
 		<key>NSOrientation</key>
 		<array>
@@ -1516,18 +1518,18 @@
 		</array>
 		<key>NSRightMargin</key>
 		<array>
-			<string>float</string>
-			<string>0</string>
+			<string>coded</string>
+			<string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
 		</array>
 		<key>NSScalingFactor</key>
 		<array>
-			<string>float</string>
-			<string>1.2</string>
+			<string>coded</string>
+			<string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFkl4M/8zMzQAAAAIY=</string>
 		</array>
 		<key>NSTopMargin</key>
 		<array>
-			<string>float</string>
-			<string>0</string>
+			<string>coded</string>
+			<string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
 		</array>
 	</dict>
 	<key>ReadOnly</key>
@@ -1567,7 +1569,7 @@
 		<key>ShowStatusBar</key>
 		<true/>
 		<key>VisibleRegion</key>
-		<string>{{0, 0}, {640, 490.833}}</string>
+		<string>{{0, 1.19209e-06}, {640, 490.833}}</string>
 		<key>Zoom</key>
 		<string>1</string>
 	</dict>

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -11,14 +11,14 @@
          
          raise NotImplementedError("Only IPrincipal interface is supported")
  
-@@ -52,9 +52,23 @@
+@@ -52,33 +52,44 @@
  class PrincipalCredentials(object):
      implements(IPrincipalCredentials)
  
 -    def __init__(self, principal, principalURI, credentials):
 -        self.principal = principal
 -        self.principalURI = principalURI
-+    def __init__(self, authnPrincipal, authnURI, authzPrincipal, authzURI, credentials):
++    def __init__(self, authnPrincipal, authzPrincipal, credentials):
 +        """
 +        Initialize with both authentication and authorization values. Note that in most cases theses will be the same
 +        since HTTP auth makes no distinction between the two - but we may be layering some addition auth on top of this
@@ -28,18 +28,20 @@
 +        @param authnURI: C{str} containing the URI of the authenticated principal.
 +        @param authzPrincipal: L{IDAVPrincipalResource} for the authorized principal.
 +        @param authzURI: C{str} containing the URI of the authorized principal.
-+        @param credentials: L{IPrincipalCredentials} for the authentication credentials.
++        @param credentials: L{ICredentials} for the authentication credentials.
 +        """
-+        
 +        self.authnPrincipal = authnPrincipal
-+        self.authnURI = authnURI
 +        self.authzPrincipal = authzPrincipal
-+        self.authzURI = authzURI
          self.credentials = credentials
  
      def checkPassword(self, password):
-@@ -66,19 +80,20 @@
+         return self.credentials.checkPassword(password)
  
+ 
+-class TwistedPropertyChecker:
++class TwistedPropertyChecker(object):
+     implements(checkers.ICredentialsChecker)
+ 
      credentialInterfaces = (IPrincipalCredentials,)
  
 -    def _cbPasswordMatch(self, matched, principalURI):
@@ -49,9 +51,9 @@
 +            # We return both URIs
 +            return principalURIs
          else:
-             raise error.UnauthorizedLogin(
+-            raise error.UnauthorizedLogin(
 -                "Bad credentials for: %s" % (principalURI,))
-+                "Bad credentials for: %s" % (principalURIs[0],))
++            raise error.UnauthorizedLogin("Bad credentials for: %s" % (principalURIs[0],))
  
      def requestAvatarId(self, credentials):
          pcreds = IPrincipalCredentials(credentials)
@@ -60,7 +62,7 @@
  
          d = defer.maybeDeferred(credentials.checkPassword, pswd)
 -        d.addCallback(self._cbPasswordMatch, pcreds.principalURI)
-+        d.addCallback(self._cbPasswordMatch, (pcreds.authnURI, pcreds.authzURI,))
++        d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
          return d
  
  ##

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/element/rfc4331.py	(revision 0)
 +++ twisted/web2/dav/element/rfc4331.py	(revision 0)
-@@ -0,0 +1,110 @@
+@@ -0,0 +1,55 @@
 +##
 +# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 +#
@@ -58,58 +58,3 @@
 +    name = "quota-used-bytes"
 +    hidden = True
 +    protected = True
-+##
-+# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
-+#
-+# Permission is hereby granted, free of charge, to any person obtaining a copy
-+# of this software and associated documentation files (the "Software"), to deal
-+# in the Software without restriction, including without limitation the rights
-+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+# copies of the Software, and to permit persons to whom the Software is
-+# furnished to do so, subject to the following conditions:
-+# 
-+# The above copyright notice and this permission notice shall be included in all
-+# copies or substantial portions of the Software.
-+# 
-+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-+# SOFTWARE.
-+#
-+# DRI: Cyrus Daboo, cdaboo at apple.com
-+##
-+
-+"""
-+RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
-+
-+This module provides XML element definitions for use with WebDAV.
-+
-+See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
-+"""
-+
-+from twisted.web2.dav.element.base import WebDAVTextElement
-+
-+##
-+# Section 3 & 4 (Quota Properties)
-+##
-+
-+class QuotaAvailableBytes (WebDAVTextElement):
-+    """
-+    Property which contains the the number of bytes available under the
-+    current quota to store data in a collection (RFC 4331, section 3)
-+    """
-+    name = "quota-available-bytes"
-+    hidden = True
-+    protected = True
-+
-+class QuotaUsedBytes (WebDAVTextElement):
-+    """
-+    Property which contains the the number of bytes used under the
-+    current quota to store data in a collection (RFC 4331, section 4)
-+    """
-+    name = "quota-used-bytes"
-+    hidden = True
-+    protected = True

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -20,7 +20,26 @@
          @return: An L{Deferred} that fires when all the children have been found
          """
  
-@@ -180,6 +182,80 @@
+@@ -125,15 +127,10 @@
+             L{responsecode.UNAUTHORIZED}) if not authorized.
+         """
+ 
+-    def principalCollections(request):
++    def principalCollections():
+         """
+-        Provides the DAV:HRef's of collection resources which contain principal
+-        resources which may be used in access control entries on this resource.
+-        (RFC 3744, section 5.8)
+-        @param request: the request being processed.
+-        @return: a deferred sequence of L{davxml.HRef}s referring to
+-            collection resources which implement the
+-            C{DAV:principal-property-search} C{REPORT}.
++        @return: an interable of L{IDAVPrincipalCollectionResource}s which
++            contain principals used in ACLs for this resource.
+         """
+ 
+     def setAccessControlList(acl):
+@@ -180,6 +177,80 @@
              the specified principal.
          """
  
@@ -101,3 +120,18 @@
  class IDAVPrincipalResource (IDAVResource):
      """
      WebDAV principal resource.  (RFC 3744, section 2)
+@@ -212,3 +283,14 @@
+         directly a member.  (RFC 3744, section 4.4)
+         @return: a iterable of group principal URLs.
+         """
++
++class IDAVPrincipalCollectionResource (IDAVResource):
++    """
++    WebDAV principal collection resource.  (RFC 3744, section 5.8)
++    """
++    def principalCollectionURL():
++        """
++        Provides a URL for this resource which may be used to identify this
++        resource in ACL requests.  (RFC 3744, section 5.8)
++        @return: a URL.
++        """

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,20 @@
+Index: twisted/web2/dav/method/report_principal_property_search.py
+===================================================================
+--- twisted/web2/dav/method/report_principal_property_search.py	(revision 18545)
++++ twisted/web2/dav/method/report_principal_property_search.py	(working copy)
+@@ -127,13 +127,8 @@
+         matchcount = 0
+ 
+         if applyTo:
+-            # Get the principal collection set
+-            pset = waitForDeferred(self.principalCollections(request))
+-            yield pset
+-            pset = pset.getResult()
+-
+-            for phref in pset:
+-                uri = str(phref)
++            for principalCollection in self.principalCollections():
++                uri = principalCollection.principalCollectionURL()
+                 resource = waitForDeferred(request.locateResource(uri))
+                 yield resource
+                 resource = resource.getResult()

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,35 @@
 ===================================================================
 --- twisted/web2/dav/resource.py	(revision 18545)
 +++ twisted/web2/dav/resource.py	(working copy)
-@@ -130,6 +130,8 @@
+@@ -40,10 +40,18 @@
+     "unauthenticatedPrincipal",
+ ]
+ 
++import __builtin__
++if not hasattr(__builtin__, "set"):
++    import sets.Set as set
++if not hasattr(__builtin__, "frozenset"):
++    import sets.ImmutableSet as frozenset
++
+ import urllib
+ 
+ from zope.interface import implements
+ from twisted.python import log
++from twisted.python.failure import Failure
++from twisted.cred.error import UnauthorizedLogin
+ from twisted.internet.defer import Deferred, maybeDeferred, succeed
+ from twisted.internet.defer import waitForDeferred, deferredGenerator
+ from twisted.internet import reactor
+@@ -57,7 +65,7 @@
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.davxml import dav_namespace, lookupElement
+ from twisted.web2.dav.davxml import twisted_dav_namespace, twisted_private_namespace
+-from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource
++from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource, IDAVPrincipalCollectionResource
+ from twisted.web2.dav.http import NeedPrivilegesResponse
+ from twisted.web2.dav.noneprops import NonePropertyStore
+ from twisted.web2.dav.util import unimplemented, parentForURL, joinURL
+@@ -130,6 +138,8 @@
          (dav_namespace, "acl-restrictions"          ), # RFC 3744, section 5.6
          (dav_namespace, "inherited-acl-set"         ), # RFC 3744, section 5.7
          (dav_namespace, "principal-collection-set"  ), # RFC 3744, section 5.8
@@ -11,7 +39,7 @@
  
          (twisted_dav_namespace, "resource-class"),
      )
-@@ -166,6 +168,14 @@
+@@ -166,6 +176,14 @@
          if qname[0] == twisted_private_namespace:
              return succeed(False)
  
@@ -26,16 +54,34 @@
          return succeed(qname in self.liveProperties or self.deadProperties().contains(qname))
  
      def readProperty(self, property, request):
-@@ -253,7 +263,7 @@
+@@ -239,8 +257,10 @@
+                     )
  
+                 if name == "supported-report-set":
+-                    supported = [davxml.SupportedReport(report,) for report in self.supportedReports()]
+-                    return davxml.SupportedReportSet(*supported)
++                    return davxml.SupportedReportSet(*[
++                        davxml.SupportedReport(report,)
++                        for report in self.supportedReports()
++                    ])
+ 
+                 if name == "supported-privilege-set":
+                     return self.supportedPrivileges(request)
+@@ -252,9 +272,10 @@
+                     return davxml.InheritedACLSet(*self.inheritedACLSet())
+ 
                  if name == "principal-collection-set":
-                     d = self.principalCollections(request)
+-                    d = self.principalCollections(request)
 -                    d.addCallback(lambda collections: davxml.PrincipalCollectionSet(*collections))
-+                    d.addCallback(lambda collections: davxml.PrincipalCollectionSet(*[davxml.HRef.fromString(uri) for uri in collections]))
-                     return d
+-                    return d
++                    return davxml.PrincipalCollectionSet(*[
++                        davxml.HRef(principalCollection.principalCollectionURL())
++                        for principalCollection in self.principalCollections()
++                    ])
  
                  def ifAllowed(privileges, callback):
-@@ -286,7 +296,33 @@
+                     def onError(failure):
+@@ -286,7 +307,33 @@
                          d.addCallback(gotACL)
                          return d
                      return ifAllowed((davxml.ReadACL(),), callback)
@@ -69,9 +115,13 @@
              elif namespace == twisted_dav_namespace:
                  if name == "resource-class":
                      class ResourceClass (davxml.WebDAVTextElement):
-@@ -366,12 +402,26 @@
-         # FIXME: A set would be better here, that that's a python 2.4+ feature.
-         qnames = list(self.liveProperties)
+@@ -363,15 +410,28 @@
+         """
+         See L{IDAVResource.listProperties}.
+         """
+-        # FIXME: A set would be better here, that that's a python 2.4+ feature.
+-        qnames = list(self.liveProperties)
++        qnames = set(self.liveProperties)
  
 +        # Add dynamic live properties that exist
 +        dynamicLiveProperties = (
@@ -97,7 +147,30 @@
      def listAllprop(self, request):
          """
          Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -509,6 +559,9 @@
+@@ -465,8 +525,22 @@
+             return super(DAVPropertyMixIn, self).displayName()
+ 
+ class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
++    """
++    WebDAV resource.
++    """
+     implements(IDAVResource)
+ 
++    def __init__(self, principalCollections=None):
++        """
++        @param principalCollections: an iterable of L{IDAVPrincipalCollectionResource}s
++            which contain principals to be used in ACLs for this resource.
++        """
++        if principalCollections is not None:
++            self._principalCollections = frozenset([
++                IDAVPrincipalCollectionResource(principalCollection)
++                for principalCollection in principalCollections
++            ])
++
+     ##
+     # DAV
+     ##
+@@ -509,6 +583,9 @@
              reactor.callLater(0, getChild)
  
          def checkPrivileges(child):
@@ -107,7 +180,7 @@
              if privileges is None:
                  return child
     
-@@ -517,14 +570,17 @@
+@@ -517,14 +594,17 @@
              return d
  
          def gotChild(child, childpath):
@@ -132,7 +205,7 @@
  
              reactor.callLater(0, getChild)
  
-@@ -535,10 +591,10 @@
+@@ -535,10 +615,10 @@
                  completionDeferred.callback(None)
              else:
                  childpath = joinURL(basepath, childname)
@@ -147,16 +220,22 @@
  
          getChild()
  
-@@ -564,7 +620,7 @@
+@@ -564,19 +644,21 @@
          See L{IDAVResource.authorize}.
          """
          def onError(failure):
 -            log.err("Invalid authentication details: %s" % (request,))
-+            log.err("Invalid authentication details: %s" % (failure,))
-             raise HTTPError(UnauthorizedResponse(
+-            raise HTTPError(UnauthorizedResponse(
++            failure.trap(UnauthorizedLogin)
++
++            log.err("Authentication failed: %s" % (failure.value,))
++            return Failure(HTTPError(UnauthorizedResponse(
                  request.credentialFactories,
                  request.remoteAddr
-@@ -574,9 +630,9 @@
+-            ))
++            )))
+ 
+         def onAuth(result):
              def onErrors(failure):
                  failure.trap(AccessDeniedError)
                  
@@ -168,8 +247,17 @@
                      response = UnauthorizedResponse(request.credentialFactories,
                                                      request.remoteAddr)
                  else:
-@@ -600,16 +656,22 @@
+@@ -587,7 +669,7 @@
+                 # class is supposed to be a FORBIDDEN status code and
+                 # "Authorization will not help" according to RFC2616
+                 #
+-                raise HTTPError(response)
++                return Failure(HTTPError(response))
  
+             d = self.checkPrivileges(request, privileges, recurse)
+             d.addErrback(onErrors)
+@@ -600,16 +682,21 @@
+ 
      def authenticate(self, request):
          def loginSuccess(result):
 -            request.user = result[1]
@@ -177,7 +265,6 @@
 +            """
 +            @param result: returned tuple from auth.DAVRealm.requestAvatar.
 +            """
-+            
 +            request.authnUser = result[1]
 +            request.authzUser = result[2]
 +            return (request.authnUser, request.authzUser,)
@@ -195,28 +282,29 @@
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -625,9 +687,11 @@
+@@ -625,9 +712,10 @@
  
                  # Try to match principals in each principal collection on the resource
                  def gotDetails(details):
 -                    principal = IDAVPrincipalResource(details[0])
 -                    principalURI = details[1]
 -                    return PrincipalCredentials(principal, principalURI, creds)
-+                    authnPrincipal = IDAVPrincipalResource(details[0][0])
-+                    authnURI = details[0][1]
-+                    authzPrincipal = IDAVPrincipalResource(details[1][0])
-+                    authzURI = details[1][1]
-+                    return PrincipalCredentials(authnPrincipal, authnURI, authzPrincipal, authzURI, creds)
++                    authnPrincipal, authzPrincipal = details
++                    authnPrincipal = IDAVPrincipalResource(authnPrincipal)
++                    authzPrincipal = IDAVPrincipalResource(authzPrincipal)
++                    return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
  
                  def login(pcreds):
                      d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -635,13 +699,14 @@
+@@ -635,13 +723,15 @@
  
                      return d
  
 -                d = self.findPrincipalForAuthID(request, creds.username)
+-                d.addCallback(gotDetails).addCallback(login)
 +                d = self.principalsForAuthID(request, creds.username)
-                 d.addCallback(gotDetails).addCallback(login)
++                d.addCallback(gotDetails)
++                d.addCallback(login)
  
                  return d
          else:
@@ -228,7 +316,7 @@
  
      ##
      # ACL
-@@ -650,10 +715,10 @@
+@@ -650,49 +740,23 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -242,43 +330,37 @@
          else:
              return unauthenticatedPrincipal
  
-@@ -666,32 +731,26 @@
-         present on this resource, it tries to get it from the parent, unless it
-         is the root or has no parent.
+-    def principalCollections(self, request):
++    def principalCollections(self):
          """
+         See L{IDAVResource.accessControlList}.
+-
+-        This implementation tries to read the L{davxml.PrincipalCollectionSet}
+-        from the dead property store of this resource and uses that. If not
+-        present on this resource, it tries to get it from the parent, unless it
+-        is the root or has no parent.
+         """
 -        try:
 -            principalCollections = self.readDeadProperty(davxml.PrincipalCollectionSet).childrenOfType(davxml.HRef)
 -        except HTTPError, e:
 -            if e.response.code != responsecode.NOT_FOUND:
 -                raise
-+        if self.hasDeadProperty(davxml.PrincipalCollectionSet):
-+            return succeed([
-+                str(href) for href in
-+                self.readDeadProperty(davxml.PrincipalCollectionSet).childrenOfType(davxml.HRef)
-+            ])
++        if hasattr(self, "_principalCollections"):
++            return self._principalCollections
++        else:
++            return ()
  
 -            principalCollections = []
-+        myURL = request.urlForResource(self)
-+        if myURL == "/":
-+            return succeed(())
- 
+-
 -            # Try the parent
 -            myURL = request.urlForResource(self)
 -            if myURL != "/":
 -                parentURL = parentForURL(myURL)
-+        def gotParent(parent):
-+            if parent is None:
-+                return ()
-+            else:
-+                return parent.principalCollections(request)
- 
+-
 -                parent = waitForDeferred(request.locateResource(parentURL))
 -                yield parent
 -                parent = parent.getResult()
-+        d = request.locateResource(parentForURL(myURL))
-+        d.addCallback(gotParent)
-+        return d
- 
+-
 -                if parent:
 -                    principalCollections = waitForDeferred(parent.principalCollections(request))
 -                    yield principalCollections
@@ -288,11 +370,61 @@
 -
 -    principalCollections = deferredGenerator(principalCollections)
 -
-     def defaultAccessControlList(self):
+-    def defaultAccessControlList(self):
++    def defaultRootAccessControlList(self):
          """
          @return: the L{davxml.ACL} element containing the default access control
-@@ -1146,49 +1205,95 @@
+             list for this resource.
+@@ -704,6 +768,17 @@
+         #
+         return readonlyACL
  
++    def defaultAccessControlList(self):
++        """
++        @return: the L{davxml.ACL} element containing the default access control
++            list for this resource.
++        """
++        #
++        # The default behaviour is no ACL; we should inherrit from the parent
++        # collection.
++        #
++        return davxml.ACL()
++
+     def setAccessControlList(self, acl):
+         """
+         See L{IDAVResource.setAccessControlList}.
+@@ -1032,9 +1107,9 @@
+ 
+             if myURL == "/":
+                 # If we get to the root without any ACLs, then use the default.
++                acl = self.defaultRootAccessControlList()
++            else:
+                 acl = self.defaultAccessControlList()
+-            else:
+-                acl = davxml.ACL()
+ 
+         # Dynamically update privileges for those ace's that are inherited.
+         if inheritance:
+@@ -1070,7 +1145,7 @@
+                                 # Adjust ACE for inherit on this resource
+                                 children = list(ace.children)
+                                 children.remove(TwistedACLInheritable())
+-                                children.append(davxml.Inherited(davxml.HRef.fromString(parentURL)))
++                                children.append(davxml.Inherited(davxml.HRef(parentURL)))
+                                 aces.append(davxml.ACE(*children))
+             else:
+                 aces.extend(inherited_aces)
+@@ -1122,7 +1197,7 @@
+                 # Adjust ACE for inherit on this resource
+                 children = list(ace.children)
+                 children.remove(TwistedACLInheritable())
+-                children.append(davxml.Inherited(davxml.HRef.fromString(request.urlForResource(self))))
++                children.append(davxml.Inherited(davxml.HRef(request.urlForResource(self))))
+                 aces.append(davxml.ACE(*children))
+                 
+         # Filter out those that do not have a principal match with the current principal
+@@ -1146,49 +1221,69 @@
+ 
          This implementation returns an empty set.
          """
 -
@@ -320,73 +452,59 @@
              It will errback with an HTTPError(responsecode.FORBIDDEN) if
              the principal isn't found.
          """
-+        def gotAuthn(principal):
-+            if principal is None:
-+                log.msg("Could not find principal matching user id: %s" % (authid,))
-+                raise HTTPError(responsecode.FORBIDDEN)
-+
-+            authnPrincipal, authnURI = principal
-+
-+            def gotAuthz(principal):
-+                authzPrincipal, authzURI = principal
-+                return ((authnPrincipal, authnURI), (authzPrincipal, authzURI))
-+
-+            d = self.authorizationPrincipal(request, authid, authnPrincipal, authnURI)
-+            d.addCallback(gotAuthz)
-+            return d
-+
-+        d = self.findPrincipalForAuthID(request, authid)
-+        d.addCallback(gotAuthn)
-+        return d
-+
-+    def findPrincipalForAuthID(self, request, authid):
-+        """
-+        Return authentication and authoirization prinicipal identifiers for the
-+        authentication identifer passed in. In this implementation authn and authz
-+        principals are the same.
-+
-+        @param request: the L{IRequest} for the request in progress.
-+        @param authid: a string containing the
-+            authentication/authorization identifier for the principal
-+            to lookup.
-+        @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
-+            that is found; {principalURI} is the C{str} URI of the principal.
-+            If not found return None.
-+        """
-         # Try to match principals in each principal collection on the resource
-         collections = waitForDeferred(self.principalCollections(request))
-         yield collections
-         collections = collections.getResult()
+-        # Try to match principals in each principal collection on the resource
+-        collections = waitForDeferred(self.principalCollections(request))
+-        yield collections
+-        collections = collections.getResult()
++        authnPrincipal = self.findPrincipalForAuthID(authid)
  
-         for collection in collections:
+-        for collection in collections:
 -            principalURI = joinURL(str(collection), authid)
-+            principalURI = joinURL(collection, authid)
++        if authnPrincipal is None:
++            log.msg("Could not find the principal resource for user id: %s" % (authid,))
++            raise HTTPError(responsecode.FORBIDDEN)
  
-             principal = waitForDeferred(request.locateResource(principalURI))
-             yield principal
-             principal = principal.getResult()
+-            principal = waitForDeferred(request.locateResource(principalURI))
+-            yield principal
+-            principal = principal.getResult()
++        d = self.authorizationPrincipal(request, authid, authnPrincipal)
++        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
++        return d
  
 -            if isPrincipalResource(principal):
 -                yield (principal, principalURI)
-+            if isPrincipalResource(principal) and principal.exists():
-+                yield principal, principalURI
-                 return
-         else:
+-                return
+-        else:
 -            principalCollections = waitForDeferred(self.principalCollections(request))
 -            yield principalCollections
 -            principalCollections = principalCollections.getResult()
-+            yield None
-+            return
++    def findPrincipalForAuthID(self, authid):
++        """
++        Return authentication and authoirization prinicipal identifiers for the
++        authentication identifer passed in. In this implementation authn and authz
++        principals are the same.
  
 -            if len(principalCollections) == 0:
 -                log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
 -            else:
 -                log.msg("Could not find principal matching user id: %s" % authid)
 -            raise HTTPError(responsecode.FORBIDDEN)
++        @param authid: a string containing the
++            authentication/authorization identifier for the principal
++            to lookup.
++        @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
++            that is found; {principalURI} is the C{str} URI of the principal.
++            If not found return None.
++        """
++        for collection in self.principalCollections():
++            principal = collection.principalForUser(authid)
++            if principal is not None:
++                return principal
++        return None
+ 
+-    findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
 -
-     findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
- 
-+    def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
++    def authorizationPrincipal(self, request, authid, authnPrincipal):
 +        """
 +        Determine the authorization principal for the given request and authentication principal.
 +        This implementation simply uses aht authentication principalk as the authoization principal.
@@ -395,16 +513,50 @@
 +        @param authid: a string containing the uthentication/authorization identifier
 +            for the principal to lookup.
 +        @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
-+        @param authnURI: a C{str} containing the URI of the authenticated principal
-+        @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
++         @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
 +            resource and URI respectively.
 +        """
-+        return succeed((authnPrincipal, authnURI,))
++        return succeed(authnPrincipal)
 +        
      def samePrincipal(self, principal1, principal2):
          """
          Check whether the two prinicpals are exactly the same in terms of
-@@ -1511,6 +1616,265 @@
+@@ -1213,7 +1308,6 @@
+             return False
+                 
+     def matchPrincipal(self, principal1, principal2, request):
+-
+         """
+         Check whether the principal1 is a principal in the set defined by
+         principal2.
+@@ -1238,6 +1332,9 @@
+             if isinstance(principal1, davxml.Unauthenticated):
+                 yield False
+                 return
++            elif isinstance(principal1, davxml.All):
++                yield False
++                return
+             else:
+                 yield True
+                 return
+@@ -1265,7 +1362,6 @@
+ 
+         assert principal2 is not None, "principal2 is None"
+ 
+-
+         # Compare two HRefs and do group membership test as well
+         if principal1 == principal2:
+             yield True
+@@ -1426,7 +1522,7 @@
+                 log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
+                 yield None
+                 return
+-            principal = davxml.HRef.fromString(self.principalURL())
++            principal = davxml.HRef(self.principalURL())
+ 
+         if isinstance(principal, davxml.HRef):
+             yield principal
+@@ -1511,6 +1607,265 @@
          return None
  
      ##
@@ -550,7 +702,7 @@
 +        assert maxsize is None or isinstance(maxsize, int), "maxsize must be an int or None"
 +        
 +        if maxsize is not None:
-+            self.writeDeadProperty(TwistedQuotaRootProperty.fromString(str(maxsize)))
++            self.writeDeadProperty(TwistedQuotaRootProperty(str(maxsize)))
 +        else:
 +            # Remove both the root and the cached used value
 +            self.removeDeadProperty(TwistedQuotaRootProperty)
@@ -640,7 +792,7 @@
 +        else:
 +            # Do brute force size determination and cache the result in the private property
 +            def _defer(result):
-+                self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(result)))
++                self.writeDeadProperty(TwistedQuotaUsedProperty(str(result)))
 +                return result
 +            d = self.quotaSize(request)
 +            d.addCallback(_defer)
@@ -660,7 +812,7 @@
 +        # Get current value
 +        def _defer(size):
 +            size += adjust
-+            self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(size)))
++            self.writeDeadProperty(TwistedQuotaUsedProperty(str(size)))
 +
 +        d = self.currentQuotaUse(request)
 +        d.addCallback(_defer)
@@ -670,7 +822,7 @@
      # HTTP
      ##
  
-@@ -1558,7 +1922,7 @@
+@@ -1558,7 +1913,7 @@
      """
      DAV resource with no children.
      """
@@ -679,7 +831,32 @@
          return succeed(None)
  
  class DAVPrincipalResource (DAVLeafResource):
-@@ -1712,6 +2076,37 @@
+@@ -1673,6 +2028,24 @@
+         else:
+             return uri in self.groupMembers()
+ 
++class DAVPrincipalCollectionResource (DAVResource):
++    """
++    WebDAV principal collection resource.  (RFC 3744, section 5.8)
++    """
++    implements(IDAVPrincipalCollectionResource)
++
++    def __init__(self, url, principalCollections=()):
++        """
++        @param url: This resource's URL.
++        """
++        DAVResource.__init__(self, principalCollections=principalCollections)
++
++        assert url.endswith("/"), "Collection URL must end in '/'"
++        self._url = url
++
++    def principalCollectionURL(self):
++        return self._url
++
+ class AccessDeniedError(Exception):
+     def __init__(self, errors):
+         """ 
+@@ -1712,6 +2085,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -24,7 +24,42 @@
  
  try:
      from twisted.web2.dav.xattrprops import xattrPropertyStore as DeadPropertyStore
-@@ -75,6 +75,12 @@
+@@ -52,9 +52,11 @@
+ 
+     Extends twisted.web2.static.File to handle WebDAV methods.
+     """
+-    def __init__(self, path,
+-                 defaultType="text/plain",
+-                 indexNames=None):
++    def __init__(
++        self, path,
++        defaultType="text/plain", indexNames=None,
++        principalCollections=()
++    ):
+         """
+         @param path: the path of the file backing this resource.
+         @param defaultType: the default mime type (as a string) for this
+@@ -62,11 +64,14 @@
+         @param indexNames: a sequence of index file names.
+         @param acl: an L{IDAVAccessControlList} with the .
+         """
+-        super(DAVFile, self).__init__(path,
+-                                      defaultType = defaultType,
+-                                      ignoredExts = (),
+-                                      processors  = None,
+-                                      indexNames  = indexNames)
++        File.__init__(
++            self, path,
++            defaultType = defaultType,
++            ignoredExts = (),
++            processors = None,
++            indexNames = indexNames,
++        )
++        DAVResource.__init__(self, principalCollections=principalCollections)
+ 
+     def __repr__(self):
+         return "<%s: %s>" % (self.__class__.__name__, self.fp.path)
+@@ -75,6 +80,12 @@
      # WebDAV
      ##
  
@@ -37,7 +72,7 @@
      def davComplianceClasses(self):
          return ("1", "access-control") # Add "2" when we have locking
  
-@@ -87,7 +93,6 @@
+@@ -87,7 +98,6 @@
          """
          See L{IDAVResource.isCollection}.
          """
@@ -45,7 +80,7 @@
          return self.fp.isdir()
  
      ##
-@@ -98,6 +103,50 @@
+@@ -98,6 +108,50 @@
          return succeed(davPrivilegeSet)
  
      ##
@@ -96,14 +131,27 @@
      # Workarounds for issues with File
      ##
  
-@@ -142,53 +191,50 @@
-         directory contents that they have read permissions for.
-         """
-         if not self.fp.exists():
+@@ -132,63 +186,11 @@
+         return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
+ 
+     def createSimilarFile(self, path):
+-        return self.__class__(path, defaultType=self.defaultType, indexNames=self.indexNames[:])
++        return self.__class__(
++            path, self.defaultType, self.indexNames[:],
++            principalCollections=self.principalCollections()
++        )
+ 
+-    def render(self, request):
+-        """
+-        This is a direct copy of web2.static.render with the
+-        listChildren behavior replaced with findChildren to ensure
+-        that the current authenticated principal can only list
+-        directory contents that they have read permissions for.
+-        """
+-        if not self.fp.exists():
 -            yield responsecode.NOT_FOUND
 -            return
-+            return responsecode.NOT_FOUND
- 
+-
 -        if self.fp.isdir():
 -            if request.uri[-1] != "/":
 -                # Redirect to include trailing '/' in URI
@@ -119,32 +167,19 @@
 -                    filtered_aces = waitForDeferred(self.inheritedACEsforChildren(request))
 -                    yield filtered_aces
 -                    filtered_aces = filtered_aces.getResult()
-+        if not self.fp.isdir():
-+            # Do regular resource behavior from superclass
-+            return super(DAVFile, self).render(request)
- 
+-
 -                    children = []
-+        #
-+        # Do custom rendering of directory so that we can enforce ACLs.
-+        #
- 
+-
 -                    def found(request, uri):
 -                        children.append(uri.split("/")[-1].rstrip("/"))
-+        if request.uri[-1] != "/":
-+            # Redirect to include trailing '/' in URI
-+            return RedirectResponse(request.unparseURL(path=request.path+'/'))
- 
+-
 -                    x = waitForDeferred(
 -                        self.findChildren("1", request, found, (davxml.Read(),),
 -                                          inherited_aces=filtered_aces)
 -                    )
 -                    yield x
 -                    x = x.getResult()
-+        # Render from the index file, if we have one
-+        index_fp = self.fp.childSearchPreauth(*self.indexNames)
-+        if index_fp:
-+            return self.createSimilarFile(index_fp.path).render(request)
- 
+-
 -                    # Render from a DirectoryLister
 -                    standin = dirlist.DirectoryLister(
 -                        self.fp.path,
@@ -155,35 +190,12 @@
 -                    )
 -                yield standin.render(request)
 -                return
-+        # Render from a DirectoryLister
-+        def findChildren(filtered_aces):
-+            children = []
- 
+-
 -        # Do regular resource behavior from superclass
 -        yield super(DAVFile, self).render(request)
 -    
 -    render = deferredGenerator(render)
-+            def found(request, uri):
-+                children.append(uri.rstrip("/").split("/")[-1])
- 
-+            d = self.findChildren("1", request, found,
-+                                  (davxml.Read(),), inherited_aces=filtered_aces)
-+            d.addCallback(render, children)
-+            return d
-+
-+        def render(_, children):
-+            return dirlist.DirectoryLister(
-+                self.fp.path,
-+                children,
-+                self.contentTypes,
-+                self.contentEncodings,
-+                self.defaultType
-+            ).render(request)
-+
-+        d = self.inheritedACEsforChildren(request)
-+        d.addCallback(findChildren)
-+        return d
-+
+-
  #
  # Attach method handlers to DAVFile
  #

Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,69 @@
+Index: twisted/web2/dav/test/test_acl.py
+===================================================================
+--- twisted/web2/dav/test/test_acl.py	(revision 18545)
++++ twisted/web2/dav/test/test_acl.py	(working copy)
+@@ -30,6 +30,7 @@
+ from twisted.web2.auth import basic
+ from twisted.web2.stream import MemoryStream
+ from twisted.web2.dav import davxml
++from twisted.web2.dav.resource import DAVPrincipalCollectionResource
+ from twisted.web2.dav.util import davXMLFromStream
+ from twisted.web2.dav.auth import TwistedPasswordProperty, IPrincipal, DavRealm, TwistedPropertyChecker, AuthenticationWrapper
+ 
+@@ -38,6 +39,11 @@
+ from twisted.web2.dav.test.util import Site, serialize
+ from twisted.web2.dav.test.test_resource import TestResource, TestDAVPrincipalResource
+ 
++class TestPrincipalsCollection(DAVPrincipalCollectionResource, TestResource):
++    def __init__(self, url, children):
++        DAVPrincipalCollectionResource.__init__(self, url)
++        TestResource.__init__(self, url, children, principalCollections=(self,))
++
+ class ACL(twisted.web2.dav.test.util.TestCase):
+     """
+     RFC 3744 (WebDAV ACL) tests.
+@@ -46,8 +52,14 @@
+         if not hasattr(self, "docroot"):
+             self.docroot = self.mktemp()
+             os.mkdir(self.docroot)
+-            rootresource = self.resource_class(self.docroot)
+ 
++            userResource = TestDAVPrincipalResource("/principals/user01")
++            userResource.writeDeadProperty(TwistedPasswordProperty("user01"))
++
++            principalCollection = TestPrincipalsCollection("/principals/", children={"user01": userResource})
++
++            rootResource = self.resource_class(self.docroot, principalCollections=(principalCollection,))
++
+             portal = Portal(DavRealm())
+             portal.registerChecker(TwistedPropertyChecker())
+ 
+@@ -56,26 +68,14 @@
+             loginInterfaces = (IPrincipal,)
+ 
+             self.site = Site(AuthenticationWrapper(
+-                rootresource, 
++                rootResource, 
+                 portal,
+                 credentialFactories,
+                 loginInterfaces
+             ))
+ 
+-            rootresource.setAccessControlList(self.grant(davxml.All()))
++            rootResource.setAccessControlList(self.grant(davxml.All()))
+ 
+-            userresource = TestDAVPrincipalResource("/principals/user01")
+-            userresource.writeDeadProperty(TwistedPasswordProperty.fromString("user01"))
+-
+-            rootresource.putChild(
+-                "principals",
+-                TestResource("/principals", {"user01": userresource})
+-            )
+-
+-            rootresource.writeDeadProperty(
+-                davxml.PrincipalCollectionSet(davxml.HRef("/principals/"))
+-            )
+-
+         for name, acl in (
+             ("none"       , self.grant()),
+             ("read"       , self.grant(davxml.Read())),

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,23 @@
 ===================================================================
 --- twisted/web2/dav/test/test_resource.py	(revision 18545)
 +++ twisted/web2/dav/test/test_resource.py	(working copy)
-@@ -282,7 +282,8 @@
+@@ -192,13 +192,10 @@
+ class AccessTests(TestCase):
+     def setUp(self):
+         gooduser = TestDAVPrincipalResource('/users/gooduser')
++        gooduser.writeDeadProperty(TwistedPasswordProperty('goodpass'))
+ 
+-        gooduser.writeDeadProperty(
+-            TwistedPasswordProperty.fromString('goodpass'))
+-
+         baduser = TestDAVPrincipalResource('/users/baduser')
+-        baduser.writeDeadProperty(
+-            TwistedPasswordProperty.fromString('badpass'))
++        baduser.writeDeadProperty(TwistedPasswordProperty('badpass'))
+ 
+         protected = TestResource('/protected')
+         protected.setAccessControlList(davxml.ACL(
+@@ -282,7 +279,8 @@
          # Has auth; should allow
  
          request = SimpleRequest(site, "GET", "/")
@@ -12,7 +28,22 @@
          d = request.locateResource('/')
          d.addCallback(_checkPrivileges)
          d.addCallback(expectOK)
-@@ -380,8 +381,8 @@
+@@ -348,12 +346,12 @@
+             davxml.Grant(davxml.Privilege(davxml.All())),
+             davxml.Protected()))
+ 
+-    def __init__(self, uri=None, children=None):
++    def __init__(self, uri=None, children=None, principalCollections=()):
+         """
+         @param uri: A string respresenting the URI of the given resource
+         @param children: a dictionary of names to Resources
+         """
+-
++        DAVResource.__init__(self, principalCollections=principalCollections)
+         self.children = children
+         self.uri = uri
+ 
+@@ -380,8 +378,8 @@
          return succeed(davPrivilegeSet)
  
      def currentPrincipal(self, request):
@@ -23,3 +54,32 @@
          else:
              return davxml.Principal(davxml.Unauthenticated())
  
+@@ -400,17 +398,23 @@
+     def accessControlList(self, request, **kwargs):
+         return succeed(self.acl)
+     
++    def principalForUser(self, user):
++        return self.children[user]
+ 
+ class AuthAllResource (TestResource):
+-    """Give Authenticated principals all privileges deny everything else
+     """
++    Give Authenticated principals all privileges and deny everyone else.
++    """
+     acl = davxml.ACL(
+         davxml.ACE(
+             davxml.Principal(davxml.Authenticated()),
+             davxml.Grant(davxml.Privilege(davxml.All())),
+-            davxml.Protected()))
+-
++            davxml.Protected()
++        )
++    )
+     
+ class TestDAVPrincipalResource(DAVPrincipalResource, TestResource):
+-    """Get deadProperties from TestResource
+-    """
++    # Get dead properties from TestResource
++
++    def principalURL(self):
++        return self.uri

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	2006-12-01 19:18:19 UTC (rev 637)
@@ -107,7 +107,7 @@
  
          #
          # Parse the URL
-@@ -406,18 +418,70 @@
+@@ -406,19 +418,71 @@
                  "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
              ))
  
@@ -124,15 +124,18 @@
  
          def notFound(f):
              f.trap(http.HTTPError)
+-            if f.response.code != responsecode.NOT_FOUND:
+-                raise f
 +            if f.value.response.code != responsecode.NOT_FOUND:
 +                return f
-+            return None
-+
+             return None
+ 
+-        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
 +        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
 +        d.addCallback(self._rememberResource, path)
 +        d.addErrback(notFound)
 +        return d
-+
+ 
 +    def locateChildResource(self, parent, child_name):
 +        """
 +        Looks up the child resource with the given name given the parent
@@ -169,16 +172,15 @@
 +
 +        def notFound(f):
 +            f.trap(http.HTTPError)
-             if f.response.code != responsecode.NOT_FOUND:
--                raise f
++            if f.value.response.code != responsecode.NOT_FOUND:
 +                return f
-             return None
- 
--        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
++            return None
++
 +        d = defer.maybeDeferred(self._getChild, None, parent, [segment], updatepaths=False)
 +        d.addCallback(self._rememberResource, url)
 +        d.addErrback(notFound)
 +        return d
- 
++
      def _processingFailed(self, reason):
          if reason.check(http.HTTPError) is not None:
+             # If the exception was an HTTPError, leave it alone

Modified: CalendarServer/trunk/run
===================================================================
--- CalendarServer/trunk/run	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/run	2006-12-01 19:18:19 UTC (rev 637)
@@ -161,6 +161,12 @@
   fi;
 
   if ! "${setup_only}"; then
+    if [ ! -f "${config}" ]; then
+        echo "Missing config file: ${config}";
+        echo "You might want to copy conf/caldavd-test.plist to conf/caldavd-dev.plist and start from there.";
+        exit 1;
+    fi;
+
     cd "${wd}";
     exec "${python}" "${caldav}/bin/caldavd" ${daemonize} \
         -f "${config}"                                    \

Copied: CalendarServer/trunk/support/CalendarServer.tmproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj)
===================================================================
--- CalendarServer/trunk/support/CalendarServer.tmproj	                        (rev 0)
+++ CalendarServer/trunk/support/CalendarServer.tmproj	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>currentDocument</key>
+	<string>../../Twisted/twisted/web2/dav/resource.py</string>
+	<key>documents</key>
+	<array>
+		<dict>
+			<key>expanded</key>
+			<true/>
+			<key>name</key>
+			<string>twistedcaldav</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../twistedcaldav</string>
+		</dict>
+		<dict>
+			<key>expanded</key>
+			<true/>
+			<key>name</key>
+			<string>web2</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../../Twisted/twisted/web2</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>twisted</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../../Twisted/twisted</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>PyOpenDirectory</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../../PyOpenDirectory</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>PyKerberos</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../../PyKerberos</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>conf</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../conf</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>bin</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../bin</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>doc</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*)$</string>
+			<key>sourceDirectory</key>
+			<string>../doc</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>support</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|.*\.xcodeproj)$</string>
+			<key>sourceDirectory</key>
+			<string></string>
+		</dict>
+		<dict>
+			<key>children</key>
+			<array>
+				<dict>
+					<key>filename</key>
+					<string>../run</string>
+					<key>lastUsed</key>
+					<date>2006-11-28T19:36:50Z</date>
+				</dict>
+				<dict>
+					<key>filename</key>
+					<string>../test</string>
+					<key>lastUsed</key>
+					<date>2006-11-27T23:16:03Z</date>
+				</dict>
+				<dict>
+					<key>filename</key>
+					<string>../testcaldav</string>
+					<key>lastUsed</key>
+					<date>2006-11-27T23:16:01Z</date>
+				</dict>
+				<dict>
+					<key>filename</key>
+					<string>../setup.py</string>
+					<key>lastUsed</key>
+					<date>2006-11-27T23:18:10Z</date>
+				</dict>
+				<dict>
+					<key>filename</key>
+					<string>../LICENSE</string>
+					<key>lastUsed</key>
+					<date>2006-11-27T23:18:09Z</date>
+				</dict>
+				<dict>
+					<key>filename</key>
+					<string>../README</string>
+				</dict>
+			</array>
+			<key>name</key>
+			<string>topfiles</string>
+		</dict>
+	</array>
+	<key>fileHierarchyDrawerWidth</key>
+	<integer>325</integer>
+	<key>metaData</key>
+	<dict>
+		<key>../../Twisted/twisted/web2/dav/resource.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>56</integer>
+				<key>line</key>
+				<integer>1265</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>1246</integer>
+		</dict>
+		<key>../twistedcaldav/resource.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>80</integer>
+				<key>line</key>
+				<integer>261</integer>
+			</dict>
+			<key>columnSelection</key>
+			<false/>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>231</integer>
+			<key>selectFrom</key>
+			<dict>
+				<key>column</key>
+				<integer>58</integer>
+				<key>line</key>
+				<integer>261</integer>
+			</dict>
+			<key>selectTo</key>
+			<dict>
+				<key>column</key>
+				<integer>80</integer>
+				<key>line</key>
+				<integer>261</integer>
+			</dict>
+		</dict>
+	</dict>
+	<key>openDocuments</key>
+	<array>
+		<string>../../Twisted/twisted/web2/dav/resource.py</string>
+		<string>../twistedcaldav/resource.py</string>
+	</array>
+	<key>showFileHierarchyDrawer</key>
+	<true/>
+	<key>windowFrame</key>
+	<string>{{705, 64}, {1019, 1050}}</string>
+</dict>
+</plist>

Copied: CalendarServer/trunk/support/CalendarServer.xcodeproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj)


Property changes on: CalendarServer/trunk/support/CalendarServer.xcodeproj
___________________________________________________________________
Name: svn:ignore
   + *.mode*
*.pbxuser


Deleted: CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj/project.pbxproj	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,329 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 42;
-	objects = {
-
-/* Begin PBXFileReference section */
-		35069C170922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
-		35069C190922B96300389D48 /* caldavxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = caldavxml.py; sourceTree = "<group>"; };
-		35069C1B0922B96300389D48 /* index.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = index.py; sourceTree = "<group>"; };
-		35069C1E0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
-		35069C200922B96300389D48 /* mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcalendar.py; sourceTree = "<group>"; };
-		35069C220922B96300389D48 /* mkcol.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcol.py; sourceTree = "<group>"; };
-		35069C240922B96300389D48 /* put.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put.py; sourceTree = "<group>"; };
-		35069C260922B96300389D48 /* report_calquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_calquery.py; sourceTree = "<group>"; };
-		35069C2A0922B96300389D48 /* static.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = static.py; sourceTree = "<group>"; };
-		35069C2D0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
-		35069C790922B96300389D48 /* test_calendarquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_calendarquery.py; sourceTree = "<group>"; };
-		35069C7B0922B96300389D48 /* test_collectioncontents.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_collectioncontents.py; sourceTree = "<group>"; };
-		35069C7D0922B96300389D48 /* test_mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_mkcalendar.py; sourceTree = "<group>"; };
-		35069C7F0922B96300389D48 /* test_options.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_options.py; sourceTree = "<group>"; };
-		35069C820922B96300389D48 /* util.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = util.py; sourceTree = "<group>"; };
-		35069C840922B96300389D48 /* ical.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = ical.py; sourceTree = "<group>"; };
-		35069C870922BA1600389D48 /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; path = data; sourceTree = "<group>"; };
-		35069DDE0922BCCE00389D48 /* twisted */ = {isa = PBXFileReference; lastKnownFileType = folder; path = twisted; sourceTree = "<group>"; };
-		3506A2EE0922BD0C00389D48 /* web2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = web2; path = twisted/web2; sourceTree = "<group>"; };
-		3506A2F10922BD2700389D48 /* dav */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dav; path = twisted/web2/dav; sourceTree = "<group>"; };
-		3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = wrapper.xcdatamodel; path = CalendarIndex.xcdatamodel; sourceTree = "<group>"; };
-		3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-desruisseaux-caldav-sched.txt"; sourceTree = "<group>"; };
-		3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-dusseault-caldav.txt"; sourceTree = "<group>"; };
-		350781A2096DF5A4004A4366 /* dateops.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = dateops.py; sourceTree = "<group>"; };
-		3508925D0ABA0AC100F9995A /* caldavd-dev.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "caldavd-dev.plist"; sourceTree = "<group>"; };
-		3508925E0ABA0AC100F9995A /* caldavd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = caldavd.plist; sourceTree = "<group>"; };
-		3508925F0ABA0AC100F9995A /* launchd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = launchd.plist; sourceTree = "<group>"; };
-		350892600ABA0AC100F9995A /* repository-dev.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-dev.xml"; sourceTree = "<group>"; };
-		350892610ABA0AC100F9995A /* repository-static.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-static.xml"; sourceTree = "<group>"; };
-		350892620ABA0AC100F9995A /* repository.dtd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.dtd; sourceTree = "<group>"; };
-		3524532E098982D900B9179C /* test_DAV.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_DAV.py; sourceTree = "<group>"; };
-		353696D1092BB6500075CE69 /* test_icalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_icalendar.py; sourceTree = "<group>"; };
-		353A557C099153D900A08D28 /* setup.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = setup.py; path = ../setup.py; sourceTree = "<group>"; };
-		353A63BF0994448C00A08D28 /* caldavd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; path = caldavd; sourceTree = "<group>"; };
-		356E29FC0AC301C900F46D07 /* authkerb.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = authkerb.py; sourceTree = "<group>"; };
-		356E29FE0AC301C900F46D07 /* customxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = customxml.py; sourceTree = "<group>"; };
-		356E29FF0AC301C900F46D07 /* db.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = db.py; sourceTree = "<group>"; };
-		356E2A000AC301C900F46D07 /* directory.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = directory.py; sourceTree = "<group>"; };
-		356E2A010AC301C900F46D07 /* itip.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = itip.py; sourceTree = "<group>"; };
-		356E2A020AC301C900F46D07 /* principalindex.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = principalindex.py; sourceTree = "<group>"; };
-		356E2A440AC3057F00F46D07 /* post.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = post.py; sourceTree = "<group>"; };
-		356E2A450AC3057F00F46D07 /* schedule_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule_common.py; sourceTree = "<group>"; };
-		356E2A480AC3490100F46D07 /* caldavd.8 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = caldavd.8; sourceTree = "<group>"; };
-		356E2A490AC3490100F46D07 /* twisted.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twisted.cfg; sourceTree = "<group>"; };
-		356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twistedcaldav.cfg; sourceTree = "<group>"; };
-		356E2A510AC3495700F46D07 /* Directory Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "Directory Repository.graffle"; sourceTree = "<group>"; };
-		356E2A520AC3495700F46D07 /* XML Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "XML Repository.graffle"; sourceTree = "<group>"; };
-		359CD65C0946136A002E3A15 /* test_xml.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_xml.py; sourceTree = "<group>"; };
-		35A15ED40985C14800D404FF /* run */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = run; path = ../run; sourceTree = "<group>"; };
-		35A15ED60985C14800D404FF /* test */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = test; path = ../test; sourceTree = "<group>"; };
-		35B48642095CA1D000AB3411 /* rfc2518.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc2518.txt; sourceTree = "<group>"; };
-		35B48643095CA1D000AB3411 /* rfc3253.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3253.txt; sourceTree = "<group>"; };
-		35B48644095CA1D000AB3411 /* rfc3744.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3744.txt; sourceTree = "<group>"; };
-		35B71A24097C3A3000E65B22 /* instance.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = instance.py; sourceTree = "<group>"; };
-		35B71A25097C3B2C00E65B22 /* copymove.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = copymove.py; sourceTree = "<group>"; };
-		35B71A26097C3B2C00E65B22 /* delete.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = delete.py; sourceTree = "<group>"; };
-		35B71A27097C3B2C00E65B22 /* report_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_multiget.py; sourceTree = "<group>"; };
-		35B8AA660A0BFE60005547E5 /* http.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = http.py; sourceTree = "<group>"; };
-		35B8AA670A0BFE60005547E5 /* repository.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = repository.py; sourceTree = "<group>"; };
-		35CF70B50A0FF59100993B2A /* repository.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.xml; sourceTree = "<group>"; };
-		35CF70B60A0FF59100993B2A /* server.pem */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = server.pem; sourceTree = "<group>"; };
-		35E2ACFF09BF6D3400BC8CB9 /* logging.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = logging.py; sourceTree = "<group>"; };
-		35E2AF1409C2491600BC8CB9 /* put_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put_common.py; sourceTree = "<group>"; };
-		35E2AF1509C2491600BC8CB9 /* schedule.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule.py; sourceTree = "<group>"; };
-		35E2B38109C7C2DF00BC8CB9 /* lib-patches */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "lib-patches"; sourceTree = "<group>"; };
-		35E8059B0981C33F000981A6 /* test_props.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_props.py; sourceTree = "<group>"; };
-		35F36BAF09B3E8AD00A3D736 /* resource.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = resource.py; sourceTree = "<group>"; };
-		35F36C0E09B4FA6A00A3D736 /* icaldav.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = icaldav.py; sourceTree = "<group>"; };
-		35FC85A009ABEC0600586387 /* test_freebusyquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_freebusyquery.py; sourceTree = "<group>"; };
-		35FC85A109ABEC0600586387 /* test_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_multiget.py; sourceTree = "<group>"; };
-		35FC85A209ABEC9700586387 /* report_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_common.py; sourceTree = "<group>"; };
-		35FC85A309ABEC9700586387 /* report_freebusy.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_freebusy.py; sourceTree = "<group>"; };
-		35FC85A809ABED2B00586387 /* version.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; name = version.py; path = ../twistedcaldav/version.py; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXGroup section */
-		35069C090922B94100389D48 = {
-			isa = PBXGroup;
-			children = (
-				35069CD60922BA8600389D48 /* CalendarServer */,
-				35069CF50922BACA00389D48 /* Twisted */,
-				3506A86B0922BE1500389D48 /* Documentation */,
-				353A63B50994444700A08D28 /* Scripts */,
-				35A15ED30985C12C00D404FF /* Support */,
-				350892570ABA0A8C00F9995A /* Configuration */,
-			);
-			sourceTree = "<group>";
-		};
-		35069C160922B96300389D48 /* twistedcaldav */ = {
-			isa = PBXGroup;
-			children = (
-				35F36C0E09B4FA6A00A3D736 /* icaldav.py */,
-				35F36BAF09B3E8AD00A3D736 /* resource.py */,
-				35069C2A0922B96300389D48 /* static.py */,
-				356E2A000AC301C900F46D07 /* directory.py */,
-				35B8AA660A0BFE60005547E5 /* http.py */,
-				35069C190922B96300389D48 /* caldavxml.py */,
-				356E29FE0AC301C900F46D07 /* customxml.py */,
-				356E29FC0AC301C900F46D07 /* authkerb.py */,
-				35069C840922B96300389D48 /* ical.py */,
-				356E2A010AC301C900F46D07 /* itip.py */,
-				35B71A24097C3A3000E65B22 /* instance.py */,
-				350781A2096DF5A4004A4366 /* dateops.py */,
-				35069C1B0922B96300389D48 /* index.py */,
-				356E2A020AC301C900F46D07 /* principalindex.py */,
-				356E29FF0AC301C900F46D07 /* db.py */,
-				35B8AA670A0BFE60005547E5 /* repository.py */,
-				35E2ACFF09BF6D3400BC8CB9 /* logging.py */,
-				35069C170922B96300389D48 /* __init__.py */,
-				35069C1D0922B96300389D48 /* method */,
-				35069C2C0922B96300389D48 /* test */,
-			);
-			path = twistedcaldav;
-			sourceTree = "<group>";
-		};
-		35069C1D0922B96300389D48 /* method */ = {
-			isa = PBXGroup;
-			children = (
-				356E2A450AC3057F00F46D07 /* schedule_common.py */,
-				35B71A25097C3B2C00E65B22 /* copymove.py */,
-				35B71A26097C3B2C00E65B22 /* delete.py */,
-				35069C200922B96300389D48 /* mkcalendar.py */,
-				35069C220922B96300389D48 /* mkcol.py */,
-				356E2A440AC3057F00F46D07 /* post.py */,
-				35069C240922B96300389D48 /* put.py */,
-				35E2AF1409C2491600BC8CB9 /* put_common.py */,
-				35069C260922B96300389D48 /* report_calquery.py */,
-				35FC85A209ABEC9700586387 /* report_common.py */,
-				35FC85A309ABEC9700586387 /* report_freebusy.py */,
-				35B71A27097C3B2C00E65B22 /* report_multiget.py */,
-				35E2AF1509C2491600BC8CB9 /* schedule.py */,
-				35069C1E0922B96300389D48 /* __init__.py */,
-			);
-			path = method;
-			sourceTree = "<group>";
-		};
-		35069C2C0922B96300389D48 /* test */ = {
-			isa = PBXGroup;
-			children = (
-				3524532E098982D900B9179C /* test_DAV.py */,
-				35069C790922B96300389D48 /* test_calendarquery.py */,
-				35069C7B0922B96300389D48 /* test_collectioncontents.py */,
-				35FC85A009ABEC0600586387 /* test_freebusyquery.py */,
-				353696D1092BB6500075CE69 /* test_icalendar.py */,
-				35069C7D0922B96300389D48 /* test_mkcalendar.py */,
-				35FC85A109ABEC0600586387 /* test_multiget.py */,
-				35069C7F0922B96300389D48 /* test_options.py */,
-				35E8059B0981C33F000981A6 /* test_props.py */,
-				359CD65C0946136A002E3A15 /* test_xml.py */,
-				35069C820922B96300389D48 /* util.py */,
-				35069C2D0922B96300389D48 /* __init__.py */,
-				35069C870922BA1600389D48 /* data */,
-			);
-			path = test;
-			sourceTree = "<group>";
-		};
-		35069CD60922BA8600389D48 /* CalendarServer */ = {
-			isa = PBXGroup;
-			children = (
-				35069C160922B96300389D48 /* twistedcaldav */,
-				35E2B38109C7C2DF00BC8CB9 /* lib-patches */,
-			);
-			name = CalendarServer;
-			path = ..;
-			sourceTree = "<group>";
-		};
-		35069CF50922BACA00389D48 /* Twisted */ = {
-			isa = PBXGroup;
-			children = (
-				3506A2F10922BD2700389D48 /* dav */,
-				3506A2EE0922BD0C00389D48 /* web2 */,
-				35069DDE0922BCCE00389D48 /* twisted */,
-			);
-			name = Twisted;
-			path = ../../Twisted;
-			sourceTree = "<group>";
-		};
-		3506A86B0922BE1500389D48 /* Documentation */ = {
-			isa = PBXGroup;
-			children = (
-				3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */,
-				356E2A500AC3492D00F46D07 /* Repository */,
-				3506A86D0922BE1500389D48 /* RFC */,
-				356E2A480AC3490100F46D07 /* caldavd.8 */,
-				356E2A490AC3490100F46D07 /* twisted.cfg */,
-				356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */,
-			);
-			name = Documentation;
-			path = ../doc;
-			sourceTree = "<group>";
-		};
-		3506A86D0922BE1500389D48 /* RFC */ = {
-			isa = PBXGroup;
-			children = (
-				35B48642095CA1D000AB3411 /* rfc2518.txt */,
-				35B48643095CA1D000AB3411 /* rfc3253.txt */,
-				35B48644095CA1D000AB3411 /* rfc3744.txt */,
-				3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */,
-				3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */,
-			);
-			path = RFC;
-			sourceTree = "<group>";
-		};
-		350892570ABA0A8C00F9995A /* Configuration */ = {
-			isa = PBXGroup;
-			children = (
-				350892800ABA0B8900F9995A /* Development */,
-				3508927F0ABA0B8300F9995A /* Open Directory */,
-				350892810ABA0BB700F9995A /* Static XML */,
-				350892620ABA0AC100F9995A /* repository.dtd */,
-				35CF70B60A0FF59100993B2A /* server.pem */,
-				3508925F0ABA0AC100F9995A /* launchd.plist */,
-			);
-			name = Configuration;
-			path = ../conf;
-			sourceTree = "<group>";
-		};
-		3508927F0ABA0B8300F9995A /* Open Directory */ = {
-			isa = PBXGroup;
-			children = (
-				3508925E0ABA0AC100F9995A /* caldavd.plist */,
-				35CF70B50A0FF59100993B2A /* repository.xml */,
-			);
-			name = "Open Directory";
-			sourceTree = "<group>";
-		};
-		350892800ABA0B8900F9995A /* Development */ = {
-			isa = PBXGroup;
-			children = (
-				3508925D0ABA0AC100F9995A /* caldavd-dev.plist */,
-				350892600ABA0AC100F9995A /* repository-dev.xml */,
-			);
-			name = Development;
-			sourceTree = "<group>";
-		};
-		350892810ABA0BB700F9995A /* Static XML */ = {
-			isa = PBXGroup;
-			children = (
-				350892610ABA0AC100F9995A /* repository-static.xml */,
-			);
-			name = "Static XML";
-			sourceTree = "<group>";
-		};
-		353A63B50994444700A08D28 /* Scripts */ = {
-			isa = PBXGroup;
-			children = (
-				353A63BF0994448C00A08D28 /* caldavd */,
-			);
-			name = Scripts;
-			path = ../bin;
-			sourceTree = "<group>";
-		};
-		356E2A500AC3492D00F46D07 /* Repository */ = {
-			isa = PBXGroup;
-			children = (
-				356E2A510AC3495700F46D07 /* Directory Repository.graffle */,
-				356E2A520AC3495700F46D07 /* XML Repository.graffle */,
-			);
-			path = Repository;
-			sourceTree = "<group>";
-		};
-		35A15ED30985C12C00D404FF /* Support */ = {
-			isa = PBXGroup;
-			children = (
-				35A15ED40985C14800D404FF /* run */,
-				35A15ED60985C14800D404FF /* test */,
-				353A557C099153D900A08D28 /* setup.py */,
-				35FC85A809ABED2B00586387 /* version.py */,
-			);
-			name = Support;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXProject section */
-		35069C0B0922B94100389D48 /* Project object */ = {
-			isa = PBXProject;
-			buildConfigurationList = 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */;
-			compatibilityVersion = "Xcode 2.4";
-			hasScannedForEncodings = 0;
-			mainGroup = 35069C090922B94100389D48;
-			projectDirPath = "";
-			projectRoot = "";
-			shouldCheckCompatibility = 1;
-			targets = (
-			);
-		};
-/* End PBXProject section */
-
-/* Begin XCBuildConfiguration section */
-		35069C0D0922B94100389D48 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				COPY_PHASE_STRIP = NO;
-			};
-			name = Debug;
-		};
-		35069C0E0922B94100389D48 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				COPY_PHASE_STRIP = YES;
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				35069C0D0922B94100389D48 /* Debug */,
-				35069C0E0922B94100389D48 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 35069C0B0922B94100389D48 /* Project object */;
-}

Copied: CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj/project.pbxproj)
===================================================================
--- CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj	                        (rev 0)
+++ CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,329 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 42;
+	objects = {
+
+/* Begin PBXFileReference section */
+		35069C170922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+		35069C190922B96300389D48 /* caldavxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = caldavxml.py; sourceTree = "<group>"; };
+		35069C1B0922B96300389D48 /* index.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = index.py; sourceTree = "<group>"; };
+		35069C1E0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+		35069C200922B96300389D48 /* mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcalendar.py; sourceTree = "<group>"; };
+		35069C220922B96300389D48 /* mkcol.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcol.py; sourceTree = "<group>"; };
+		35069C240922B96300389D48 /* put.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put.py; sourceTree = "<group>"; };
+		35069C260922B96300389D48 /* report_calquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_calquery.py; sourceTree = "<group>"; };
+		35069C2A0922B96300389D48 /* static.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = static.py; sourceTree = "<group>"; };
+		35069C2D0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+		35069C790922B96300389D48 /* test_calendarquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_calendarquery.py; sourceTree = "<group>"; };
+		35069C7B0922B96300389D48 /* test_collectioncontents.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_collectioncontents.py; sourceTree = "<group>"; };
+		35069C7D0922B96300389D48 /* test_mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_mkcalendar.py; sourceTree = "<group>"; };
+		35069C7F0922B96300389D48 /* test_options.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_options.py; sourceTree = "<group>"; };
+		35069C820922B96300389D48 /* util.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = util.py; sourceTree = "<group>"; };
+		35069C840922B96300389D48 /* ical.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = ical.py; sourceTree = "<group>"; };
+		35069C870922BA1600389D48 /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; path = data; sourceTree = "<group>"; };
+		35069DDE0922BCCE00389D48 /* twisted */ = {isa = PBXFileReference; lastKnownFileType = folder; path = twisted; sourceTree = "<group>"; };
+		3506A2EE0922BD0C00389D48 /* web2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = web2; path = twisted/web2; sourceTree = "<group>"; };
+		3506A2F10922BD2700389D48 /* dav */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dav; path = twisted/web2/dav; sourceTree = "<group>"; };
+		3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = wrapper.xcdatamodel; path = CalendarIndex.xcdatamodel; sourceTree = "<group>"; };
+		3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-desruisseaux-caldav-sched.txt"; sourceTree = "<group>"; };
+		3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-dusseault-caldav.txt"; sourceTree = "<group>"; };
+		350781A2096DF5A4004A4366 /* dateops.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = dateops.py; sourceTree = "<group>"; };
+		3508925D0ABA0AC100F9995A /* caldavd-dev.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "caldavd-dev.plist"; sourceTree = "<group>"; };
+		3508925E0ABA0AC100F9995A /* caldavd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = caldavd.plist; sourceTree = "<group>"; };
+		3508925F0ABA0AC100F9995A /* launchd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = launchd.plist; sourceTree = "<group>"; };
+		350892600ABA0AC100F9995A /* repository-dev.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-dev.xml"; sourceTree = "<group>"; };
+		350892610ABA0AC100F9995A /* repository-static.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-static.xml"; sourceTree = "<group>"; };
+		350892620ABA0AC100F9995A /* repository.dtd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.dtd; sourceTree = "<group>"; };
+		3524532E098982D900B9179C /* test_DAV.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_DAV.py; sourceTree = "<group>"; };
+		353696D1092BB6500075CE69 /* test_icalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_icalendar.py; sourceTree = "<group>"; };
+		353A557C099153D900A08D28 /* setup.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = setup.py; path = ../setup.py; sourceTree = "<group>"; };
+		353A63BF0994448C00A08D28 /* caldavd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; path = caldavd; sourceTree = "<group>"; };
+		356E29FC0AC301C900F46D07 /* authkerb.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = authkerb.py; sourceTree = "<group>"; };
+		356E29FE0AC301C900F46D07 /* customxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = customxml.py; sourceTree = "<group>"; };
+		356E29FF0AC301C900F46D07 /* db.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = db.py; sourceTree = "<group>"; };
+		356E2A000AC301C900F46D07 /* directory.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = directory.py; sourceTree = "<group>"; };
+		356E2A010AC301C900F46D07 /* itip.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = itip.py; sourceTree = "<group>"; };
+		356E2A020AC301C900F46D07 /* principalindex.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = principalindex.py; sourceTree = "<group>"; };
+		356E2A440AC3057F00F46D07 /* post.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = post.py; sourceTree = "<group>"; };
+		356E2A450AC3057F00F46D07 /* schedule_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule_common.py; sourceTree = "<group>"; };
+		356E2A480AC3490100F46D07 /* caldavd.8 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = caldavd.8; sourceTree = "<group>"; };
+		356E2A490AC3490100F46D07 /* twisted.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twisted.cfg; sourceTree = "<group>"; };
+		356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twistedcaldav.cfg; sourceTree = "<group>"; };
+		356E2A510AC3495700F46D07 /* Directory Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "Directory Repository.graffle"; sourceTree = "<group>"; };
+		356E2A520AC3495700F46D07 /* XML Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "XML Repository.graffle"; sourceTree = "<group>"; };
+		359CD65C0946136A002E3A15 /* test_xml.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_xml.py; sourceTree = "<group>"; };
+		35A15ED40985C14800D404FF /* run */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = run; path = ../run; sourceTree = "<group>"; };
+		35A15ED60985C14800D404FF /* test */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = test; path = ../test; sourceTree = "<group>"; };
+		35B48642095CA1D000AB3411 /* rfc2518.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc2518.txt; sourceTree = "<group>"; };
+		35B48643095CA1D000AB3411 /* rfc3253.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3253.txt; sourceTree = "<group>"; };
+		35B48644095CA1D000AB3411 /* rfc3744.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3744.txt; sourceTree = "<group>"; };
+		35B71A24097C3A3000E65B22 /* instance.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = instance.py; sourceTree = "<group>"; };
+		35B71A25097C3B2C00E65B22 /* copymove.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = copymove.py; sourceTree = "<group>"; };
+		35B71A26097C3B2C00E65B22 /* delete.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = delete.py; sourceTree = "<group>"; };
+		35B71A27097C3B2C00E65B22 /* report_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_multiget.py; sourceTree = "<group>"; };
+		35B8AA660A0BFE60005547E5 /* http.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = http.py; sourceTree = "<group>"; };
+		35B8AA670A0BFE60005547E5 /* repository.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = repository.py; sourceTree = "<group>"; };
+		35CF70B50A0FF59100993B2A /* repository.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.xml; sourceTree = "<group>"; };
+		35CF70B60A0FF59100993B2A /* server.pem */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = server.pem; sourceTree = "<group>"; };
+		35E2ACFF09BF6D3400BC8CB9 /* logging.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = logging.py; sourceTree = "<group>"; };
+		35E2AF1409C2491600BC8CB9 /* put_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put_common.py; sourceTree = "<group>"; };
+		35E2AF1509C2491600BC8CB9 /* schedule.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule.py; sourceTree = "<group>"; };
+		35E2B38109C7C2DF00BC8CB9 /* lib-patches */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "lib-patches"; sourceTree = "<group>"; };
+		35E8059B0981C33F000981A6 /* test_props.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_props.py; sourceTree = "<group>"; };
+		35F36BAF09B3E8AD00A3D736 /* resource.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = resource.py; sourceTree = "<group>"; };
+		35F36C0E09B4FA6A00A3D736 /* icaldav.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = icaldav.py; sourceTree = "<group>"; };
+		35FC85A009ABEC0600586387 /* test_freebusyquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_freebusyquery.py; sourceTree = "<group>"; };
+		35FC85A109ABEC0600586387 /* test_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_multiget.py; sourceTree = "<group>"; };
+		35FC85A209ABEC9700586387 /* report_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_common.py; sourceTree = "<group>"; };
+		35FC85A309ABEC9700586387 /* report_freebusy.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_freebusy.py; sourceTree = "<group>"; };
+		35FC85A809ABED2B00586387 /* version.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; name = version.py; path = ../twistedcaldav/version.py; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXGroup section */
+		35069C090922B94100389D48 = {
+			isa = PBXGroup;
+			children = (
+				35069CD60922BA8600389D48 /* CalendarServer */,
+				35069CF50922BACA00389D48 /* Twisted */,
+				3506A86B0922BE1500389D48 /* Documentation */,
+				353A63B50994444700A08D28 /* Scripts */,
+				35A15ED30985C12C00D404FF /* Support */,
+				350892570ABA0A8C00F9995A /* Configuration */,
+			);
+			sourceTree = "<group>";
+		};
+		35069C160922B96300389D48 /* twistedcaldav */ = {
+			isa = PBXGroup;
+			children = (
+				35F36C0E09B4FA6A00A3D736 /* icaldav.py */,
+				35F36BAF09B3E8AD00A3D736 /* resource.py */,
+				35069C2A0922B96300389D48 /* static.py */,
+				356E2A000AC301C900F46D07 /* directory.py */,
+				35B8AA660A0BFE60005547E5 /* http.py */,
+				35069C190922B96300389D48 /* caldavxml.py */,
+				356E29FE0AC301C900F46D07 /* customxml.py */,
+				356E29FC0AC301C900F46D07 /* authkerb.py */,
+				35069C840922B96300389D48 /* ical.py */,
+				356E2A010AC301C900F46D07 /* itip.py */,
+				35B71A24097C3A3000E65B22 /* instance.py */,
+				350781A2096DF5A4004A4366 /* dateops.py */,
+				35069C1B0922B96300389D48 /* index.py */,
+				356E2A020AC301C900F46D07 /* principalindex.py */,
+				356E29FF0AC301C900F46D07 /* db.py */,
+				35B8AA670A0BFE60005547E5 /* repository.py */,
+				35E2ACFF09BF6D3400BC8CB9 /* logging.py */,
+				35069C170922B96300389D48 /* __init__.py */,
+				35069C1D0922B96300389D48 /* method */,
+				35069C2C0922B96300389D48 /* test */,
+			);
+			path = twistedcaldav;
+			sourceTree = "<group>";
+		};
+		35069C1D0922B96300389D48 /* method */ = {
+			isa = PBXGroup;
+			children = (
+				356E2A450AC3057F00F46D07 /* schedule_common.py */,
+				35B71A25097C3B2C00E65B22 /* copymove.py */,
+				35B71A26097C3B2C00E65B22 /* delete.py */,
+				35069C200922B96300389D48 /* mkcalendar.py */,
+				35069C220922B96300389D48 /* mkcol.py */,
+				356E2A440AC3057F00F46D07 /* post.py */,
+				35069C240922B96300389D48 /* put.py */,
+				35E2AF1409C2491600BC8CB9 /* put_common.py */,
+				35069C260922B96300389D48 /* report_calquery.py */,
+				35FC85A209ABEC9700586387 /* report_common.py */,
+				35FC85A309ABEC9700586387 /* report_freebusy.py */,
+				35B71A27097C3B2C00E65B22 /* report_multiget.py */,
+				35E2AF1509C2491600BC8CB9 /* schedule.py */,
+				35069C1E0922B96300389D48 /* __init__.py */,
+			);
+			path = method;
+			sourceTree = "<group>";
+		};
+		35069C2C0922B96300389D48 /* test */ = {
+			isa = PBXGroup;
+			children = (
+				3524532E098982D900B9179C /* test_DAV.py */,
+				35069C790922B96300389D48 /* test_calendarquery.py */,
+				35069C7B0922B96300389D48 /* test_collectioncontents.py */,
+				35FC85A009ABEC0600586387 /* test_freebusyquery.py */,
+				353696D1092BB6500075CE69 /* test_icalendar.py */,
+				35069C7D0922B96300389D48 /* test_mkcalendar.py */,
+				35FC85A109ABEC0600586387 /* test_multiget.py */,
+				35069C7F0922B96300389D48 /* test_options.py */,
+				35E8059B0981C33F000981A6 /* test_props.py */,
+				359CD65C0946136A002E3A15 /* test_xml.py */,
+				35069C820922B96300389D48 /* util.py */,
+				35069C2D0922B96300389D48 /* __init__.py */,
+				35069C870922BA1600389D48 /* data */,
+			);
+			path = test;
+			sourceTree = "<group>";
+		};
+		35069CD60922BA8600389D48 /* CalendarServer */ = {
+			isa = PBXGroup;
+			children = (
+				35069C160922B96300389D48 /* twistedcaldav */,
+				35E2B38109C7C2DF00BC8CB9 /* lib-patches */,
+			);
+			name = CalendarServer;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		35069CF50922BACA00389D48 /* Twisted */ = {
+			isa = PBXGroup;
+			children = (
+				3506A2F10922BD2700389D48 /* dav */,
+				3506A2EE0922BD0C00389D48 /* web2 */,
+				35069DDE0922BCCE00389D48 /* twisted */,
+			);
+			name = Twisted;
+			path = ../../Twisted;
+			sourceTree = "<group>";
+		};
+		3506A86B0922BE1500389D48 /* Documentation */ = {
+			isa = PBXGroup;
+			children = (
+				3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */,
+				356E2A500AC3492D00F46D07 /* Repository */,
+				3506A86D0922BE1500389D48 /* RFC */,
+				356E2A480AC3490100F46D07 /* caldavd.8 */,
+				356E2A490AC3490100F46D07 /* twisted.cfg */,
+				356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */,
+			);
+			name = Documentation;
+			path = ../doc;
+			sourceTree = "<group>";
+		};
+		3506A86D0922BE1500389D48 /* RFC */ = {
+			isa = PBXGroup;
+			children = (
+				35B48642095CA1D000AB3411 /* rfc2518.txt */,
+				35B48643095CA1D000AB3411 /* rfc3253.txt */,
+				35B48644095CA1D000AB3411 /* rfc3744.txt */,
+				3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */,
+				3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */,
+			);
+			path = RFC;
+			sourceTree = "<group>";
+		};
+		350892570ABA0A8C00F9995A /* Configuration */ = {
+			isa = PBXGroup;
+			children = (
+				350892800ABA0B8900F9995A /* Development */,
+				3508927F0ABA0B8300F9995A /* Open Directory */,
+				350892810ABA0BB700F9995A /* Static XML */,
+				350892620ABA0AC100F9995A /* repository.dtd */,
+				35CF70B60A0FF59100993B2A /* server.pem */,
+				3508925F0ABA0AC100F9995A /* launchd.plist */,
+			);
+			name = Configuration;
+			path = ../conf;
+			sourceTree = "<group>";
+		};
+		3508927F0ABA0B8300F9995A /* Open Directory */ = {
+			isa = PBXGroup;
+			children = (
+				3508925E0ABA0AC100F9995A /* caldavd.plist */,
+				35CF70B50A0FF59100993B2A /* repository.xml */,
+			);
+			name = "Open Directory";
+			sourceTree = "<group>";
+		};
+		350892800ABA0B8900F9995A /* Development */ = {
+			isa = PBXGroup;
+			children = (
+				3508925D0ABA0AC100F9995A /* caldavd-dev.plist */,
+				350892600ABA0AC100F9995A /* repository-dev.xml */,
+			);
+			name = Development;
+			sourceTree = "<group>";
+		};
+		350892810ABA0BB700F9995A /* Static XML */ = {
+			isa = PBXGroup;
+			children = (
+				350892610ABA0AC100F9995A /* repository-static.xml */,
+			);
+			name = "Static XML";
+			sourceTree = "<group>";
+		};
+		353A63B50994444700A08D28 /* Scripts */ = {
+			isa = PBXGroup;
+			children = (
+				353A63BF0994448C00A08D28 /* caldavd */,
+			);
+			name = Scripts;
+			path = ../bin;
+			sourceTree = "<group>";
+		};
+		356E2A500AC3492D00F46D07 /* Repository */ = {
+			isa = PBXGroup;
+			children = (
+				356E2A510AC3495700F46D07 /* Directory Repository.graffle */,
+				356E2A520AC3495700F46D07 /* XML Repository.graffle */,
+			);
+			path = Repository;
+			sourceTree = "<group>";
+		};
+		35A15ED30985C12C00D404FF /* Support */ = {
+			isa = PBXGroup;
+			children = (
+				35A15ED40985C14800D404FF /* run */,
+				35A15ED60985C14800D404FF /* test */,
+				353A557C099153D900A08D28 /* setup.py */,
+				35FC85A809ABED2B00586387 /* version.py */,
+			);
+			name = Support;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXProject section */
+		35069C0B0922B94100389D48 /* Project object */ = {
+			isa = PBXProject;
+			buildConfigurationList = 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */;
+			compatibilityVersion = "Xcode 2.4";
+			hasScannedForEncodings = 0;
+			mainGroup = 35069C090922B94100389D48;
+			projectDirPath = "";
+			projectRoot = "";
+			shouldCheckCompatibility = 1;
+			targets = (
+			);
+		};
+/* End PBXProject section */
+
+/* Begin XCBuildConfiguration section */
+		35069C0D0922B94100389D48 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COPY_PHASE_STRIP = NO;
+			};
+			name = Debug;
+		};
+		35069C0E0922B94100389D48 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				COPY_PHASE_STRIP = YES;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				35069C0D0922B94100389D48 /* Debug */,
+				35069C0E0922B94100389D48 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 35069C0B0922B94100389D48 /* Project object */;
+}

Modified: CalendarServer/trunk/support/patchmaker.py
===================================================================
--- CalendarServer/trunk/support/patchmaker.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/patchmaker.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -30,7 +30,7 @@
 cwd = os.getcwd()
 libpatches = os.path.join(cwd, "lib-patches")
 
-svn = "/usr/local/subversion/bin/svn"
+svn = "/usr/bin/svn"
 
 # Stuff we have to manually ignore because our ignore logic cannot cope
 ignores = set((

Modified: CalendarServer/trunk/support/submit
===================================================================
--- CalendarServer/trunk/support/submit	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/submit	2006-12-01 19:18:19 UTC (rev 637)
@@ -19,7 +19,7 @@
 ##
 
 ##
-# Submit project to B&I (Apple Internal Build)
+# Submit project to B&I (Apple internal build)
 ##
 
 set -e
@@ -34,6 +34,8 @@
 # Command line
 ##
 
+build=false;
+
 usage ()
 {
   program="$(basename "$0")";
@@ -41,23 +43,29 @@
   if [ "${1-}" != "-" ]; then echo "$1"; echo; fi;
 
   echo "Usage: ${program} release";
+  echo "       ${program} -b";
+  echo "";
+  echo "Options:";
+  echo "	-b Run buildit";
 
   if [ "${1-}" == "-" ]; then return 0; fi;
   exit 64;
 }
 
-while getopts 'h' option; do
+while getopts 'hb' option; do
   case "$option" in
     '?') usage; ;;
     'h') usage -; exit 0; ;;
+    'b') build=true; ;;
   esac;
 done;
 shift $((${OPTIND} - 1));
 
-if [ $# == 0 ]; then usage "No release specified"; fi;
+if ! "${build}"; then
+    if [ $# == 0 ]; then usage "No release specified"; fi;
+    release="$1"; shift;
+fi;
 
-release="$1"; shift;
-
 if [ $# != 0 ]; then usage "Unrecognized arguments:" "$@"; fi;
 
  project="CalendarServer";
@@ -123,8 +131,15 @@
 echo "Preparing sources for ${project_version}...";
 make -C "${wc}" prep;
 
-echo "";
-echo "Submitting sources for ${project_version}...";
-~rc/bin/submitproject "${wc}" "${release}";
+if "${build}"; then
+    echo "";
+    echo "Building ${project_version}...";
+    sudo ~rc/bin/buildit "${wc}" -arch ppc -arch i386;
+else
+    echo "";
+    echo "Submitting sources for ${project_version}...";
+    ~rc/bin/submitproject "${wc}" "${release}";
+fi;
 
+
 rm -rf "${tmp}";

Modified: CalendarServer/trunk/test
===================================================================
--- CalendarServer/trunk/test	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/test	2006-12-01 19:18:19 UTC (rev 637)
@@ -29,7 +29,7 @@
 if [ $# -gt 0 ]; then
     test_modules="$@";
 else
-    test_modules="twistedcaldav.test";
+    test_modules="twistedcaldav";
 fi;
 
 cd "${wd}" && "${twisted}/bin/trial" ${test_modules};

Modified: CalendarServer/trunk/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/__init__.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/__init__.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -36,6 +36,7 @@
     "instance",
     "principalindex",
     "resource",
+    "sql",
     "static",
 ]
 

Modified: CalendarServer/trunk/twistedcaldav/directory/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/__init__.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/__init__.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -26,4 +26,6 @@
     "directory",
     "idirectory",
     "resource",
+    "sqldb",
+    "xmlfile",
 ]

Copied: CalendarServer/trunk/twistedcaldav/directory/apache.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/apache.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/apache.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,198 @@
+##
+# 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
+##
+
+"""
+Apache UserFile/GroupFile compatible directory service implementation.
+"""
+
+__all__ = [
+    "BasicDirectoryService",
+    "DigestDirectoryService",
+]
+
+from crypt import crypt
+
+from twisted.python.filepath import FilePath
+from twisted.cred.credentials import UsernamePassword
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.directory import UnknownRecordTypeError
+
+class AbstractDirectoryService(DirectoryService):
+    """
+    Abstract Apache-compatible implementation of L{IDirectoryService}.
+    """
+    def __repr__(self):
+        return "<%s %r %r>" % (self.__class__.__name__, self.userFile, self.groupFile)
+
+    def __init__(self, userFile, groupFile=None):
+        super(AbstractDirectoryService, self).__init__()
+
+        if type(userFile) is str:
+            userFile = FilePath(userFile)
+        if type(groupFile) is str:
+            groupFile = FilePath(groupFile)
+
+        self.userFile = userFile
+        self.groupFile = groupFile
+
+    def recordTypes(self):
+        recordTypes = ("user",)
+        if self.groupFile is not None:
+            recordTypes += ("group",)
+        return recordTypes
+
+    def listRecords(self, recordType):
+        for entryShortName, entryData in self.entriesForRecordType(recordType):
+            if recordType == "user":
+                yield self.userRecordClass(
+                    service       = self,
+                    recordType    = recordType,
+                    shortName     = entryShortName,
+                    cryptPassword = entryData,
+                )
+
+            elif recordType == "group":
+                yield GroupRecord(
+                    service    = self,
+                    recordType = recordType,
+                    shortName  = entryShortName,
+                    members    = entryData,
+                )
+
+            else:
+                # Subclass should cover the remaining record types
+                raise AssertionError("Unknown record type: %r" % (recordType,))
+
+    def recordWithShortName(self, recordType, shortName):
+        for entryShortName, entryData in self.entriesForRecordType(recordType):
+            if entryShortName == shortName:
+                if recordType == "user":
+                    return self.userRecordClass(
+                        service       = self,
+                        recordType    = recordType,
+                        shortName     = entryShortName,
+                        cryptPassword = entryData,
+                    )
+
+                if recordType == "group":
+                    return GroupRecord(
+                        service    = self,
+                        recordType = recordType,
+                        shortName  = entryShortName,
+                        members    = entryData,
+                    )
+
+                # Subclass should cover the remaining record types
+                raise AssertionError("Unknown record type: %r" % (recordType,))
+
+        return None
+
+    def recordWithGUID(self, guid):
+        raise NotImplementedError()
+
+    def entriesForRecordType(self, recordType):
+        if recordType == "user":
+            recordFile = self.userFile
+        elif recordType == "group":
+            recordFile = self.groupFile
+        else:
+            raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
+
+        if recordFile is None:
+            return
+
+        for entry in recordFile.open():
+            if entry and entry[0] != "#":
+                shortName, rest = entry.rstrip("\n").split(":", 1)
+                yield shortName, rest
+
+class AbstractDirectoryRecord(DirectoryRecord):
+    """
+    Abstract Apache-compatible implementation of L{IDirectoryRecord}.
+    """
+    def __init__(self, service, recordType, shortName):
+        super(AbstractDirectoryRecord, self).__init__(
+            service               = service,
+            recordType            = recordType,
+            guid                  = None,
+            shortName             = shortName,
+            fullName              = None,
+            calendarUserAddresses = (),
+        )
+
+class AbstractUserRecord(AbstractDirectoryRecord):
+    def __init__(self, service, recordType, shortName, cryptPassword=None):
+        super(AbstractUserRecord, self).__init__(service, recordType, shortName)
+
+        self._cryptPassword = cryptPassword
+
+    def groups(self):
+        for group in self.service.listRecords("group"):
+            for member in group.members():
+                if member == self:
+                    yield group
+                    continue
+
+class BasicUserRecord(AbstractUserRecord):
+    """
+    Apache UserFile implementation of L{IDirectoryRecord}.
+    """
+    def verifyCredentials(self, credentials):
+        if self._cryptPassword in ("", "*", "x"):
+            return False
+
+        if isinstance(credentials, UsernamePassword):
+            return crypt(credentials.password, self._cryptPassword) == self._cryptPassword
+
+        return super(BasicUserRecord, self).verifyCredentials(credentials)
+
+class BasicDirectoryService(AbstractDirectoryService):
+    """
+    Apache UserFile/GroupFile implementation of L{IDirectoryService}.
+    """
+    userRecordClass = BasicUserRecord
+
+class DigestUserRecord(AbstractUserRecord):
+    """
+    Apache DigestUserFile implementation of L{IDirectoryRecord}.
+    """
+    def verifyCredentials(self, credentials):
+        raise NotImplementedError()
+
+class DigestDirectoryService(AbstractDirectoryService):
+    """
+    Apache DigestUserFile/GroupFile implementation of L{IDirectoryService}.
+    """
+    userRecordClass = DigestUserRecord
+
+class GroupRecord(AbstractDirectoryRecord):
+    """
+    Apache GroupFile implementation of L{IDirectoryRecord}.
+    """
+    def __init__(self, service, recordType, shortName, members=()):
+        super(GroupRecord, self).__init__(service, recordType, shortName)
+
+        if type(members) is str:
+            members = tuple(m.strip() for m in members.split(","))
+
+        self._members = members
+
+    def members(self):
+        for shortName in self._members:
+            yield self.service.recordWithShortName("user", shortName)

Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -13,101 +13,215 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# DRI: Cyrus Daboo, cdaboo at apple.com
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 """
-Apple Open Directory implementation.
+Apple Open Directory directory service implementation.
 """
 
 __all__ = [
     "OpenDirectoryService",
-    "OpenDirectoryRecord",
+    "OpenDirectoryInitError",
 ]
 
+import sys
+
 import opendirectory
 import dsattributes
 
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.cred.credentials import UsernamePassword
+
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
 
+recordListCacheTimeout = 60 * 5 # 5 minutes
+
 class OpenDirectoryService(DirectoryService):
     """
     Open Directory implementation of L{IDirectoryService}.
     """
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.node)
+
     def __init__(self, node="/Search"):
+        """
+        @param node: an OpenDirectory node name to bind to.
+        """
         directory = opendirectory.odInit(node)
         if directory is None:
-            raise ValueError("Failed to open Open Directory Node: %s" % (node,))
+            raise OpenDirectoryInitError("Failed to open Open Directory Node: %s" % (node,))
 
-        self._directory = directory
+        self.directory = directory
+        self.node = node
+        self._records = {}
 
+    def __cmp__(self, other):
+        if not isinstance(other, DirectoryRecord):
+            return super(DirectoryRecord, self).__eq__(other)
+
+        for attr in ("directory", "node"):
+            diff = cmp(getattr(self, attr), getattr(other, attr))
+            if diff != 0:
+                return diff
+        return 0
+
+    def __hash__(self):
+        h = hash(self.__class__)
+        for attr in ("directory", "node"):
+            h = (h + hash(getattr(self, attr))) & sys.maxint
+        return h
+
     def recordTypes(self):
         return ("user", "group", "resource")
 
-    def listRecords(self, recordType):
-        def makeRecord(shortName, guid, lastModified, principalURI):
-            if not guid:
-                return None
+    def _cacheRecords(self, recordType):
+        if recordType not in self._records:
+            log.msg("Reloading %s record cache" % (recordType,))
 
-            ##
-            # 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.
-            ##
+            if recordType == "user":
+                listRecords = opendirectory.listUsers
+            elif recordType == "group":
+                listRecords = opendirectory.listGroups
+            elif recordType == "resource":
+                listRecords = opendirectory.listResources
+            else:
+                raise UnknownRecordTypeError("Unknown Open Directory record type: %s" % (recordType,))
 
-            return OpenDirectoryRecord(
-                directory = self,
-                recordType = recordType,
-                guid = guid,
-                shortName = shortName,
-                fullName = None,
-            )
+            records = {}
 
-        if recordType == "user":
-            for data in opendirectory.listUsers(self._directory):
-                yield makeRecord(*data)
-            return
+            for shortName, guid, lastModified, principalURI in listRecords(self.directory):
+                if not guid:
+                    continue
 
-        if recordType == "group":
-            for data in opendirectory.listGroups(self._directory):
-                yield makeRecord(*data)
-            return
+                # FIXME: This is a second directory lookup; we should have gotten everything in one pass...
+                if recordType == "group":
+                    result = opendirectory.listGroupsWithAttributes(self.directory, [shortName])
+                    if result is None or shortName not in result:
+                        log.err("Group %s exists and then doesn't." % (shortName,))
+                        continue
+                    result = result[shortName]
 
-        if recordType == "resource":
-            for data in opendirectory.listResources(self._directory):
-                yield makeRecord(*data)
-            return
+                    memberGUIDs = result.get(dsattributes.attrGroupMembers, None)
+                    if memberGUIDs is None:
+                        memberGUIDs = ()
+                    elif type(memberGUIDs) is str:
+                        memberGUIDs = (memberGUIDs,)
+                else:
+                    memberGUIDs = ()
 
-        raise AssertionError("Unknown Open Directory record type: %s" % (recordType,))
+                records[shortName] = OpenDirectoryRecord(
+                    service               = self,
+                    recordType            = recordType,
+                    guid                  = guid,
+                    shortName             = shortName,
+                    fullName              = None, # FIXME: Need to get this attribute
+                    calendarUserAddresses = (), # FIXME: Should be able to look up email, etc.
+                    memberGUIDs           = memberGUIDs,
+                )
 
+            if records:
+                self._records[recordType] = records
+
+                def flush():
+                    log.msg("Flushing %s record cache" % (recordType,))
+                    del self._records[recordType]
+                reactor.callLater(recordListCacheTimeout, flush)
+            else:
+                # records is empty.  This may mean the directory went down.
+                # Don't cache this result, so that we keep checking the directory.
+                return records
+
+        return self._records[recordType]
+
+    def listRecords(self, recordType):
+        return self._cacheRecords(recordType).itervalues()
+
     def recordWithShortName(self, recordType, shortName):
-        if recordType == "user":
-            result = opendirectory.listUsersWithAttributes(self._directory, [shortName])
-            if shortName not in result:
-                return None
-            result = result[shortName]
-        elif recordType == "group":
-            result = opendirectory.groupAttributes(self._directory, shortName)
-        elif recordType == "resource":
-            result = opendirectory.resourceAttributes(self._directory, shortName)
-        else:
-            raise ValueError("Unknown record type: %s" % (recordType,))
+        return self._cacheRecords(recordType).get(shortName, None)
 
-        return OpenDirectoryRecord(
-            directory = self,
-            recordType = recordType,
-            guid = result[dsattributes.attrGUID],
-            shortName = shortName,
-            fullName = result[dsattributes.attrRealName],
-        )
+    def recordWithGUID(self, guid):
+        for recordType in self.recordTypes():
+            for record in self._cacheRecords(recordType).itervalues():
+                if record.guid == guid:
+                    return record
+        return None
 
+#    def recordWithShortName(self, recordType, shortName):
+#        if recordType == "user":
+#            listRecords = opendirectory.listUsersWithAttributes
+#        elif recordType == "group":
+#            listRecords = opendirectory.listGroupsWithAttributes
+#        elif recordType == "resource":
+#            listRecords = opendirectory.listResourcesWithAttributes
+#        else:
+#            raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
+#
+#        result = listRecords(self.directory, [shortName])
+#        if result is None or shortName not in result:
+#            return None
+#        else:
+#            result = result[shortName]
+#
+#        if dsattributes.attrGUID in result:
+#            guid = result[dsattributes.attrGUID]
+#        else:
+#            raise DirectoryError("Found OpenDirectory record %s of type %s with no GUID attribute"
+#                                 % (shortName, recordType))
+#
+#        if dsattributes.attrRealName in result:
+#            fullName = result[dsattributes.attrRealName]
+#        else:
+#            fullName = None
+#
+#        return OpenDirectoryRecord(
+#            service = self,
+#            recordType = recordType,
+#            guid = guid,
+#            shortName = shortName,
+#            fullName = fullName,
+#        )
+
 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)
+    def __init__(self, service, recordType, guid, shortName, fullName, calendarUserAddresses, memberGUIDs):
+        super(OpenDirectoryRecord, self).__init__(
+            service               = service,
+            recordType            = recordType,
+            guid                  = guid,
+            shortName             = shortName,
+            fullName              = fullName,
+            calendarUserAddresses = calendarUserAddresses,
+        )
+        self._memberGUIDs = tuple(memberGUIDs)
 
-        return False
+    def members(self):
+        if self.recordType != "group":
+            return
+
+        for guid in self._memberGUIDs:
+            userRecord = self.service.recordWithGUID(guid)
+            if userRecord is None:
+                log.err("No record for member of group %s with GUID %s" % (self.shortName, guid))
+            else:
+                yield userRecord
+
+    def groups(self):
+        for groupRecord in self.service._cacheRecords("group").itervalues():
+            if self.guid in groupRecord._memberGUIDs:
+                yield groupRecord
+
+    def verifyCredentials(self, credentials):
+        if isinstance(credentials, UsernamePassword):
+            return opendirectory.authenticateUser(self.service.directory, self.shortName, credentials.password)
+
+        return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
+
+class OpenDirectoryInitError(DirectoryError):
+    """
+    OpenDirectory initialization error.
+    """

Deleted: CalendarServer/trunk/twistedcaldav/directory/cred.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/cred.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/cred.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,79 +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__ = [
-    "DirectoryCredentialsChecker",
-]
-
-from twisted.internet.defer import succeed
-from twisted.cred.credentials import UsernamePassword
-from twisted.cred.error import UnauthorizedLogin
-from twisted.web2.dav.auth import IPrincipalCredentials
-from twisted.web2.dav.auth import TwistedPropertyChecker
-
-import opendirectory
-
-from twistedcaldav import customxml
-
-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 UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
-
-#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.userWithShortName(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,)) 

Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# DRI: Cyrus Daboo, cdaboo at apple.com
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 """
@@ -21,25 +21,108 @@
 """
 
 __all__ = [
+    "DirectoryService",
     "DirectoryRecord",
+    "DirectoryError",
+    "UnknownRecordError",
+    "UnknownRecordTypeError",
 ]
 
+import sys
+
 from zope.interface import implements
 
+from twisted.cred.error import UnauthorizedLogin
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.web2.dav.auth import IPrincipalCredentials
+
 from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
 
 class DirectoryService(object):
-    implements(IDirectoryService)
+    implements(IDirectoryService, ICredentialsChecker)
 
+    ##
+    # IDirectoryService
+    ##
+
+    realmName = None
+
+    ##
+    # ICredentialsChecker
+    ##
+
+    # For ICredentialsChecker
+    credentialInterfaces = (IPrincipalCredentials,)
+
+    def requestAvatarId(self, credentials):
+        credentials = IPrincipalCredentials(credentials)
+
+        # FIXME: ?
+        # We were checking if principal is enabled; seems unnecessary in current
+        # implementation because you shouldn't have a principal object for a
+        # disabled directory principal.
+
+        user = self.recordWithShortName("user", credentials.credentials.username)
+        if user is None:
+            raise UnauthorizedLogin("No such user: %s" % (user,))
+
+        if user.verifyCredentials(credentials.credentials):
+            return (credentials.authnPrincipal.principalURL(), credentials.authzPrincipal.principalURL())
+        else:
+            raise UnauthorizedLogin("Incorrect credentials for %s" % (user,)) 
+
 class DirectoryRecord(object):
     implements(IDirectoryRecord)
 
-    def __init__(self, directory, recordType, guid, shortName, fullName=None):
-        self.directory  = directory
-        self.recordType = recordType
-        self.guid       = guid
-        self.shortName  = shortName
-        self.fullName   = fullName
+    def __repr__(self):
+        return "<%s[%s@%s] %s(%s) %r>" % (
+            self.__class__.__name__,
+            self.recordType,
+            self.service,
+            self.guid,
+            self.shortName,
+            self.fullName
+        )
 
-    def authenticate(credentials):
+    def __init__(self, service, recordType, guid, shortName, fullName, calendarUserAddresses):
+        self.service               = service
+        self.recordType            = recordType
+        self.guid                  = guid
+        self.shortName             = shortName
+        self.fullName              = fullName
+        self.calendarUserAddresses = calendarUserAddresses
+
+    def __cmp__(self, other):
+        if not isinstance(other, DirectoryRecord):
+            return NotImplemented
+
+        for attr in ("service", "recordType", "shortName", "guid"):
+            diff = cmp(getattr(self, attr), getattr(other, attr))
+            if diff != 0:
+                return diff
+        return 0
+
+    def __hash__(self):
+        h = hash(self.__class__)
+        for attr in ("service", "recordType", "shortName", "guid"):
+            h = (h + hash(getattr(self, attr))) & sys.maxint
+        return h
+
+    def members(self):
+        return ()
+
+    def groups(self):
+        return ()
+
+    def verifyCredentials(self, credentials):
         return False
+
+class DirectoryError(RuntimeError):
+    """
+    Generic directory error.
+    """
+
+class UnknownRecordTypeError(DirectoryError):
+    """
+    Unknown directory record type.
+    """

Modified: CalendarServer/trunk/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/idirectory.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/idirectory.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -31,6 +31,8 @@
     """
     Directory Service
     """
+    realmName = Attribute("The name of the authentication realm this service represents.")
+
     def recordTypes():
         """
         @return: a sequence of strings denoting the record types that are kept
@@ -48,21 +50,41 @@
         @param recordType: the type of the record to look up.
         @param shortName: the short name of the record to look up.
         @return: an L{IDirectoryRecord} provider with the given short name, or
-            C{None} of no such record exists.
+            C{None} if no such record exists.
         """
 
+    def recordWithGUID(guid):
+        """
+        @param shortName: the GUID of the record to look up.
+        @return: an L{IDirectoryRecord} provider with the given GUID, or C{None}
+            if no such record exists.
+        """
+
 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.")
+    service               = 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("The list of calendar user addresses for this record.")
 
-    def authenticate(credentials):
+    def members():
         """
+        @return: an iterable of L{IDirectoryRecord}s for the members of this
+            (group) record.
+        """
+
+    def group():
+        """
+        @return: an iterable of L{IDirectoryRecord}s for the groups this
+            record is a member of.
+        """
+
+    def verifyCredentials(credentials):
+        """
         Verify that the given credentials can authenticate the principal
         represented by this record.
         @param credentials: the credentials to authenticate with.

Copied: CalendarServer/trunk/twistedcaldav/directory/principal.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/principal.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,350 @@
+##
+# 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
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+    "DirectoryPrincipalProvisioningResource",
+    "DirectoryPrincipalTypeResource",
+    "DirectoryPrincipalResource",
+]
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import succeed
+from twisted.web2 import responsecode
+from twisted.web2.http import Response, HTTPError
+from twisted.web2.http_headers import MimeType
+from twisted.web2.dav import davxml
+from twisted.web2.dav.resource import TwistedACLInheritable
+from twisted.web2.dav.static import DAVFile
+from twisted.web2.dav.util import joinURL
+
+from twistedcaldav.extensions import ReadOnlyResourceMixIn
+from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
+from twistedcaldav.directory.idirectory import IDirectoryService
+
+# FIXME: These should not be tied to DAVFile
+# The reason that they is that web2.dav only implements DAV methods on
+# DAVFile instead of DAVResource.  That should change.
+
+class PermissionsMixIn (ReadOnlyResourceMixIn):
+    def defaultAccessControlList(self):
+        return authReadACL
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        # Permissions here are fixed, and are not subject to inherritance rules, etc.
+        return succeed(self.defaultAccessControlList())
+
+class DirectoryPrincipalProvisioningResource (PermissionsMixIn, CalendarPrincipalCollectionResource, DAVFile):
+    """
+    Collection resource which provisions directory principals as its children.
+    """
+    def __init__(self, path, url, directory):
+        """
+        @param path: the path to the file which will back the resource.
+        @param url: the canonical URL for the resource.
+        @param directory: an L{IDirectoryService} to provision principals from.
+        """
+        assert url.endswith("/"), "Collection URL must end in '/'"
+
+        CalendarPrincipalCollectionResource.__init__(self, url)
+        DAVFile.__init__(self, path)
+
+        self.directory = IDirectoryService(directory)
+
+        # FIXME: Smells like a hack
+        directory.principalCollection = self
+
+        self._provision()
+
+        # Create children
+        for recordType in self.directory.recordTypes():
+            self.putChild(recordType, DirectoryPrincipalTypeResource(self.fp.child(recordType).path, self, recordType))
+
+    def _provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            self.fp.makedirs()
+            self.fp.restat(False)
+
+    def principalForUser(self, user):
+        return self.getChild("user").getChild(user)
+
+    def principalForRecord(self, record):
+        typeResource = self.getChild(record.recordType)
+        if typeResource is None:
+            return None
+        return typeResource.getChild(record.shortName)
+
+    ##
+    # Static
+    ##
+
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    def getChild(self, name):
+        self._provision()
+        return self.putChildren.get(name, None)
+
+    def listChildren(self):
+        return self.putChildren.keys()
+
+    ##
+    # ACL
+    ##
+
+    def principalCollections(self):
+        return (self,)
+
+class DirectoryPrincipalTypeResource (PermissionsMixIn, CalendarPrincipalCollectionResource, DAVFile):
+    """
+    Collection resource which provisions directory principals of a specific type as its children.
+    """
+    def __init__(self, path, parent, recordType):
+        """
+        @param path: the path to the file which will back the resource.
+        @param directory: an L{IDirectoryService} to provision calendars from.
+        @param recordType: the directory record type to provision.
+        """
+        CalendarPrincipalCollectionResource.__init__(self, joinURL(parent.principalCollectionURL(), recordType) + "/")
+        DAVFile.__init__(self, path)
+
+        self.directory = parent.directory
+        self.recordType = recordType
+        self._parent = parent
+
+        self._provision()
+
+    def _provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.makedirs()
+            self.fp.restat(False)
+
+    def principalForUser(self, user):
+        return self._parent.principalForUser(user)
+
+    def principalForRecord(self, record):
+        return self._parent.principalForRecord(record)
+
+    ##
+    # Static
+    ##
+
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    def getChild(self, name, record=None):
+        self._provision()
+
+        if name == "":
+            return self
+
+        if record is None:
+            record = self.directory.recordWithShortName(self.recordType, name)
+            if record is None:
+                return None
+        else:
+            assert name is None
+            name = record.shortName
+
+        return DirectoryPrincipalResource(self.fp.child(name).path, self, record)
+
+    def listChildren(self):
+        return (record.shortName for record in self.directory.listRecords(self.recordType))
+
+    ##
+    # ACL
+    ##
+
+    def principalCollections(self):
+        return self._parent.principalCollections()
+
+class DirectoryPrincipalResource (PermissionsMixIn, CalendarPrincipalResource, DAVFile):
+    """
+    Directory principal resource.
+    """
+    def __init__(self, path, parent, record):
+        """
+        @param path: them path to the file which will back this resource.
+        @param parent: the parent of this resource.
+        @param record: the L{IDirectoryRecord} that this resource represents.
+        """
+        super(DirectoryPrincipalResource, self).__init__(path, joinURL(parent.principalCollectionURL(), record.shortName))
+
+        self.record = record
+        self._parent = parent
+        self._url = joinURL(parent.principalCollectionURL(), record.shortName)
+
+        self._provision()
+
+    def _provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.open("w").close()
+            self.fp.restat(False)
+
+    ##
+    # HTTP
+    ##
+
+    def render(self, request):
+        def format_list(method, *args):
+            def genlist():
+                try:
+                    item = None
+                    for item in method(*args):
+                        yield " -> %s\n" % (item,)
+                    if item is None:
+                        yield " '()\n"
+                except Exception, e:
+                    log.err("Exception while rendering: %s" % (e,))
+                    Failure().printTraceback()
+                    yield "  ** %s **: %s\n" % (e.__class__.__name__, e)
+            return "".join(genlist())
+
+        output = ("".join((
+            "Principal resource\n"
+            "------------------\n"
+            "\n"
+           #"Directory service: %s\n"      % (self.record.service,),
+            "Record type: %s\n"            % (self.record.recordType,),
+            "GUID: %s\n"                   % (self.record.guid,),
+            "Short name: %s\n"             % (self.record.shortName,),
+            "Full name: %s\n"              % (self.record.fullName,),
+            "Principal UID: %s\n"          % (self.principalUID(),),
+            "Principal URL: %s\n"          % (self.principalURL(),),
+            "\nAlternate URIs:\n"          , format_list(self.alternateURIs),
+            "\nGroup members:\n"           , format_list(self.groupMembers),
+            "\nGroup memberships:\n"       , format_list(self.groupMemberships),
+            "\nCalendar homes:\n"          , format_list(self.calendarHomeURLs),
+            "\nCalendar user addresses:\n" , format_list(self.calendarUserAddresses),
+        )))
+
+        if type(output) == unicode:
+            output = output.encode("utf-8")
+            mime_params = {"charset": "utf-8"}
+        else:
+            mime_params = {}
+
+        response = Response(code=responsecode.OK, stream=output)
+        response.headers.setHeader("content-type", MimeType("text", "plain", mime_params))
+
+        return response
+
+    ##
+    # DAV
+    ##
+
+    def displayName(self):
+        if self.record.fullName:
+            return self.record.fullName
+        else:
+            return self.record.shortName
+
+    ##
+    # ACL
+    ##
+
+    def alternateURIs(self):
+        # FIXME: Add API to IDirectoryRecord for getting a record URI?
+        return ()
+
+    def principalURL(self):
+        return self._url
+
+    def _getRelatives(self, method, record=None, relatives=None, records=None):
+        if record is None:
+            record = self.record
+        if relatives is None:
+            relatives = set()
+        if records is None:
+            records = set()
+
+        if record not in records:
+            records.add(record)
+            myRecordType = self.record.recordType
+            for relative in getattr(record, method)():
+                if relative not in records:
+                    if relative.recordType == myRecordType: 
+                        relatives.add(self._parent.getChild(None, record=relative))
+                    else:
+                        relatives.add(self._parent._parent.getChild(relative.recordType).getChild(None, record=relative))
+                    self._getRelatives(method, relative, relatives, records)
+
+        return relatives
+
+    def groupMembers(self):
+        return self._getRelatives("members")
+
+    def groupMemberships(self):
+        return self._getRelatives("groups")
+
+    def principalCollections(self):
+        return self._parent.principalCollections()
+
+    ##
+    # CalDAV
+    ##
+
+    def principalUID(self):
+        return self.record.shortName
+        
+    def calendarUserAddresses(self):
+        # Add the principal URL to whatever calendar user addresses
+        # the directory record provides.
+        return (self.principalURL(),) + tuple(self.record.calendarUserAddresses)
+
+    def calendarHomeURLs(self):
+        # FIXME: self.record.service.calendarHomesCollection smells like a hack
+        # See CalendarHomeProvisioningFile.__init__()
+        service = self.record.service
+        if hasattr(service, "calendarHomesCollection"):
+            return (service.calendarHomesCollection.homeForDirectoryRecord(self.record).url(),)
+        else:
+            return ()
+
+    def scheduleInboxURL(self):
+        return self._homeChildURL("inbox/")
+
+    def scheduleOutboxURL(self):
+        return self._homeChildURL("outbox/")
+
+    def _homeChildURL(self, name):
+        homes = self.calendarHomeURLs()
+        if homes:
+            return joinURL(homes[0], name)
+        else:
+            return None
+
+authReadACL = davxml.ACL(
+    # Read access for authenticated users.
+    davxml.ACE(
+        davxml.Principal(davxml.Authenticated()),
+        davxml.Grant(davxml.Privilege(davxml.Read())),
+        davxml.Protected(),
+    ),
+)

Deleted: CalendarServer/trunk/twistedcaldav/directory/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/resource.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,985 +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 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)
-            if new_entries is not None:
-                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, None, ["calendar"],
-                                               self.type == DirectoryTypePrincipalProvisioningResource.typeResource, 
-                                               self.type == DirectoryTypePrincipalProvisioningResource.typeUser)
-        
-        # 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()))
-    
-    @staticmethod
-    def findAnyGUID(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 CalendarPrincipalCollectionResource.principleCollectionSet.keys():
-            try:
-                pcollection = CalendarPrincipalCollectionResource.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
-
-    @staticmethod
-    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 CalendarPrincipalCollectionResource.principleCollectionSet.keys():
-            try:
-                pcollection = CalendarPrincipalCollectionResource.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()
-        )

Copied: CalendarServer/trunk/twistedcaldav/directory/sqldb.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sqldb.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/sqldb.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,331 @@
+##
+# 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
+##
+
+
+"""
+SQL (sqlite) based user/group/resource directory service implementation.
+"""
+
+"""
+SCHEMA:
+
+User Database:
+
+ROW: TYPE, UID (unique), PSWD, NAME, CANPROXY
+
+Group Database:
+
+ROW: GRPUID, UID
+
+CUAddress database:
+
+ROW: CUADDR (unqiue), UID
+
+"""
+
+__all__ = [
+    "SQLDirectoryService",
+]
+
+from twisted.cred.credentials import UsernamePassword
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+from twistedcaldav.sql import AbstractSQLDatabase
+
+import os
+
+class SQLDirectoryManager(AbstractSQLDatabase):
+    """
+    House keeping operations on the SQL DB, including loading from XML file,
+    and record dumping. This can be used as a standalong DB management tool.
+    """
+
+    DBTYPE = "DIRECTORYSERVICE"
+    DBNAME = ".db.accounts"
+    DBVERSION = "1"
+    ACCOUNTDB = "ACCOUNTS"
+    GROUPSDB = "GROUPS"
+    CUADDRDB = "CUADDRS"
+
+    def __init__(self, path):
+        path = os.path.join(path, SQLDirectoryManager.DBNAME)
+        super(SQLDirectoryManager, self).__init__(path, SQLDirectoryManager.DBVERSION)
+
+    def loadFromXML(self, xmlFile):
+       xmlAccounts = XMLAccountsParser(xmlFile)
+       
+       # Totally wipe existing DB and start from scratch
+       if os.path.exists(self.dbpath):
+           os.remove(self.dbpath)
+
+       # Now add records to db
+       for item in xmlAccounts.items.itervalues():
+           self._add_to_db(item)
+       self._db_commit()
+
+    def listRecords(self, recordType):
+        # Get each account record
+        rowiter = self._db_execute("select UID, PSWD, NAME from ACCOUNTS where TYPE = :1", recordType)
+        for row in rowiter:
+            uid = row[0]
+            password = row[1]
+            name = row[2]
+            members = []
+            groups = []
+            calendarUserAddresses = []
+    
+            # See if we have a group
+            if recordType == "group":
+                rowiter = self._db_execute("select UID from GROUPS where GRPUID = :1", uid)
+                for row in rowiter:
+                    members.append(row[0])
+                
+            # See if we are a member of a group
+            rowiter = self._db_execute("select GRPUID from GROUPS where UID = :1", uid)
+            for row in rowiter:
+                groups.append(row[0])
+                
+            # Get calendar user addresses
+            rowiter = self._db_execute("select CUADDR from CUADDRS where UID = :1", uid)
+            for row in rowiter:
+                calendarUserAddresses.append(row[0])
+                
+            yield uid, password, name, members, groups, calendarUserAddresses
+
+    def getRecord(self, recordType, uid):
+        # Get individual account record
+        rowiter = self._db_execute("select UID, PSWD, NAME from ACCOUNTS where TYPE = :1 and UID = :2", recordType, uid)
+        result = None
+        for row in rowiter:
+            if result:
+                result = None
+                break
+            result = row
+
+        if result is None:
+            return None
+        
+        uid = result[0]
+        password = result[1]
+        name = result[2]
+        members = []
+        groups = []
+        calendarUserAddresses = []
+
+        # See if we have a group
+        if recordType == "group":
+            rowiter = self._db_execute("select UID from GROUPS where GRPUID = :1", uid)
+            for row in rowiter:
+                members.append(row[0])
+            
+        # See if we are a member of a group
+        rowiter = self._db_execute("select GRPUID from GROUPS where UID = :1", uid)
+        for row in rowiter:
+            groups.append(row[0])
+            
+        # Get calendar user addresses
+        rowiter = self._db_execute("select CUADDR from CUADDRS where UID = :1", uid)
+        for row in rowiter:
+            calendarUserAddresses.append(row[0])
+            
+        return uid, password, name, members, groups, calendarUserAddresses
+            
+    def _add_to_db(self, record):
+        # Do regular account entry
+        type = record.recordType
+        uid = record.uid
+        password = record.password
+        name = record.name
+        canproxy = ('F', 'T')[record.canproxy]
+        self._db_execute(
+            """
+            insert into ACCOUNTS (TYPE, UID, PSWD, NAME, CANPROXY)
+            values (:1, :2, :3, :4, :5)
+            """, type, uid, password, name, canproxy
+        )
+        
+        # Check for group
+        if type == "group":
+            for member in record.members:
+                self._db_execute(
+                    """
+                    insert into GROUPS (GRPUID, UID)
+                    values (:1, :2)
+                    """, uid, member
+                )
+                
+        # CUAddress
+        for cuaddr in record.calendarUserAddresses:
+            self._db_execute(
+                """
+                insert into CUADDRS (CUADDR, UID)
+                values (:1, :2)
+                """, cuaddr, uid
+            )
+       
+    def _delete_from_db(self, uid):
+        """
+        Deletes the specified entry from all dbs.
+        @param name: the name of the resource to delete.
+        @param uid: the uid of the resource to delete.
+        """
+        self._db_execute("delete from ACCOUNTS where UID = :1", uid)
+        self._db_execute("delete from GROUPS where GRPUID = :1", uid)
+        self._db_execute("delete from GROUPS where UID = :1", uid)
+        self._db_execute("delete from CUADDRS where UID = :1", uid)
+    
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return SQLDirectoryManager.DBTYPE
+        
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+        #
+        # ACCOUNTS table
+        #
+        q.execute(
+            """
+            create table ACCOUNTS (
+                TYPE           text,
+                UID            text unique,
+                PSWD           text,
+                NAME           text,
+                CANPROXY       text(1)
+            )
+            """
+        )
+
+        #
+        # GROUPS table
+        #
+        q.execute(
+            """
+            create table GROUPS (
+                GRPUID     text,
+                UID        text
+            )
+            """
+        )
+
+        #
+        # CUADDRS table
+        #
+        q.execute(
+            """
+            create table CUADDRS (
+                CUADDR         text unique,
+                UID            text
+            )
+            """
+        )
+
+class SQLDirectoryService(DirectoryService):
+    """
+    XML based implementation of L{IDirectoryService}.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, dbParentPath, xmlFile = None):
+        super(SQLDirectoryService, self).__init__()
+
+        if type(dbParentPath) is str:
+            dbParentPath = FilePath(dbParentPath)
+            
+        self.manager = SQLDirectoryManager(dbParentPath.path)
+        if xmlFile:
+            self.manager.loadFromXML(xmlFile)
+
+    def recordTypes(self):
+        recordTypes = ("user", "group", "resource")
+        return recordTypes
+
+    def listRecords(self, recordType):
+        for result in self.manager.listRecords(recordType):
+            yield SQLDirectoryRecord(
+                service               = self,
+                recordType            = recordType,
+                shortName             = result[0],
+                password              = result[1],
+                name                  = result[2],
+                members               = result[3],
+                groups                = result[4],
+                calendarUserAddresses = result[5],
+            )
+
+    def recordWithShortName(self, recordType, shortName):
+        result = self.manager.getRecord(recordType, shortName)
+        if result:
+            return SQLDirectoryRecord(
+                service               = self,
+                recordType            = recordType,
+                shortName             = result[0],
+                password              = result[1],
+                name                  = result[2],
+                members               = result[3],
+                groups                = result[4],
+                calendarUserAddresses = result[5],
+            )
+
+        return None
+
+    def recordWithGUID(self, guid):
+        raise NotImplementedError()
+
+class SQLDirectoryRecord(DirectoryRecord):
+    """
+    XML based implementation implementation of L{IDirectoryRecord}.
+    """
+    def __init__(self, service, recordType, shortName, password, name, members, groups, calendarUserAddresses):
+        super(SQLDirectoryRecord, self).__init__(
+            service               = service,
+            recordType            = recordType,
+            guid                  = None,
+            shortName             = shortName,
+            fullName              = name,
+            calendarUserAddresses = calendarUserAddresses,
+        )
+
+        self.password = password
+        self._members = members
+        self._groups  = groups
+
+    def members(self):
+        for shortName in self._members:
+            yield self.service.recordWithShortName("user", shortName)
+
+    def groups(self):
+        for shortName in self._groups:
+            yield self.service.recordWithShortName("group", shortName)
+
+    def verifyCredentials(self, credentials):
+        if isinstance(credentials, UsernamePassword):
+            return credentials.password == self.password
+
+        return super(SQLDirectoryRecord, self).verifyCredentials(credentials)
+
+if __name__ == '__main__':
+    mgr = SQLDirectoryManager("./")
+    mgr.loadFromXML("test/accounts.xml")

Copied: CalendarServer/trunk/twistedcaldav/directory/test (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test)

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/__init__.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,17 +0,0 @@
-##
-# Copyright (c) 2005-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
-##

Copied: CalendarServer/trunk/twistedcaldav/directory/test/__init__.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/__init__.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,17 @@
+##
+# Copyright (c) 2005-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
+##

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,39 +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
- -->
-
-<!ELEMENT accounts (user*, group*, resource*) >
-
-  <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
-    <!ATTLIST user repeat CDATA "1">
-
-  <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
-    <!ATTLIST group repeat CDATA "1">
-
-  <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
-    <!ATTLIST resource repeat CDATA "1">
-
-    <!ELEMENT uid         (#PCDATA)>
-    <!ELEMENT pswd        (#PCDATA)>
-    <!ELEMENT name        (#PCDATA)>
-    <!ELEMENT cuaddr      (#PCDATA)>
-    <!ELEMENT calendar    (#PCDATA)>
-    <!ELEMENT quota       (#PCDATA)>
-    <!ELEMENT autorespond EMPTY>
-    <!ELEMENT canproxy    EMPTY>
-    <!ELEMENT members     (uid*)>
-    
\ No newline at end of file

Copied: CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,39 @@
+<!--
+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
+ -->
+
+<!ELEMENT accounts (user*, group*, resource*) >
+
+  <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+    <!ATTLIST user repeat CDATA "1">
+
+  <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
+    <!ATTLIST group repeat CDATA "1">
+
+  <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+    <!ATTLIST resource repeat CDATA "1">
+
+    <!ELEMENT uid         (#PCDATA)>
+    <!ELEMENT pswd        (#PCDATA)>
+    <!ELEMENT name        (#PCDATA)>
+    <!ELEMENT cuaddr      (#PCDATA)>
+    <!ELEMENT calendar    (#PCDATA)>
+    <!ELEMENT quota       (#PCDATA)>
+    <!ELEMENT autorespond EMPTY>
+    <!ELEMENT canproxy    EMPTY>
+    <!ELEMENT members     (uid*)>
+    
\ No newline at end of file

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-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.
- -->
-
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-
-<accounts realm="Test Realm">
-  <user>
-    <uid>admin</uid>
-    <pswd>nimda</pswd>
-    <name>Super User</name>
-  </user>
-  <user>
-    <uid>proxy</uid>
-    <pswd>yxorp</pswd>
-    <name>User who can authorize as someone else</name>
-    <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
-  </user>
-  <user>
-    <uid>wsanchez</uid>
-    <pswd>zehcnasw</pswd>
-    <name>Wilfredo Sanchez</name>
-    <cuaddr>mailto:wsanchez at example.com</cuaddr>
-  </user>
-  <user>
-    <uid>cdaboo</uid>
-    <pswd>oobadc</pswd>
-    <name>Cyrus Daboo</name>
-    <cuaddr>mailto:cdaboo at example.com</cuaddr>
-  </user>
-  <user>
-    <uid>lecroy</uid>
-    <pswd>yorcel</pswd>
-    <name>Chris Lecroy</name>
-    <cuaddr>mailto:lecroy at example.com</cuaddr>
-  </user>
-  <user>
-    <uid>dreid</uid>
-    <pswd>dierd</pswd>
-    <name>David Reid</name>
-    <cuaddr>mailto:dreid at example.com</cuaddr>
-  </user>
-  <user repeat="2">
-    <uid>user%02d</uid>
-    <pswd>%02duser</pswd>
-    <name>User %02d</name>
-  </user>
-  <group>
-    <uid>managers</uid>
-    <pswd>managers</pswd>
-    <name>Managers</name>
-    <members>
-      <uid>lecroy</uid>
-    </members>
-  </group>
-  <group>
-    <uid>grunts</uid>
-    <pswd>grunts</pswd>
-    <name>We do all the work</name>
-    <members>
-      <uid>wsanchez</uid>
-      <uid>cdaboo</uid>
-      <uid>dreid</uid>
-    </members>
-  </group>
-  <group>
-    <uid>right_coast</uid>
-    <pswd>right_coast</pswd>
-    <name>East Coast</name>
-    <members>
-      <uid>cdaboo</uid>
-    </members>
-  </group>
-  <group>
-    <uid>left_coast</uid>
-    <pswd>left_coast</pswd>
-    <name>West Coast</name>
-    <members>
-      <uid>wsanchez</uid>
-      <uid>lecroy</uid>
-      <uid>dreid</uid>
-    </members>
-  </group>
-  <resource>
-    <uid>mercury</uid>
-    <pswd>mercury</pswd>
-    <name>Mecury Seven</name>
-    <cuaddr>mailto:mercury at example.com</cuaddr>
-  </resource>
-  <resource>
-    <uid>gemini</uid>
-    <pswd>gemini</pswd>
-    <name>Gemini Twelve</name>
-    <cuaddr>mailto:gemini at example.com</cuaddr>
-  </resource>
-  <resource>
-    <uid>apollo</uid>
-    <pswd>apollo</pswd>
-    <name>Apollo Eleven</name>
-    <cuaddr>mailto:apollo at example.com</cuaddr>
-  </resource>
-</accounts>

Copied: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <user>
+    <uid>admin</uid>
+    <pswd>nimda</pswd>
+    <name>Super User</name>
+  </user>
+  <user>
+    <uid>proxy</uid>
+    <pswd>yxorp</pswd>
+    <name>User who can authorize as someone else</name>
+    <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
+  </user>
+  <user>
+    <uid>wsanchez</uid>
+    <pswd>zehcnasw</pswd>
+    <name>Wilfredo Sanchez</name>
+    <cuaddr>mailto:wsanchez at example.com</cuaddr>
+  </user>
+  <user>
+    <uid>cdaboo</uid>
+    <pswd>oobadc</pswd>
+    <name>Cyrus Daboo</name>
+    <cuaddr>mailto:cdaboo at example.com</cuaddr>
+  </user>
+  <user>
+    <uid>lecroy</uid>
+    <pswd>yorcel</pswd>
+    <name>Chris Lecroy</name>
+    <cuaddr>mailto:lecroy at example.com</cuaddr>
+  </user>
+  <user>
+    <uid>dreid</uid>
+    <pswd>dierd</pswd>
+    <name>David Reid</name>
+    <cuaddr>mailto:dreid at example.com</cuaddr>
+  </user>
+  <user repeat="2">
+    <uid>user%02d</uid>
+    <pswd>%02duser</pswd>
+    <name>User %02d</name>
+  </user>
+  <group>
+    <uid>managers</uid>
+    <pswd>managers</pswd>
+    <name>Managers</name>
+    <members>
+      <uid>lecroy</uid>
+    </members>
+  </group>
+  <group>
+    <uid>grunts</uid>
+    <pswd>grunts</pswd>
+    <name>We do all the work</name>
+    <members>
+      <uid>wsanchez</uid>
+      <uid>cdaboo</uid>
+      <uid>dreid</uid>
+    </members>
+  </group>
+  <group>
+    <uid>right_coast</uid>
+    <pswd>right_coast</pswd>
+    <name>East Coast</name>
+    <members>
+      <uid>cdaboo</uid>
+    </members>
+  </group>
+  <group>
+    <uid>left_coast</uid>
+    <pswd>left_coast</pswd>
+    <name>West Coast</name>
+    <members>
+      <uid>wsanchez</uid>
+      <uid>lecroy</uid>
+      <uid>dreid</uid>
+    </members>
+  </group>
+  <resource>
+    <uid>mercury</uid>
+    <pswd>mercury</pswd>
+    <name>Mecury Seven</name>
+    <cuaddr>mailto:mercury at example.com</cuaddr>
+  </resource>
+  <resource>
+    <uid>gemini</uid>
+    <pswd>gemini</pswd>
+    <name>Gemini Twelve</name>
+    <cuaddr>mailto:gemini at example.com</cuaddr>
+  </resource>
+  <resource>
+    <uid>apollo</uid>
+    <pswd>apollo</pswd>
+    <name>Apollo Eleven</name>
+    <cuaddr>mailto:apollo at example.com</cuaddr>
+  </resource>
+</accounts>

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/basic
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/basic	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-wsanchez:Cytm0Bwm7CPJs
-cdaboo:I.Ef5FJl5GVh2
-dreid:LVhqAv4qSrYPs
-lecroy:/7/5VDrkrLxY.

Copied: CalendarServer/trunk/twistedcaldav/directory/test/basic (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/basic	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/basic	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+wsanchez:Cytm0Bwm7CPJs
+cdaboo:I.Ef5FJl5GVh2
+dreid:LVhqAv4qSrYPs
+lecroy:/7/5VDrkrLxY.

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/digest
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/digest	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
-cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
-dreid:Test:8ee67801004b2752f72b84e7064889a6
-lecroy:Test:60d4feb424430953be045738041e51be

Copied: CalendarServer/trunk/twistedcaldav/directory/test/digest (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/digest	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/digest	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
+cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
+dreid:Test:8ee67801004b2752f72b84e7064889a6
+lecroy:Test:60d4feb424430953be045738041e51be

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/groups
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/groups	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-managers: lecroy
-grunts: wsanchez, cdaboo, dreid
-right_coast: cdaboo
-left_coast: wsanchez, dreid, lecroy

Copied: CalendarServer/trunk/twistedcaldav/directory/test/groups (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/groups	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/groups	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+managers: lecroy
+grunts: wsanchez, cdaboo, dreid
+right_coast: cdaboo
+left_coast: wsanchez, dreid, lecroy

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,106 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-
-digestRealm = "Test"
-
-basicUserFile  = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
-digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
-groupFile      = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class Apache (object):
-    recordTypes = set(("user", "group"))
-
-    users = {
-        "wsanchez": "foo",
-        "cdaboo"  : "bar",
-        "dreid"   : "baz",
-        "lecroy"  : "quux",
-    }
-
-    groups = {
-        "managers"   : ("lecroy",),
-        "grunts"     : ("wsanchez", "cdaboo", "dreid"),
-        "right_coast": ("cdaboo",),
-        "left_coast" : ("wsanchez", "dreid", "lecroy"),
-    }
-
-    serviceClass = BasicDirectoryService
-
-    def service(self):
-        return self.serviceClass(self.userFile(), self.groupFile())
-
-    userFileName = None
-
-    def userFile(self):
-        if not hasattr(self, "_userFile"):
-            if self.userFileName is None:
-                raise NotImplementedError("Test subclass needs to specify userFileName.")
-            self._userFile = FilePath(self.mktemp())
-            basicUserFile.copyTo(self._userFile)
-        return self._userFile
-
-    def groupFile(self):
-        if not hasattr(self, "_groupFile"):
-            self._groupFile = FilePath(self.mktemp())
-            groupFile.copyTo(self._groupFile)
-        return self._groupFile
-
-    def test_changedGroupFile(self):
-        self.groupFile().open("w").write("grunts: wsanchez\n")
-        self.assertEquals(self.recordNames("group"), set(("grunts",)))
-
-    def test_recordTypes_user(self):
-        """
-        IDirectoryService.recordTypes(userFile)
-        """
-        self.assertEquals(set(self.serviceClass(self.userFile()).recordTypes()), set(("user",)))
-
-    userEntry = None
-
-    def test_changedUserFile(self):
-        if self.userEntry is None:
-            raise NotImplementedError("Test subclass needs to specify userEntry.")
-        self.userFile().open("w").write(self.userEntry[1])
-        self.assertEquals(self.recordNames("user"), set((self.userEntry[0],)))
-
-class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase):
-    """
-    Test Apache-Compatible UserFile/GroupFile directory implementation.
-    """
-    userFileName = basicUserFile
-    userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
-
-class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase):
-    """
-    Test Apache-Compatible DigestFile/GroupFile directory implementation.
-    """
-    userFileName = digestUserFile
-    userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
-
-    def test_verifyCredentials_digest(self):
-        raise NotImplementedError() # Use super's implementation
-    test_verifyCredentials_digest.todo = "unimplemented"

Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,106 @@
+##
+# Copyright (c) 2005-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
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+
+digestRealm = "Test"
+
+basicUserFile  = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
+digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
+groupFile      = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class Apache (object):
+    recordTypes = set(("user", "group"))
+
+    users = {
+        "wsanchez": "foo",
+        "cdaboo"  : "bar",
+        "dreid"   : "baz",
+        "lecroy"  : "quux",
+    }
+
+    groups = {
+        "managers"   : ("lecroy",),
+        "grunts"     : ("wsanchez", "cdaboo", "dreid"),
+        "right_coast": ("cdaboo",),
+        "left_coast" : ("wsanchez", "dreid", "lecroy"),
+    }
+
+    serviceClass = BasicDirectoryService
+
+    def service(self):
+        return self.serviceClass(self.userFile(), self.groupFile())
+
+    userFileName = None
+
+    def userFile(self):
+        if not hasattr(self, "_userFile"):
+            if self.userFileName is None:
+                raise NotImplementedError("Test subclass needs to specify userFileName.")
+            self._userFile = FilePath(self.mktemp())
+            basicUserFile.copyTo(self._userFile)
+        return self._userFile
+
+    def groupFile(self):
+        if not hasattr(self, "_groupFile"):
+            self._groupFile = FilePath(self.mktemp())
+            groupFile.copyTo(self._groupFile)
+        return self._groupFile
+
+    def test_changedGroupFile(self):
+        self.groupFile().open("w").write("grunts: wsanchez\n")
+        self.assertEquals(self.recordNames("group"), set(("grunts",)))
+
+    def test_recordTypes_user(self):
+        """
+        IDirectoryService.recordTypes(userFile)
+        """
+        self.assertEquals(set(self.serviceClass(self.userFile()).recordTypes()), set(("user",)))
+
+    userEntry = None
+
+    def test_changedUserFile(self):
+        if self.userEntry is None:
+            raise NotImplementedError("Test subclass needs to specify userEntry.")
+        self.userFile().open("w").write(self.userEntry[1])
+        self.assertEquals(self.recordNames("user"), set((self.userEntry[0],)))
+
+class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase):
+    """
+    Test Apache-Compatible UserFile/GroupFile directory implementation.
+    """
+    userFileName = basicUserFile
+    userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
+
+class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase):
+    """
+    Test Apache-Compatible DigestFile/GroupFile directory implementation.
+    """
+    userFileName = digestUserFile
+    userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
+
+    def test_verifyCredentials_digest(self):
+        raise NotImplementedError() # Use super's implementation
+    test_verifyCredentials_digest.todo = "unimplemented"

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_opendirectory.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,44 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-try:
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-except ImportError:
-    pass
-else:
-    import twistedcaldav.directory.test.util
-
-    # Wonky hack to prevent unclean reactor shutdowns
-    class DummyReactor(object):
-        @staticmethod
-        def callLater(*args):
-            pass
-    import twistedcaldav.directory.appleopendirectory
-    twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
-
-    class OpenDirectory (
-        twistedcaldav.directory.test.util.BasicTestCase,
-        twistedcaldav.directory.test.util.DigestTestCase
-    ):
-        """
-        Test Open Directory directory implementation.
-        """
-        recordTypes = set(("user", "group", "resource"))
-
-        def service(self):
-            return OpenDirectoryService(node="/Local")

Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_opendirectory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2005-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
+##
+
+try:
+    from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
+except ImportError:
+    pass
+else:
+    import twistedcaldav.directory.test.util
+
+    # Wonky hack to prevent unclean reactor shutdowns
+    class DummyReactor(object):
+        @staticmethod
+        def callLater(*args):
+            pass
+    import twistedcaldav.directory.appleopendirectory
+    twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
+
+    class OpenDirectory (
+        twistedcaldav.directory.test.util.BasicTestCase,
+        twistedcaldav.directory.test.util.DigestTestCase
+    ):
+        """
+        Test Open Directory directory implementation.
+        """
+        recordTypes = set(("user", "group", "resource"))
+
+        def service(self):
+            return OpenDirectoryService(node="/Local")

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_principal.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,326 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-#from twisted.web2 import responsecode
-#from twisted.web2.iweb import IResponse
-#from twisted.web2.dav import davxml
-#from twisted.web2.dav.util import davXMLFromStream
-#from twisted.web2.test.test_server import SimpleRequest
-#from twistedcaldav import caldavxml
-
-import os
-
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.test.test_server import SimpleRequest
-from twisted.web2.dav.test.util import serialize
-
-from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.principal import DirectoryPrincipalTypeResource
-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-
-import twistedcaldav.test.util
-
-directoryServices = (
-    BasicDirectoryService(basicUserFile, groupFile),
-    DigestDirectoryService(digestUserFile, groupFile),
-    XMLDirectoryService(xmlFile),
-)
-
-class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
-    """
-    Directory service provisioned principals.
-    """
-    def setUp(self):
-        super(ProvisionedPrincipals, self).setUp()
-        
-        # Set up a principals hierarchy for each service we're testing with
-        self.principalRootResources = {}
-        for directory in directoryServices:
-            name = directory.__class__.__name__
-            url = "/" + name + "/"
-            path = os.path.join(self.docroot, url[1:])
-
-            if os.path.exists(path):
-                rmdir(path)
-            os.mkdir(path)
-
-            provisioningResource = DirectoryPrincipalProvisioningResource(path, url, directory)
-
-            self.site.resource.putChild(name, provisioningResource)
-
-            self.principalRootResources[directory.__class__.__name__] = provisioningResource
-
-    def test_hierarchy(self):
-        """
-        DirectoryPrincipalProvisioningResource.listChildren(),
-        DirectoryPrincipalProvisioningResource.getChildren(),
-        DirectoryPrincipalProvisioningResource.principalCollectionURL(),
-        DirectoryPrincipalProvisioningResource.principalCollections()
-
-        DirectoryPrincipalTypeResource.listChildren(),
-        DirectoryPrincipalTypeResource.getChildren(),
-        DirectoryPrincipalTypeResource.principalCollectionURL(),
-        DirectoryPrincipalTypeResource.principalCollections()
-
-        DirectoryPrincipalResource.principalURL(),
-        """
-        for directory in directoryServices:
-            #print "\n -> %s" % (directory.__class__.__name__,)
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
-            provisioningURL = "/" + directory.__class__.__name__ + "/"
-            self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
-
-            principalCollections = provisioningResource.principalCollections()
-            self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
-            recordTypes = set(provisioningResource.listChildren())
-            self.assertEquals(recordTypes, set(directory.recordTypes()))
-
-            for recordType in recordTypes:
-                #print "   -> %s" % (recordType,)
-                typeResource = provisioningResource.getChild(recordType)
-                self.failUnless(isinstance(typeResource, DirectoryPrincipalTypeResource))
-
-                typeURL = provisioningURL + recordType + "/"
-                self.assertEquals(typeURL, typeResource.principalCollectionURL())
-
-                principalCollections = typeResource.principalCollections()
-                self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
-                shortNames = set(typeResource.listChildren())
-                self.assertEquals(shortNames, set(r.shortName for r in directory.listRecords(recordType)))
-                
-                for shortName in shortNames:
-                    #print "     -> %s" % (shortName,)
-                    recordResource = typeResource.getChild(shortName)
-                    self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
-
-                    recordURL = typeURL + shortName
-                    self.assertEquals(recordURL, recordResource.principalURL())
-
-                    principalCollections = recordResource.principalCollections()
-                    self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
-    def test_principalForUser(self):
-        """
-        DirectoryPrincipalProvisioningResource.principalForUser()
-        """
-        for directory in directoryServices:
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
-            for user in directory.listRecords("user"):
-                userResource = provisioningResource.principalForUser(user.shortName)
-                self.failIf(userResource is None)
-                self.assertEquals(user, userResource.record)
-
-    def test_principalForRecord(self):
-        """
-        DirectoryPrincipalProvisioningResource.principalForRecord()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.assertEquals(recordResource.record, record)
-                    
-    def test_displayName(self):
-        """
-        DirectoryPrincipalResource.displayName()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.failUnless(recordResource.displayName())
-
-    def test_groupMembers(self):
-        """
-        DirectoryPrincipalResource.groupMembers()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.failUnless(set(record.members()).issubset(set(r.record for r in recordResource.groupMembers())))
-
-    def test_groupMemberships(self):
-        """
-        DirectoryPrincipalResource.groupMemberships()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.failUnless(set(record.groups()).issubset(set(r.record for r in recordResource.groupMemberships())))
-
-    def test_principalUID(self):
-        """
-        DirectoryPrincipalResource.principalUID()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.assertEquals(record.shortName, recordResource.principalUID())
-
-    def test_calendarUserAddresses(self):
-        """
-        DirectoryPrincipalResource.calendarUserAddresses()
-        """
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.failUnless(
-                (
-                    set((recordResource.principalURL(),)) |
-                    set(record.calendarUserAddresses)
-                ).issubset(set(recordResource.calendarUserAddresses()))
-            )
-
-    def test_calendarHomeURLs(self):
-        """
-        DirectoryPrincipalResource.calendarHomeURLs(),
-        DirectoryPrincipalResource.scheduleInboxURL(),
-        DirectoryPrincipalResource.scheduleOutboxURL()
-        """
-        # No calendar home provisioner should result in no calendar homes.
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            self.failIf(tuple(recordResource.calendarHomeURLs()))
-            self.failIf(recordResource.scheduleInboxURL())
-            self.failIf(recordResource.scheduleOutboxURL())
-
-        # Need to create a calendar home provisioner for each service.
-        calendarRootResources = {}
-
-        for directory in directoryServices:
-            url = "/homes_" + directory.__class__.__name__ + "/"
-            path = os.path.join(self.docroot, url[1:])
-
-            if os.path.exists(path):
-                rmdir(path)
-            os.mkdir(path)
-
-            provisioningResource = CalendarHomeProvisioningFile(path, directory, url)
-
-            calendarRootResources[directory.__class__.__name__] = provisioningResource
-        
-        # Calendar home provisioners should result in calendar homes.
-        for provisioningResource, recordType, recordResource, record in self._allRecords():
-            homeURLs = tuple(recordResource.calendarHomeURLs())
-            self.failUnless(homeURLs)
-
-            calendarRootURL = calendarRootResources[record.service.__class__.__name__].url()
-
-            inboxURL = recordResource.scheduleInboxURL()
-            outboxURL = recordResource.scheduleOutboxURL()
-
-            self.failUnless(inboxURL)
-            self.failUnless(outboxURL)
-
-            for homeURL in homeURLs:
-                self.failUnless(homeURL.startswith(calendarRootURL))
-
-                if inboxURL and inboxURL.startswith(homeURL):
-                    self.failUnless(len(inboxURL) > len(homeURL))
-                    self.failUnless(inboxURL.endswith("/"))
-                    inboxURL = None
-
-                if outboxURL and outboxURL.startswith(homeURL):
-                    self.failUnless(len(outboxURL) > len(homeURL))
-                    self.failUnless(outboxURL.endswith("/"))
-                    outboxURL = None
-
-            self.failIf(inboxURL)
-            self.failIf(outboxURL)
-
-    def test_defaultAccessControlList_principals(self):
-        """
-        Default access controls for principals.
-        """
-        def work():
-            for provisioningResource, recordType, recordResource, record in self._allRecords():
-                for args in _authReadOnlyPrivileges(recordResource, recordResource.principalURL()):
-                    yield args
-
-        return serialize(self._checkPrivileges, work())
-
-    def test_defaultAccessControlList_provisioners(self):
-        """
-        Default access controls for principal provisioning resources.
-        """
-        def work():
-            for directory in directoryServices:
-                #print "\n -> %s" % (directory.__class__.__name__,)
-                provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
-                for args in _authReadOnlyPrivileges(provisioningResource, provisioningResource.principalCollectionURL()):
-                    yield args
-
-                for recordType in provisioningResource.listChildren():
-                    #print "   -> %s" % (recordType,)
-                    typeResource = provisioningResource.getChild(recordType)
-
-                    for args in _authReadOnlyPrivileges(typeResource, typeResource.principalCollectionURL()):
-                        yield args
-
-        return serialize(self._checkPrivileges, work())
-
-    def _allRecords(self):
-        """
-        @return: an iterable of tuples
-            C{(provisioningResource, recordType, recordResource, record)}, where
-            C{provisioningResource} is the root provisioning resource,
-            C{recordType} is the record type,
-            C{recordResource} is the principal resource and
-            C{record} is the directory service record
-            for each record in each directory in C{directoryServices}.
-        """
-        for directory in directoryServices:
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
-            for recordType in directory.recordTypes():
-                for record in directory.listRecords(recordType):
-                    recordResource = provisioningResource.principalForRecord(record)
-                    yield provisioningResource, recordType, recordResource, record
-
-    def _checkPrivileges(self, resource, url, principal, privilege, allowed):
-        request = SimpleRequest(self.site, "GET", "/")
-
-        def gotResource(resource):
-            d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
-            if allowed:
-                def onError(f):
-                    f.trap(AccessDeniedError)
-                    #print resource.readDeadProperty(davxml.ACL)
-                    self.fail("%s should have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
-                d.addErrback(onError)
-            else:
-                def onError(f):
-                    f.trap(AccessDeniedError)
-                def onSuccess(_):
-                    #print resource.readDeadProperty(davxml.ACL)
-                    self.fail("%s should not have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
-                d.addCallback(onSuccess)
-                d.addErrback(onError)
-            return d
-
-        d = request.locateResource(url)
-        d.addCallback(gotResource)
-        return d
-
-def _authReadOnlyPrivileges(resource, url):
-    for principal, privilege, allowed in (
-        ( davxml.All()             , davxml.Read()  , False ),
-        ( davxml.All()             , davxml.Write() , False ),
-        ( davxml.Unauthenticated() , davxml.Read()  , False ),
-        ( davxml.Unauthenticated() , davxml.Write() , False ),
-        ( davxml.Authenticated()   , davxml.Read()  , True  ),
-        ( davxml.Authenticated()   , davxml.Write() , False ),
-    ):
-        yield resource, url, principal, privilege, allowed

Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_principal.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,326 @@
+##
+# Copyright (c) 2005-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
+##
+
+#from twisted.web2 import responsecode
+#from twisted.web2.iweb import IResponse
+#from twisted.web2.dav import davxml
+#from twisted.web2.dav.util import davXMLFromStream
+#from twisted.web2.test.test_server import SimpleRequest
+#from twistedcaldav import caldavxml
+
+import os
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.web2.dav import davxml
+from twisted.web2.dav.fileop import rmdir
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.test.test_server import SimpleRequest
+from twisted.web2.dav.test.util import serialize
+
+from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.directory.principal import DirectoryPrincipalTypeResource
+from twistedcaldav.directory.principal import DirectoryPrincipalResource
+
+import twistedcaldav.test.util
+
+directoryServices = (
+    BasicDirectoryService(basicUserFile, groupFile),
+    DigestDirectoryService(digestUserFile, groupFile),
+    XMLDirectoryService(xmlFile),
+)
+
+class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
+    """
+    Directory service provisioned principals.
+    """
+    def setUp(self):
+        super(ProvisionedPrincipals, self).setUp()
+        
+        # Set up a principals hierarchy for each service we're testing with
+        self.principalRootResources = {}
+        for directory in directoryServices:
+            name = directory.__class__.__name__
+            url = "/" + name + "/"
+            path = os.path.join(self.docroot, url[1:])
+
+            if os.path.exists(path):
+                rmdir(path)
+            os.mkdir(path)
+
+            provisioningResource = DirectoryPrincipalProvisioningResource(path, url, directory)
+
+            self.site.resource.putChild(name, provisioningResource)
+
+            self.principalRootResources[directory.__class__.__name__] = provisioningResource
+
+    def test_hierarchy(self):
+        """
+        DirectoryPrincipalProvisioningResource.listChildren(),
+        DirectoryPrincipalProvisioningResource.getChildren(),
+        DirectoryPrincipalProvisioningResource.principalCollectionURL(),
+        DirectoryPrincipalProvisioningResource.principalCollections()
+
+        DirectoryPrincipalTypeResource.listChildren(),
+        DirectoryPrincipalTypeResource.getChildren(),
+        DirectoryPrincipalTypeResource.principalCollectionURL(),
+        DirectoryPrincipalTypeResource.principalCollections()
+
+        DirectoryPrincipalResource.principalURL(),
+        """
+        for directory in directoryServices:
+            #print "\n -> %s" % (directory.__class__.__name__,)
+            provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+            provisioningURL = "/" + directory.__class__.__name__ + "/"
+            self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
+
+            principalCollections = provisioningResource.principalCollections()
+            self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+            recordTypes = set(provisioningResource.listChildren())
+            self.assertEquals(recordTypes, set(directory.recordTypes()))
+
+            for recordType in recordTypes:
+                #print "   -> %s" % (recordType,)
+                typeResource = provisioningResource.getChild(recordType)
+                self.failUnless(isinstance(typeResource, DirectoryPrincipalTypeResource))
+
+                typeURL = provisioningURL + recordType + "/"
+                self.assertEquals(typeURL, typeResource.principalCollectionURL())
+
+                principalCollections = typeResource.principalCollections()
+                self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+                shortNames = set(typeResource.listChildren())
+                self.assertEquals(shortNames, set(r.shortName for r in directory.listRecords(recordType)))
+                
+                for shortName in shortNames:
+                    #print "     -> %s" % (shortName,)
+                    recordResource = typeResource.getChild(shortName)
+                    self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
+
+                    recordURL = typeURL + shortName
+                    self.assertEquals(recordURL, recordResource.principalURL())
+
+                    principalCollections = recordResource.principalCollections()
+                    self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+    def test_principalForUser(self):
+        """
+        DirectoryPrincipalProvisioningResource.principalForUser()
+        """
+        for directory in directoryServices:
+            provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+            for user in directory.listRecords("user"):
+                userResource = provisioningResource.principalForUser(user.shortName)
+                self.failIf(userResource is None)
+                self.assertEquals(user, userResource.record)
+
+    def test_principalForRecord(self):
+        """
+        DirectoryPrincipalProvisioningResource.principalForRecord()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.assertEquals(recordResource.record, record)
+                    
+    def test_displayName(self):
+        """
+        DirectoryPrincipalResource.displayName()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failUnless(recordResource.displayName())
+
+    def test_groupMembers(self):
+        """
+        DirectoryPrincipalResource.groupMembers()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failUnless(set(record.members()).issubset(set(r.record for r in recordResource.groupMembers())))
+
+    def test_groupMemberships(self):
+        """
+        DirectoryPrincipalResource.groupMemberships()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failUnless(set(record.groups()).issubset(set(r.record for r in recordResource.groupMemberships())))
+
+    def test_principalUID(self):
+        """
+        DirectoryPrincipalResource.principalUID()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.assertEquals(record.shortName, recordResource.principalUID())
+
+    def test_calendarUserAddresses(self):
+        """
+        DirectoryPrincipalResource.calendarUserAddresses()
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failUnless(
+                (
+                    set((recordResource.principalURL(),)) |
+                    set(record.calendarUserAddresses)
+                ).issubset(set(recordResource.calendarUserAddresses()))
+            )
+
+    def test_calendarHomeURLs(self):
+        """
+        DirectoryPrincipalResource.calendarHomeURLs(),
+        DirectoryPrincipalResource.scheduleInboxURL(),
+        DirectoryPrincipalResource.scheduleOutboxURL()
+        """
+        # No calendar home provisioner should result in no calendar homes.
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failIf(tuple(recordResource.calendarHomeURLs()))
+            self.failIf(recordResource.scheduleInboxURL())
+            self.failIf(recordResource.scheduleOutboxURL())
+
+        # Need to create a calendar home provisioner for each service.
+        calendarRootResources = {}
+
+        for directory in directoryServices:
+            url = "/homes_" + directory.__class__.__name__ + "/"
+            path = os.path.join(self.docroot, url[1:])
+
+            if os.path.exists(path):
+                rmdir(path)
+            os.mkdir(path)
+
+            provisioningResource = CalendarHomeProvisioningFile(path, directory, url)
+
+            calendarRootResources[directory.__class__.__name__] = provisioningResource
+        
+        # Calendar home provisioners should result in calendar homes.
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            homeURLs = tuple(recordResource.calendarHomeURLs())
+            self.failUnless(homeURLs)
+
+            calendarRootURL = calendarRootResources[record.service.__class__.__name__].url()
+
+            inboxURL = recordResource.scheduleInboxURL()
+            outboxURL = recordResource.scheduleOutboxURL()
+
+            self.failUnless(inboxURL)
+            self.failUnless(outboxURL)
+
+            for homeURL in homeURLs:
+                self.failUnless(homeURL.startswith(calendarRootURL))
+
+                if inboxURL and inboxURL.startswith(homeURL):
+                    self.failUnless(len(inboxURL) > len(homeURL))
+                    self.failUnless(inboxURL.endswith("/"))
+                    inboxURL = None
+
+                if outboxURL and outboxURL.startswith(homeURL):
+                    self.failUnless(len(outboxURL) > len(homeURL))
+                    self.failUnless(outboxURL.endswith("/"))
+                    outboxURL = None
+
+            self.failIf(inboxURL)
+            self.failIf(outboxURL)
+
+    def test_defaultAccessControlList_principals(self):
+        """
+        Default access controls for principals.
+        """
+        def work():
+            for provisioningResource, recordType, recordResource, record in self._allRecords():
+                for args in _authReadOnlyPrivileges(recordResource, recordResource.principalURL()):
+                    yield args
+
+        return serialize(self._checkPrivileges, work())
+
+    def test_defaultAccessControlList_provisioners(self):
+        """
+        Default access controls for principal provisioning resources.
+        """
+        def work():
+            for directory in directoryServices:
+                #print "\n -> %s" % (directory.__class__.__name__,)
+                provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+                for args in _authReadOnlyPrivileges(provisioningResource, provisioningResource.principalCollectionURL()):
+                    yield args
+
+                for recordType in provisioningResource.listChildren():
+                    #print "   -> %s" % (recordType,)
+                    typeResource = provisioningResource.getChild(recordType)
+
+                    for args in _authReadOnlyPrivileges(typeResource, typeResource.principalCollectionURL()):
+                        yield args
+
+        return serialize(self._checkPrivileges, work())
+
+    def _allRecords(self):
+        """
+        @return: an iterable of tuples
+            C{(provisioningResource, recordType, recordResource, record)}, where
+            C{provisioningResource} is the root provisioning resource,
+            C{recordType} is the record type,
+            C{recordResource} is the principal resource and
+            C{record} is the directory service record
+            for each record in each directory in C{directoryServices}.
+        """
+        for directory in directoryServices:
+            provisioningResource = self.principalRootResources[directory.__class__.__name__]
+            for recordType in directory.recordTypes():
+                for record in directory.listRecords(recordType):
+                    recordResource = provisioningResource.principalForRecord(record)
+                    yield provisioningResource, recordType, recordResource, record
+
+    def _checkPrivileges(self, resource, url, principal, privilege, allowed):
+        request = SimpleRequest(self.site, "GET", "/")
+
+        def gotResource(resource):
+            d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
+            if allowed:
+                def onError(f):
+                    f.trap(AccessDeniedError)
+                    #print resource.readDeadProperty(davxml.ACL)
+                    self.fail("%s should have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
+                d.addErrback(onError)
+            else:
+                def onError(f):
+                    f.trap(AccessDeniedError)
+                def onSuccess(_):
+                    #print resource.readDeadProperty(davxml.ACL)
+                    self.fail("%s should not have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
+                d.addCallback(onSuccess)
+                d.addErrback(onError)
+            return d
+
+        d = request.locateResource(url)
+        d.addCallback(gotResource)
+        return d
+
+def _authReadOnlyPrivileges(resource, url):
+    for principal, privilege, allowed in (
+        ( davxml.All()             , davxml.Read()  , False ),
+        ( davxml.All()             , davxml.Write() , False ),
+        ( davxml.Unauthenticated() , davxml.Read()  , False ),
+        ( davxml.Unauthenticated() , davxml.Write() , False ),
+        ( davxml.Authenticated()   , davxml.Read()  , True  ),
+        ( davxml.Authenticated()   , davxml.Write() , False ),
+    ):
+        yield resource, url, principal, privilege, allowed

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,44 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-import twistedcaldav.directory.test.test_xmlfile
-from twistedcaldav.directory.sqldb import SQLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class SQLDB (
-    twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
-    twistedcaldav.directory.test.util.BasicTestCase,
-    twistedcaldav.directory.test.util.DigestTestCase
-):
-    """
-    Test SQL directory implementation.
-    """
-    def service(self):
-        return SQLDirectoryService(os.getcwd(), self.xmlFile())
-
-    def test_verifyCredentials_digest(self):
-        raise NotImplementedError("Use super's implementation")
-    test_verifyCredentials_digest.todo = "FIXME: SQLDirectoryService.realmName is None"

Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2005-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
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+import twistedcaldav.directory.test.test_xmlfile
+from twistedcaldav.directory.sqldb import SQLDirectoryService
+
+xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class SQLDB (
+    twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
+    twistedcaldav.directory.test.util.BasicTestCase,
+    twistedcaldav.directory.test.util.DigestTestCase
+):
+    """
+    Test SQL directory implementation.
+    """
+    def service(self):
+        return SQLDirectoryService(os.getcwd(), self.xmlFile())
+
+    def test_verifyCredentials_digest(self):
+        raise NotImplementedError("Use super's implementation")
+    test_verifyCredentials_digest.todo = "FIXME: SQLDirectoryService.realmName is None"

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,95 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class XMLFileBase(object):
-    recordTypes = set(("user", "group", "resource"))
-
-    users = {
-        "admin"   : "nimda",
-        "proxy"   : "yxorp",
-        "wsanchez": "zehcnasw",
-        "cdaboo"  : "oobadc",
-        "lecroy"  : "yorcel",
-        "dreid"   : "dierd",
-        "user01"  : "01user",
-        "user02"  : "02user",
-    }
-
-    groups = {
-        "managers"   : ("lecroy",),
-        "grunts"     : ("wsanchez", "cdaboo", "dreid"),
-        "right_coast": ("cdaboo",),
-        "left_coast" : ("wsanchez", "dreid", "lecroy"),
-    }
-
-    resources = set((
-        "mercury",
-        "gemini",
-        "apollo",
-    ))
-
-    def xmlFile(self):
-        if not hasattr(self, "_xmlFile"):
-            self._xmlFile = FilePath(self.mktemp())
-            xmlFile.copyTo(self._xmlFile)
-        return self._xmlFile
-
-class XMLFile (
-    XMLFileBase,
-    twistedcaldav.directory.test.util.BasicTestCase,
-    twistedcaldav.directory.test.util.DigestTestCase
-):
-    """
-    Test XML file based directory implementation.
-    """
-    def service(self):
-        return XMLDirectoryService(self.xmlFile())
-
-    def test_changedXML(self):
-        self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts>
-  <user>
-    <uid>admin</uid>
-    <pswd>nimda</pswd>
-    <name>Super User</name>
-  </user>
-</accounts>
-"""
-        )
-        for recordType, expectedRecords in (
-            ( "user"     , ("admin",) ),
-            ( "group"    , ()         ),
-            ( "resource" , ()         ),
-        ):
-            self.assertEquals(
-                set(r.shortName for r in self.service().listRecords(recordType)),
-                set(expectedRecords)
-            )

Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,95 @@
+##
+# Copyright (c) 2005-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
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+
+xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class XMLFileBase(object):
+    recordTypes = set(("user", "group", "resource"))
+
+    users = {
+        "admin"   : "nimda",
+        "proxy"   : "yxorp",
+        "wsanchez": "zehcnasw",
+        "cdaboo"  : "oobadc",
+        "lecroy"  : "yorcel",
+        "dreid"   : "dierd",
+        "user01"  : "01user",
+        "user02"  : "02user",
+    }
+
+    groups = {
+        "managers"   : ("lecroy",),
+        "grunts"     : ("wsanchez", "cdaboo", "dreid"),
+        "right_coast": ("cdaboo",),
+        "left_coast" : ("wsanchez", "dreid", "lecroy"),
+    }
+
+    resources = set((
+        "mercury",
+        "gemini",
+        "apollo",
+    ))
+
+    def xmlFile(self):
+        if not hasattr(self, "_xmlFile"):
+            self._xmlFile = FilePath(self.mktemp())
+            xmlFile.copyTo(self._xmlFile)
+        return self._xmlFile
+
+class XMLFile (
+    XMLFileBase,
+    twistedcaldav.directory.test.util.BasicTestCase,
+    twistedcaldav.directory.test.util.DigestTestCase
+):
+    """
+    Test XML file based directory implementation.
+    """
+    def service(self):
+        return XMLDirectoryService(self.xmlFile())
+
+    def test_changedXML(self):
+        self.xmlFile().open("w").write(
+"""<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+<accounts>
+  <user>
+    <uid>admin</uid>
+    <pswd>nimda</pswd>
+    <name>Super User</name>
+  </user>
+</accounts>
+"""
+        )
+        for recordType, expectedRecords in (
+            ( "user"     , ("admin",) ),
+            ( "group"    , ()         ),
+            ( "resource" , ()         ),
+        ):
+            self.assertEquals(
+                set(r.shortName for r in self.service().listRecords(recordType)),
+                set(expectedRecords)
+            )

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -1,234 +0,0 @@
-##
-# Copyright (c) 2005-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
-##
-
-import twisted.trial.unittest
-from twisted.trial.unittest import SkipTest
-from twisted.cred.credentials import UsernamePassword
-from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class DirectoryTestCase (twisted.trial.unittest.TestCase):
-    """
-    Tests a directory implementation.
-    """
-    # Subclass should init this to a set of recordtypes.
-    recordTypes = set()
-
-    # Subclass should init this to a dict of username keys and password values.
-    users = {}
-
-    # Subclass should init this to a dict of groupname keys and
-    # sequence-of-members values.
-    groups = {}
-
-    # Subclass should init this to a set of resourcenames.
-    resources = set()
-
-    # Subclass should init this to an IDirectoryService implementation class.
-    def service(self):
-        """
-        Returns an IDirectoryService.
-        """
-        raise NotImplementedError("Subclass needs to implement service()")
-
-    def test_recordTypes(self):
-        """
-        IDirectoryService.recordTypes()
-        """
-        if not self.recordTypes:
-            raise SkipTest("No record types")
-
-        self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
-
-    def test_listRecords_user(self):
-        """
-        IDirectoryService.listRecords("user")
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        self.assertEquals(self.recordNames("user"), set(self.users.keys()))
-
-    def test_listRecords_group(self):
-        """
-        IDirectoryService.listRecords("group")
-        """
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        self.assertEquals(self.recordNames("group"), set(self.groups.keys()))
-
-    def test_listRecords_resources(self):
-        """
-        IDirectoryService.listRecords("resources")
-        """
-        if not self.resources:
-            raise SkipTest("No resources")
-
-        self.assertEquals(self.recordNames("resource"), self.resources)
-
-    def test_recordWithShortName_user(self):
-        """
-        IDirectoryService.recordWithShortName("user")
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        service = self.service()
-        for user in self.users:
-            record = service.recordWithShortName("user", user)
-            self.assertEquals(record.shortName, user)
-        self.assertEquals(service.recordWithShortName("user", "IDunnoWhoThisIsIReallyDont"), None)
-
-    def test_recordWithShortName_group(self):
-        """
-        IDirectoryService.recordWithShortName("group")
-        """
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        service = self.service()
-        for group in self.groups:
-            groupRecord = service.recordWithShortName("group", group)
-            self.assertEquals(groupRecord.shortName, group)
-        self.assertEquals(service.recordWithShortName("group", "IDunnoWhoThisIsIReallyDont"), None)
-
-    def test_recordWithShortName_resource(self):
-        """
-        XMLDirectoryService.recordWithShortName("resource")
-        """
-        if not self.resources:
-            raise SkipTest("No resources")
-
-        service = self.service()
-        for resource in self.resources:
-            resourceRecord = service.recordWithShortName("resource", resource)
-            self.assertEquals(resourceRecord.shortName, resource)
-
-    def test_groupMembers(self):
-        """
-        IDirectoryRecord.members()
-        """
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        service = self.service()
-        for group in self.groups:
-            groupRecord = service.recordWithShortName("group", group)
-            self.assertEquals(
-                set(m.shortName for m in groupRecord.members()),
-                set(self.groups[group])
-            )
-
-    def test_groupMemberships(self):
-        """
-        IDirectoryRecord.groups()
-        """
-        if not self.users:
-            raise SkipTest("No users")
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        service = self.service()
-        for user in self.users:
-            userRecord = service.recordWithShortName("user", user)
-            self.assertEquals(
-                set(g.shortName for g in userRecord.groups()),
-                set(g for g in self.groups if user in self.groups[g])
-            )
-
-    def recordNames(self, recordType):
-        return set(r.shortName for r in self.service().listRecords(recordType))
-
-class BasicTestCase (DirectoryTestCase):
-    """
-    Tests a directory implementation with basic auth.
-    """
-    def test_verifyCredentials_basic(self):
-        """
-        IDirectoryRecord.verifyCredentials() with basic
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        service = self.service()
-        for user in self.users:
-            userRecord = service.recordWithShortName("user", user)
-            self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, self.users[user])))
-
-# authRequest = {
-#    username="username",
-#    realm="test realm",
-#    nonce="178288758716122392881254770685",
-#    uri="/write/",
-#    response="62f388be1cf678fbdfce87910871bcc5",
-#    opaque="1041524039",
-#    algorithm="md5",
-#    cnonce="29fc54aa1641c6fa0e151419361c8f23",
-#    nc=00000001,
-#    qop="auth",
-# }
-
-class DigestTestCase (DirectoryTestCase):
-    """
-    Tests a directory implementation with digest auth.
-    """
-    def test_verifyCredentials_digest(self):
-        """
-        IDirectoryRecord.verifyCredentials() with digest
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        service = self.service()
-        for user in self.users:
-            userRecord = service.recordWithShortName("user", user)
-
-            # I'm glad this is so simple...
-            response = calcResponse(
-                calcHA1(
-                    "md5",
-                    user,
-                    service.realmName,
-                    userRecord.password,
-                    "booger",
-                    "phlegm",
-                ),
-                "md5",
-                "booger",
-                None,
-                "phlegm",
-                "auth",
-                "GET",
-                "/",
-                None,
-            )
-
-            self.failUnless(userRecord.verifyCredentials(DigestedCredentials(
-                user,
-                "GET",
-                service.realmName,
-                {
-                    "response": response,
-                    "uri": "/",
-                    "nonce": "booger",
-                    "cnonce": "phlegm",
-                    "nc": None,
-                },
-            )))

Copied: CalendarServer/trunk/twistedcaldav/directory/test/util.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/util.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,234 @@
+##
+# Copyright (c) 2005-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
+##
+
+import twisted.trial.unittest
+from twisted.trial.unittest import SkipTest
+from twisted.cred.credentials import UsernamePassword
+from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class DirectoryTestCase (twisted.trial.unittest.TestCase):
+    """
+    Tests a directory implementation.
+    """
+    # Subclass should init this to a set of recordtypes.
+    recordTypes = set()
+
+    # Subclass should init this to a dict of username keys and password values.
+    users = {}
+
+    # Subclass should init this to a dict of groupname keys and
+    # sequence-of-members values.
+    groups = {}
+
+    # Subclass should init this to a set of resourcenames.
+    resources = set()
+
+    # Subclass should init this to an IDirectoryService implementation class.
+    def service(self):
+        """
+        Returns an IDirectoryService.
+        """
+        raise NotImplementedError("Subclass needs to implement service()")
+
+    def test_recordTypes(self):
+        """
+        IDirectoryService.recordTypes()
+        """
+        if not self.recordTypes:
+            raise SkipTest("No record types")
+
+        self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
+
+    def test_listRecords_user(self):
+        """
+        IDirectoryService.listRecords("user")
+        """
+        if not self.users:
+            raise SkipTest("No users")
+
+        self.assertEquals(self.recordNames("user"), set(self.users.keys()))
+
+    def test_listRecords_group(self):
+        """
+        IDirectoryService.listRecords("group")
+        """
+        if not self.groups:
+            raise SkipTest("No groups")
+
+        self.assertEquals(self.recordNames("group"), set(self.groups.keys()))
+
+    def test_listRecords_resources(self):
+        """
+        IDirectoryService.listRecords("resources")
+        """
+        if not self.resources:
+            raise SkipTest("No resources")
+
+        self.assertEquals(self.recordNames("resource"), self.resources)
+
+    def test_recordWithShortName_user(self):
+        """
+        IDirectoryService.recordWithShortName("user")
+        """
+        if not self.users:
+            raise SkipTest("No users")
+
+        service = self.service()
+        for user in self.users:
+            record = service.recordWithShortName("user", user)
+            self.assertEquals(record.shortName, user)
+        self.assertEquals(service.recordWithShortName("user", "IDunnoWhoThisIsIReallyDont"), None)
+
+    def test_recordWithShortName_group(self):
+        """
+        IDirectoryService.recordWithShortName("group")
+        """
+        if not self.groups:
+            raise SkipTest("No groups")
+
+        service = self.service()
+        for group in self.groups:
+            groupRecord = service.recordWithShortName("group", group)
+            self.assertEquals(groupRecord.shortName, group)
+        self.assertEquals(service.recordWithShortName("group", "IDunnoWhoThisIsIReallyDont"), None)
+
+    def test_recordWithShortName_resource(self):
+        """
+        XMLDirectoryService.recordWithShortName("resource")
+        """
+        if not self.resources:
+            raise SkipTest("No resources")
+
+        service = self.service()
+        for resource in self.resources:
+            resourceRecord = service.recordWithShortName("resource", resource)
+            self.assertEquals(resourceRecord.shortName, resource)
+
+    def test_groupMembers(self):
+        """
+        IDirectoryRecord.members()
+        """
+        if not self.groups:
+            raise SkipTest("No groups")
+
+        service = self.service()
+        for group in self.groups:
+            groupRecord = service.recordWithShortName("group", group)
+            self.assertEquals(
+                set(m.shortName for m in groupRecord.members()),
+                set(self.groups[group])
+            )
+
+    def test_groupMemberships(self):
+        """
+        IDirectoryRecord.groups()
+        """
+        if not self.users:
+            raise SkipTest("No users")
+        if not self.groups:
+            raise SkipTest("No groups")
+
+        service = self.service()
+        for user in self.users:
+            userRecord = service.recordWithShortName("user", user)
+            self.assertEquals(
+                set(g.shortName for g in userRecord.groups()),
+                set(g for g in self.groups if user in self.groups[g])
+            )
+
+    def recordNames(self, recordType):
+        return set(r.shortName for r in self.service().listRecords(recordType))
+
+class BasicTestCase (DirectoryTestCase):
+    """
+    Tests a directory implementation with basic auth.
+    """
+    def test_verifyCredentials_basic(self):
+        """
+        IDirectoryRecord.verifyCredentials() with basic
+        """
+        if not self.users:
+            raise SkipTest("No users")
+
+        service = self.service()
+        for user in self.users:
+            userRecord = service.recordWithShortName("user", user)
+            self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, self.users[user])))
+
+# authRequest = {
+#    username="username",
+#    realm="test realm",
+#    nonce="178288758716122392881254770685",
+#    uri="/write/",
+#    response="62f388be1cf678fbdfce87910871bcc5",
+#    opaque="1041524039",
+#    algorithm="md5",
+#    cnonce="29fc54aa1641c6fa0e151419361c8f23",
+#    nc=00000001,
+#    qop="auth",
+# }
+
+class DigestTestCase (DirectoryTestCase):
+    """
+    Tests a directory implementation with digest auth.
+    """
+    def test_verifyCredentials_digest(self):
+        """
+        IDirectoryRecord.verifyCredentials() with digest
+        """
+        if not self.users:
+            raise SkipTest("No users")
+
+        service = self.service()
+        for user in self.users:
+            userRecord = service.recordWithShortName("user", user)
+
+            # I'm glad this is so simple...
+            response = calcResponse(
+                calcHA1(
+                    "md5",
+                    user,
+                    service.realmName,
+                    userRecord.password,
+                    "booger",
+                    "phlegm",
+                ),
+                "md5",
+                "booger",
+                None,
+                "phlegm",
+                "auth",
+                "GET",
+                "/",
+                None,
+            )
+
+            self.failUnless(userRecord.verifyCredentials(DigestedCredentials(
+                user,
+                "GET",
+                service.realmName,
+                {
+                    "response": response,
+                    "uri": "/",
+                    "nonce": "booger",
+                    "cnonce": "phlegm",
+                    "nc": None,
+                },
+            )))

Copied: CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,194 @@
+##
+# 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
+##
+
+
+"""
+XML based user/group/resource configuration file handling.
+"""
+
+__all__ = [
+    "XMLAccountsParser",
+]
+
+import xml.dom.minidom
+
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.resource import CalDAVResource
+
+ELEMENT_ACCOUNTS    = "accounts"
+ELEMENT_USER        = "user"
+ELEMENT_GROUP       = "group"
+ELEMENT_RESOURCE    = "resource"
+
+ELEMENT_USERID      = "uid"
+ELEMENT_PASSWORD    = "pswd"
+ELEMENT_NAME        = "name"
+ELEMENT_MEMBERS     = "members"
+ELEMENT_CUADDR      = "cuaddr"
+ELEMENT_CANPROXY    = "canproxy"
+
+ATTRIBUTE_REALM     = "realm"
+ATTRIBUTE_REPEAT    = "repeat"
+
+class XMLAccountsParser(object):
+    """
+    XML account configuration file parser.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, xmlFile):
+        if type(xmlFile) is str:
+            xmlFile = FilePath(xmlFile)
+
+        self.xmlFile = xmlFile
+        self.realm = None
+        self.items = {}
+
+        # Read in XML
+        fd = open(self.xmlFile.path, "r")
+        doc = xml.dom.minidom.parse( fd )
+        fd.close()
+
+        # Verify that top-level element is correct
+        accounts_node = doc._get_documentElement()
+        if accounts_node._get_localName() != ELEMENT_ACCOUNTS:
+            self.log("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
+            return
+        self._parseXML(accounts_node)
+        
+    def _parseXML(self, node):
+        """
+        Parse the XML root node from the accounts configuration document.
+        @param node: the L{Node} to parse.
+        """
+        if node.hasAttribute(ATTRIBUTE_REALM):
+            self.realm = node.getAttribute(ATTRIBUTE_REALM)
+
+        for child in node._get_childNodes():
+            if child._get_localName() in (ELEMENT_USER, ELEMENT_GROUP, ELEMENT_RESOURCE):
+                if child.hasAttribute(ATTRIBUTE_REPEAT):
+                    repeat = int(child.getAttribute(ATTRIBUTE_REPEAT))
+                else:
+                    repeat = 1
+
+                recordType = {
+                    ELEMENT_USER:    "user",
+                    ELEMENT_GROUP:   "group",
+                    ELEMENT_RESOURCE:"resource",}[child._get_localName()]
+                
+                principal = XMLAccountRecord(recordType)
+                principal.parseXML( child )
+                if repeat > 1:
+                    for ctr in range(repeat):
+                        newprincipal = principal.repeat(ctr + 1)
+                        self.items[newprincipal.uid] = newprincipal
+                        if recordType == "group":
+                            self._updateMembership(newprincipal)
+                else:
+                    self.items[principal.uid] = principal
+                    if recordType == "group":
+                        self._updateMembership(principal)
+
+    def _updateMembership(self, group):
+        # Update group membership
+        for member in group.members:
+            if self.items.has_key(member):
+                self.items[member].groups.append(group.uid)
+        
+class XMLAccountRecord (object):
+    """
+    Contains provision information for one user.
+    """
+    def __init__(self, recordType):
+        """
+        @param recordType:    record type for directory entry.
+        """
+        
+        self.recordType = recordType
+        self.uid = None
+        self.password = None
+        self.name = None
+        self.members = []
+        self.groups = []
+        self.calendarUserAddresses = []
+        self.canproxy = False
+
+    def repeat(self, ctr):
+        """
+        Create another object like this but with all text items having % substitution
+        done on them with the numeric value provided.
+        @param ctr: an integer to substitute into text.
+        """
+        
+        if self.uid.find("%") != -1:
+            uid = self.uid % ctr
+        else:
+            uid = self.uid
+        if self.password.find("%") != -1:
+            password = self.password % ctr
+        else:
+            password = self.password
+        if self.name.find("%") != -1:
+            name = self.name % ctr
+        else:
+            name = self.name
+        calendarUserAddresses = []
+        for cuaddr in self.calendarUserAddresses:
+            if cuaddr.find("%") != -1:
+                calendarUserAddresses.append(cuaddr % ctr)
+            else:
+                calendarUserAddresses.append(cuaddr)
+        
+        result = XMLAccountRecord(self.recordType)
+        result.uid = uid
+        result.password = password
+        result.name = name
+        result.members = self.members
+        result.calendarUserAddresses = calendarUserAddresses
+        result.canproxy = self.canproxy
+        return result
+
+    def parseXML( self, node ):
+
+        for child in node._get_childNodes():
+            if child._get_localName() == ELEMENT_USERID:
+                if child.firstChild is not None:
+                   self.uid = child.firstChild.data.encode("utf-8")
+            elif child._get_localName() == ELEMENT_PASSWORD:
+                if child.firstChild is not None:
+                    self.password = child.firstChild.data.encode("utf-8")
+            elif child._get_localName() == ELEMENT_NAME:
+                if child.firstChild is not None:
+                   self.name = child.firstChild.data.encode("utf-8")
+            elif child._get_localName() == ELEMENT_MEMBERS:
+                self._parseMembers(child)
+            elif child._get_localName() == ELEMENT_CUADDR:
+                if child.firstChild is not None:
+                   self.calendarUserAddresses.append(child.firstChild.data.encode("utf-8"))
+            elif child._get_localName() == ELEMENT_CANPROXY:
+                CalDAVResource.proxyUsers.add(self.uid)
+                self.canproxy = True
+
+    def _parseMembers( self, node ):
+
+        for child in node._get_childNodes():
+            if child._get_localName() == ELEMENT_USERID:
+                if child.firstChild is not None:
+                   self.members.append(child.firstChild.data.encode("utf-8"))

Copied: CalendarServer/trunk/twistedcaldav/directory/xmlfile.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlfile.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlfile.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,124 @@
+##
+# 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
+##
+
+"""
+XML based user/group/resource directory service implementation.
+"""
+
+__all__ = [
+    "XMLDirectoryService",
+]
+
+from twisted.cred.credentials import UsernamePassword
+from twisted.web2.auth.digest import DigestedCredentials
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+
+class XMLDirectoryService(DirectoryService):
+    """
+    XML based implementation of L{IDirectoryService}.
+    """
+    def __repr__(self):
+        return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+    def __init__(self, xmlFile):
+        super(XMLDirectoryService, self).__init__()
+
+        if type(xmlFile) is str:
+            xmlFile = FilePath(xmlFile)
+
+        self.xmlFile = xmlFile
+        self._fileInfo = None
+
+    def recordTypes(self):
+        recordTypes = ("user", "group", "resource")
+        return recordTypes
+
+    def listRecords(self, recordType):
+        for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+            yield XMLDirectoryRecord(
+                service       = self,
+                recordType    = recordType,
+                shortName     = entryShortName,
+                xmlPrincipal  = xmlprincipal,
+            )
+
+    def recordWithShortName(self, recordType, shortName):
+        for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+            if entryShortName == shortName:
+                return XMLDirectoryRecord(
+                    service       = self,
+                    recordType    = recordType,
+                    shortName     = entryShortName,
+                    xmlPrincipal  = xmlprincipal,
+                )
+
+        return None
+
+    def recordWithGUID(self, guid):
+        raise NotImplementedError()
+
+    def _entriesForRecordType(self, recordType):
+        for entry in self._accounts().itervalues():
+            if entry.recordType == recordType:
+                 yield entry.uid, entry
+
+    def _accounts(self):
+        fileInfo = (self.xmlFile.getmtime(), self.xmlFile.getsize())
+        if fileInfo != self._fileInfo:
+            parser = XMLAccountsParser(self.xmlFile)
+            self._parsedAccounts = parser.items
+            self.realmName = parser.realm
+            self._fileInfo = fileInfo
+        return self._parsedAccounts
+
+class XMLDirectoryRecord(DirectoryRecord):
+    """
+    XML based implementation implementation of L{IDirectoryRecord}.
+    """
+    def __init__(self, service, recordType, shortName, xmlPrincipal):
+        super(XMLDirectoryRecord, self).__init__(
+            service               = service,
+            recordType            = recordType,
+            guid                  = None,
+            shortName             = shortName,
+            fullName              = xmlPrincipal.name,
+            calendarUserAddresses = xmlPrincipal.calendarUserAddresses
+        )
+
+        self.password = xmlPrincipal.password
+        self._members = xmlPrincipal.members
+        self._groups  = xmlPrincipal.groups
+
+    def members(self):
+        for shortName in self._members:
+            yield self.service.recordWithShortName("user", shortName)
+
+    def groups(self):
+        for shortName in self._groups:
+            yield self.service.recordWithShortName("group", shortName)
+
+    def verifyCredentials(self, credentials):
+        if isinstance(credentials, UsernamePassword):
+            return credentials.password == self.password
+        if isinstance(credentials, DigestedCredentials):
+            return credentials.checkPassword(self.password)
+
+        return super(XMLDirectoryRecord, self).verifyCredentials(credentials)

Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -76,13 +76,13 @@
             )
 
     @classmethod
-    def provision(clzz, principal, cuhome):
+    def provision(clzz, cuhome):
         """
         Provision user account with appropriate collections for drop box
         and notifications.
         
         @param principal: the L{CalendarPrincipalResource} for the principal to provision
-        @param cuhome: C{tuple} of (C{str} - URI of user calendar home, L{DAVResource} - resource of user calendar home)
+        @param cuhome: L{DAVResource} - resource of user calendar home
         """
         
         # Only if enabled
@@ -92,7 +92,7 @@
         # Create drop box collection in calendar-home collection resource if not already present.
         
         from twistedcaldav.static import CalDAVFile
-        child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.dropboxName))
+        child = CalDAVFile(os.path.join(cuhome.fp.path, DropBox.dropboxName))
         child_exists = child.exists()
         if not child_exists:
             c = child.createSpecialCollection(davxml.ResourceType.dropboxhome)
@@ -102,7 +102,7 @@
         if not DropBox.notifications:
             return
         
-        child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.notifcationName))
+        child = CalDAVFile(os.path.join(cuhome.fp.path, DropBox.notifcationName))
         child_exists = child.exists()
         if not child_exists:
             c = child.createSpecialCollection(davxml.ResourceType.notifications)

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -27,6 +27,7 @@
 ]
 
 from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError
 from twisted.web2.dav.http import StatusResponse
 
 import twisted.web2.dav.resource
@@ -52,7 +53,7 @@
     )
 
     def _forbidden(self, request):
-        return readOnlyResponse
+        return self.readOnlyResponse
 
     http_DELETE    = _forbidden
     http_MOVE      = _forbidden
@@ -60,4 +61,4 @@
     http_PUT       = _forbidden
 
     def writeProperty(self, property, request):
-        raise HTTPError(readOnlyResponse)
+        raise HTTPError(self.readOnlyResponse)

Modified: CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcalendar.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/method/mkcalendar.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -22,8 +22,7 @@
 
 __all__ = ["http_MKCALENDAR"]
 
-from twisted.internet.defer import deferredGenerator
-from twisted.internet.defer import waitForDeferred
+from twisted.internet.defer import deferredGenerator, waitForDeferred
 from twisted.python import log
 from twisted.python.failure import Failure
 from twisted.web2 import responsecode

Modified: CalendarServer/trunk/twistedcaldav/repository.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/repository.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/repository.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -24,6 +24,14 @@
 
 __all__ = ["RepositoryBuilder"]
 
+import os
+
+from xml.dom.minidom import Element
+from xml.dom.minidom import Text
+import xml.dom.minidom
+
+from zope.interface import implements
+
 from twisted.application.internet import SSLServer, TCPServer
 from twisted.application.service import Application, IServiceCollection, MultiService
 from twisted.cred.portal import Portal
@@ -45,15 +53,9 @@
 from twistedcaldav import authkerb
 from twistedcaldav.logging import RotatingFileAccessLoggingObserver
 from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.static import CalendarHomeFile, CalendarPrincipalFile
-from twistedcaldav.directory.cred import DirectoryCredentialsChecker
+from twistedcaldav.static import CalendarHomeFile
+from twistedcaldav.directory.idirectory import IDirectoryService
 
-import os
-
-from xml.dom.minidom import Element
-from xml.dom.minidom import Text
-import xml.dom.minidom
-
 ELEMENT_REPOSITORY = "repository"
 
 ELEMENT_DOCROOT = "docroot"
@@ -127,6 +129,7 @@
 def startServer(docroot, repo, doacct, doacl, dossl,
                 keyfile, certfile, onlyssl, port, sslport, maxsize,
                 quota, serverlogfile,
+                directoryservice,
                 dropbox, dropboxName, dropboxACLs,
                 notifications, notifcationName,
                 manhole):
@@ -177,13 +180,26 @@
     # Turn on drop box support before building the repository
     DropBox.enable(dropbox, dropboxName, dropboxACLs, notifications, notifcationName)
 
+    dirname = directoryservice["type"]
+    dirparams = directoryservice["params"]
+    try:
+        resource_class = namedObject(dirname)
+    except:
+        log.err("Unable to locate Python class %r" % (dirname,))
+        raise
+    try:
+        directory = resource_class(**dirparams)
+    except Exception:
+        log.err("Unable to instantiate Python class %r with arguments %r" % (resource_class, dirparams))
+        raise
+
     # Build the server
     builder = RepositoryBuilder(docroot,
                                 doAccounts=doacct,
                                 resetACLs=doacl,
                                 maxsize=maxsize,
                                 quota=quota)
-    builder.buildFromFile(repo)
+    builder.buildFromFile(repo, directory)
     rootresource = builder.docRoot.collection.resource
     
     application = Application("CalDAVServer")
@@ -200,7 +216,7 @@
         portal.registerChecker(auth.TwistedPropertyChecker())
         print "Using property-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_DIRECTORY:
-        portal.registerChecker(DirectoryCredentialsChecker())
+        portal.registerChecker(directory)
         print "Using directory-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_KERBEROS:
         if authenticator.type == "basic":
@@ -285,24 +301,24 @@
         if self.quota <= 0:
             self.quota = None
         
-    def buildFromFile(self, file):
+    def buildFromFile(self, filename, directory):
         """
         Parse the required information from an XML file.
         @param file: the path of the XML file to parse.
         """
         # Read in XML
-        fd = open(file, "r")
+        fd = open(filename, "r")
         doc = xml.dom.minidom.parse( fd )
         fd.close()
 
         # Verify that top-level element is correct
         repository_node = doc._get_documentElement()
         if repository_node._get_localName() != ELEMENT_REPOSITORY:
-            self.log("Ignoring file \"%s\" because it is not a repository builder file" % (file,))
+            self.log("Ignoring file %r because it is not a repository builder file" % (filename,))
             return
         self.parseXML(repository_node)
         
-        self.docRoot.build()
+        self.docRoot.build(directory)
         if self.doAccounts:
             self.accounts.provision(
                 self.docRoot.principalCollections,
@@ -358,11 +374,11 @@
                 self.collection.parseXML(child, self)
                 break
 
-    def build(self):
+    def build(self, directory):
         """
         Build the entire repository starting at the root resource.
         """
-        self.collection.build(self.path, "/")
+        self.collection.build(self.path, "/", directory)
         
         # Setup the principal-collection-set property if required
         if self.autoPrincipalCollectionSet:
@@ -384,7 +400,7 @@
     """
     def __init__(self):
         self.name = None
-        self.pytype = "twistedcaldav.static.CalDAVFile" # FIXME: Why not None?
+        self.pytype = None
         self.params = {}
         self.properties = []
         self.acl = None
@@ -466,7 +482,7 @@
                 self.properties.append(Prop())
                 self.properties[-1].parseXML(child)
 
-    def build(self, docroot, urlroot):
+    def build(self, docroot, urlroot, directory):
         """
         Create this collection, initialising any properties and then create any child
         collections.
@@ -490,14 +506,19 @@
         kwargs = {}
         argnames = resource_class.__init__.func_code.co_varnames
         for name, value in (
-            ("path", mypath),
-            ("url" , myurl ),
+            ("path"     , mypath   ),
+            ("url"      , myurl    ),
+            ("directory", directory),
         ):
             if name in argnames:
                 kwargs[name] = value
         if self.params:
             kwargs["params"] = self.params
-        self.resource = resource_class(**kwargs)
+        try:
+            self.resource = resource_class(**kwargs)
+        except Exception:
+            log.err("Unable to instantiate Python class %r with arguments %r" % (resource_class, kwargs))
+            raise
 
         self.uri = myurl
         
@@ -510,7 +531,7 @@
             self.resource.setAccessControlList(self.acl.acl)
 
         for member in self.members:
-            child = member.build(mypath, myurl)
+            child = member.build(mypath, myurl, directory)
             # Only putChild if one does not already exists
             if self.resource.putChildren.get(member.name, None) is None:
                 self.resource.putChild(member.name, child)
@@ -704,10 +725,10 @@
                     self.calendarHome.resource,
                 )
 
-        # Check for proper account home
-        if not self.accountCollection:
-            log.err("Accounts cannot be created: no principal collection was marked with an account attribute.")
-            raise ValueError, "Accounts cannot be created."
+#        # Check for proper account home
+#        if not self.accountCollection:
+#            log.err("Accounts cannot be created: no principal collection was marked with an account attribute.")
+#            raise ValueError, "Accounts cannot be created."
 
         # Provision each user
         for repeat, principal in self.items:
@@ -717,44 +738,6 @@
                 for ctr in range(1, repeat+1):
                     self.provisionOne(principal.repeat(ctr), resetACLs)
     
-    def provisionOne(self, item, resetACLs):
-        """
-        Provision one user account/
-        @param item:      The account to provision.
-        @param resetACLs: if True, ACL privileges on all resources related to the
-            accounts being created are reset, if False no ACL privileges are changed.
-        """
-        principalURL = joinURL(self.accountCollection.uri, item.uid)
-
-        # Create principal resource
-        principal = FilePath(os.path.join(self.accountCollection.resource.fp.path, item.uid))
-        principal_exists = principal.exists()
-        if not principal_exists:
-            principal.open("w").close()
-            log.msg("Created principal: %s" % principalURL)
-        principal = CalendarPrincipalFile(principal.path, principalURL)
-        
-        # Special case: if we have an explicit cuhome URL, we will use that,
-        # otherwise we fall back to the inferred home and resource
-        if item.cuhome:
-            cuhome = (item.cuhome, None)
-        else:
-            cuhome = (self.calendarHome.uri, self.calendarHome.resource)
-            
-        # Principal knows how to provision itself in the appropriate manner
-        principal.provisionCalendarAccount(
-            item.name,
-            item.pswd,
-            resetACLs or not principal_exists,
-            item.cuaddrs,
-            cuhome,
-            item.acl,
-            item.quota,
-            item.calendars,
-            item.autorespond,
-            True
-            )
-
 class ProvisionPrincipal (object):
     """
     Contains provision information for one user.

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -33,8 +33,6 @@
     "isScheduleOutboxResource",
 ]
 
-from weakref import WeakValueDictionary
-
 from zope.interface import implements
 
 from twisted.internet import reactor
@@ -43,7 +41,8 @@
 from twisted.python import log
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource
+from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
+from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource, DAVPrincipalCollectionResource
 from twisted.web2.dav.davxml import dav_namespace
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.resource import TwistedACLInheritable
@@ -233,7 +232,7 @@
 
         return super(CalDAVResource, self).accessControlList(*args, **kwargs)
 
-    def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
+    def authorizationPrincipal(self, request, authid, authnPrincipal):
         """
         Determine the authorization principal for the given request and authentication principal.
         This implementation looks for an X-Authorize-As header value to use as the authoization principal.
@@ -242,10 +241,10 @@
         @param authid: a string containing the uthentication/authorization identifier
             for the principal to lookup.
         @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
-        @param authnURI: a C{str} containing the URI of the authenticated principal
         @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
             resource and URI respectively.
         """
+        # FIXME: Unroll defgen
 
         # Look for X-Authorize-As Header
         authz = request.headers.getRawHeaders("x-authorize-as")
@@ -260,15 +259,13 @@
                     log.msg("Cannot proxy as another proxy: user '%s' as user '%s'" % (authid, authz))
                     raise HTTPError(responsecode.UNAUTHORIZED)
                 else:
-                    d = waitForDeferred(self.findPrincipalForAuthID(request, authz))
-                    yield d
-                    result = d.getResult()
+                    authzPrincipal = waitForDeferred(self.findPrincipalForAuthID(request, authz))
+                    yield authzPrincipal
+                    authzPrincipal = authzPrincipal.getResult()
 
-                    if result is not None:
+                    if authzPrincipal is not None:
                         log.msg("Allow proxy: user '%s' as '%s'" % (authid, authz,))
-                        authzPrincipal = result[0]
-                        authzURI = result[1]
-                        yield authzPrincipal, authzURI
+                        yield authzPrincipal
                         return
                     else:
                         log.msg("Could not find proxy user id: '%s'" % authid)
@@ -281,7 +278,7 @@
             raise HTTPError(responsecode.UNAUTHORIZED)
         else:
             # No proxy - do default behavior
-            d = waitForDeferred(super(CalDAVResource, self).authorizationPrincipal(request, authid, authnPrincipal, authnURI))
+            d = waitForDeferred(super(CalDAVResource, self).authorizationPrincipal(request, authid, authnPrincipal))
             yield d
             yield d.getResult()
             return
@@ -491,13 +488,11 @@
         """
         return request.locateResource(parentForURL(uri))
 
-class CalendarPrincipalCollectionResource (CalDAVResource):
+class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
     """
     CalDAV principal collection.
     """
-    # Use a WeakKeyDictionary to keep track of all instances.
-    # A WeakKeySet would be more appropriate, but there is no such class yet.
-    principleCollectionSet = WeakValueDictionary()
+    implements(IDAVPrincipalCollectionResource)
 
     @classmethod
     def outboxForCalendarUser(clazz, request, address):
@@ -539,13 +534,6 @@
         d.addCallback(_defer)
         return d
 
-    def __init__(self, url):
-        self._url = url
-
-        # Register self with class
-        if url not in CalendarPrincipalCollectionResource.principleCollectionSet:
-            CalendarPrincipalCollectionResource.principleCollectionSet[url] = self
-
     def isCollection(self):
         return True
 
@@ -580,9 +568,6 @@
 
     findCalendarUser = deferredGenerator(findCalendarUser)
 
-    def principalCollectionURL(self):
-        return self._url
-
     def supportedReports(self):
         """
         Principal collections are the only resources supporting the
@@ -592,6 +577,29 @@
         result.append(davxml.Report(davxml.PrincipalSearchPropertySet(),))
         return result
 
+    def principalSearchPropertySet(self):
+        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"}
+                ),
+            ),
+        )
+
+# FIXME: Replace this
 def findAnyCalendarUser(request, address):
     """
     Find the calendar user principal associated with the specified calendar
@@ -601,25 +609,18 @@
     @return: the L{CalendarPrincipalResource} for the specified calendar
         user, or C{None} if the user is not found.
     """
-    for url in CalendarPrincipalCollectionResource.principleCollectionSet.keys():
-        try:
-            # Explicitly locate the prinicpal collection resource to force URL caching in request
-            pcollection = waitForDeferred(request.locateResource(url))
-            yield pcollection
-            pcollection = pcollection.getResult()
+    for collection in self.principalCollections():
+        if isinstance(collection, CalendarPrincipalCollectionResource):
+            principal = waitForDeferred(collection.findCalendarUser(request, address))
+            yield principal
+            principal = principal.getResult()
 
-            if isinstance(pcollection, CalendarPrincipalCollectionResource):
-                principal = waitForDeferred(pcollection.findCalendarUser(request, address))
+            if principal is not None:
                 yield principal
-                principal = principal.getResult()
-                if principal is not None:
-                    yield principal
-                    return
-        except ReferenceError:
-            pass
+                return
+    else:
+        yield None
 
-    yield None
-
 findAnyCalendarUser = deferredGenerator(findAnyCalendarUser)
 
 class CalendarPrincipalResource (DAVPrincipalResource):
@@ -655,9 +656,9 @@
                     )
 
                 if name == "calendar-user-address-set":
-                    return caldavxml.CalendarUserAddressSet(
-                        *[davxml.HRef(url) for url in self.calendarHomeURLs()]
-                    )
+                    return succeed(caldavxml.CalendarUserAddressSet(
+                        *[davxml.HRef(uri) for uri in self.calendarUserAddresses()]
+                    ))
 
                 if name == "schedule-inbox-URL":
                     url = self.scheduleInboxURL()

Copied: CalendarServer/trunk/twistedcaldav/sql.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sql.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sql.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,213 @@
+##
+# Copyright (c) 2005-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 SQL database access object.
+"""
+
+__all__ = [
+    "AbstractSQLDatabase",
+]
+
+import os
+
+from pysqlite2 import dbapi2 as sqlite
+
+from twisted.python import log
+
+class AbstractSQLDatabase(object):
+    """
+    A generic SQL database.
+    """
+
+    def __init__(self, dbpath, version):
+        """
+        @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+            index. C{resource} must be a calendar collection (ie.
+            C{resource.isPseudoCalendarCollection()} returns C{True}.)
+        """
+        self.dbpath = dbpath
+        self.version = version
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        raise NotImplementedError
+        
+    def _db(self):
+        """
+        Access the underlying database.
+        @return: a db2 connection object for this index's underlying data store.
+        """
+        if not hasattr(self, "_db_connection"):
+            db_filename = self.dbpath
+            self._db_connection = sqlite.connect(db_filename)
+
+            #
+            # Set up the schema
+            #
+            q = self._db_connection.cursor()
+            try:
+                # Create CALDAV table if needed
+                q.execute(
+                    """
+                    select (1) from SQLITE_MASTER
+                     where TYPE = 'table' and NAME = 'CALDAV'
+                    """)
+                caldav = q.fetchone()
+
+                if caldav:
+                    q.execute(
+                        """
+                        select VALUE from CALDAV
+                         where KEY = 'SCHEMA_VERSION'
+                        """)
+                    version = q.fetchone()
+
+                    if version is not None: version = version[0]
+
+                    q.execute(
+                        """
+                        select VALUE from CALDAV
+                         where KEY = 'TYPE'
+                        """)
+                    type = q.fetchone()
+
+                    if type is not None: type = type[0]
+
+                    if (version != self.version) or (type != self._db_type()):
+                        if version != self.version:
+                            log.err("Database %s has different schema (v.%s vs. v.%s)"
+                                    % (db_filename, version, self.version))
+                        if type != self._db_type():
+                            log.err("Database %s has different type (%s vs. %s)"
+                                    % (db_filename, type, self._db_type()))
+
+                        # Delete this index and start over
+                        q.close()
+                        q = None
+                        self._db_connection.close()
+                        del(self._db_connection)
+                        os.remove(db_filename)
+                        return self._db()
+
+                else:
+                    self._db_init(db_filename, q)
+
+                self._db_connection.commit()
+            finally:
+                if q is not None: q.close()
+        return self._db_connection
+
+    def _db_init(self, db_filename, q):
+        """
+        Initialise the underlying database tables.
+        @param db_filename: the file name of the index database.
+        @param q:           a database cursor to use.
+        """
+        log.msg("Initializing database %s" % (db_filename,))
+
+        self._db_init_schema_table(q)
+        self._db_init_data_tables(q)
+
+    def _db_init_schema_table(self, q):
+        """
+        Initialise the underlying database tables.
+        @param db_filename: the file name of the index database.
+        @param q:           a database cursor to use.
+        """
+
+        #
+        # CALDAV table keeps track of our schema version and type
+        #
+        q.execute(
+            """
+            create table CALDAV (
+                KEY text unique, VALUE text unique
+            )
+            """
+        )
+        q.execute(
+            """
+            insert into CALDAV (KEY, VALUE)
+            values ('SCHEMA_VERSION', :1)
+            """, [self.version]
+        )
+        q.execute(
+            """
+            insert into CALDAV (KEY, VALUE)
+            values ('TYPE', :1)
+            """, [self._db_type()]
+        )
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param db_filename: the file name of the index database.
+        @param q:           a database cursor to use.
+        """
+        raise NotImplementedError
+
+    def _db_values_for_sql(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain the resulting values.
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: an interable of values in the first column of each row
+            resulting from executing C{sql} with C{query_params}.
+        @raise AssertionError: if the query yields multiple columns.
+        """
+        return (row[0] for row in self._db_execute(sql, *query_params))
+
+    def _db_value_for_sql(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain a single value.
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: the value resulting from the executing C{sql} with
+            C{query_params}.
+        @raise AssertionError: if the query yields multiple rows or columns.
+        """
+        value = None
+        for row in self._db_values_for_sql(sql, *query_params):
+            assert value is None, "Multiple values in DB for %s %s" % (sql, query_params)
+            value = row
+        return value
+
+    def _db_execute(self, sql, *query_params):
+        """
+        Execute an SQL query and obtain the resulting values.
+        @param sql: the SQL query to execute.
+        @param query_params: parameters to C{sql}.
+        @return: an interable of tuples for each row resulting from executing
+            C{sql} with C{query_params}.
+        """
+        q = self._db().cursor()
+        try:
+            try:
+                q.execute(sql, query_params)
+            except:
+                log.err("Exception while executing SQL: %r %r" % (sql, query_params))
+                raise
+            return q.fetchall()
+        finally:
+            q.close()
+
+    def _db_commit  (self): self._db_connection.commit()
+    def _db_rollback(self): self._db_connection.rollback()

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/static.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -26,13 +26,11 @@
     "ScheduleOutboxFile",
     "CalendarHomeFile",
     "CalendarHomeProvisioningFile",
-    "CalendarPrincipalFile",
     "CalendarPrincipalCollectionFile",
 ]
 
 import os
 import errno
-from posixpath import basename
 from urlparse import urlsplit
 
 from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
@@ -45,25 +43,25 @@
 from twisted.web2.dav.fileop import mkcollection, rmdir
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.idav import IDAVResource
-from twisted.web2.dav.resource import TwistedACLInheritable
-from twisted.web2.dav.resource import TwistedQuotaRootProperty
+from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty, davPrivilegeSet
 from twisted.web2.dav.util import parentForURL, joinURL, bindMethods
 
 from twistedcaldav import caldavxml
 from twistedcaldav import customxml
+from twistedcaldav.extensions import ReadOnlyResourceMixIn
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import Property as iProperty
 from twistedcaldav.index import Index, IndexSchedule, db_basename
 from twistedcaldav.resource import CalDAVResource, isNonCalendarCollectionParentResource, CalendarPrincipalResource
-from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource, CalendarPrincipalCollectionResource
+from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.extensions import DAVFile
+from twistedcaldav.directory.idirectory import IDirectoryService
 
 class CalDAVFile (CalDAVResource, DAVFile):
     """
     CalDAV-accessible L{DAVFile} resource.
     """
-
     def __repr__(self):
         if self.isCalendarCollection():
             return "<%s (calendar collection): %s>" % (self.__class__.__name__, self.fp.path)
@@ -212,65 +210,13 @@
         return caldavxml.CalendarData.fromCalendarData(self.iCalendarText(name))
 
     def supportedPrivileges(self, request):
-        if not hasattr(CalDAVFile, "_supportedCalendarPrivilegeSet"):
-            CalDAVFile._supportedCalendarPrivilegeSet = davxml.SupportedPrivilegeSet(
-                davxml.SupportedPrivilege(
-                    davxml.Privilege(davxml.All()),
-                    davxml.Description("all privileges", **{"xml:lang": "en"}),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Description("read resource", **{"xml:lang": "en"}),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(caldavxml.ReadFreeBusy()),
-                            davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
-                        ),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Write()),
-                        davxml.Description("write resource", **{"xml:lang": "en"}),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteProperties()),
-                            davxml.Description("write resource properties", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteContent()),
-                            davxml.Description("write resource content", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Bind()),
-                            davxml.Description("add child resource", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Unbind()),
-                            davxml.Description("remove child resource", **{"xml:lang": "en"}),
-                        ),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Unlock()),
-                        davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadACL()),
-                        davxml.Description("read resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.WriteACL()),
-                        davxml.Description("write resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
-                    ),
-                ),
-            )
-            
         # read-free-busy support on calendar collection and calendar object resources
         if self.isCollection():
-            return succeed(CalDAVFile._supportedCalendarPrivilegeSet)
+            return succeed(calendarPrivilegeSet)
         else:
             def _callback(parent):
                 if parent and isCalendarCollectionResource(parent):
-                    return succeed(CalDAVFile._supportedCalendarPrivilegeSet)
+                    return succeed(calendarPrivilegeSet)
                 else:
                     return super(CalDAVFile, self).supportedPrivileges(request)
 
@@ -280,7 +226,6 @@
         
         return super(CalDAVFile, self).supportedPrivileges(request)
 
-
     ##
     # Public additions
     ##
@@ -413,13 +358,21 @@
     
     _checkParents = deferredGenerator(_checkParents)
 
-class ScheduleInboxFile (ScheduleInboxResource, CalDAVFile):
-    """
-    L{CalDAVFile} calendar inbox collection resource.
-    """
-    def __repr__(self):
-        return "<%s (calendar inbox collection): %s>" % (self.__class__.__name__, self.fp.path)
+class ScheduleFile (CalDAVFile):
+    def __init__(self, path, parent):
+        super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
+        self._parent = parent
+        
+        self.provision()
 
+    def provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.makedirs()
+            self.fp.restat(False)
+
     def index(self):
         """
         Obtains the index for an schedule collection resource.
@@ -430,6 +383,7 @@
         return IndexSchedule(self)
 
     def createSimilarFile(self, path):
+        self.provision()
         if path == self.fp.path:
             return self
         else:
@@ -441,499 +395,311 @@
     def http_MKCOL      (self, request): return responsecode.FORBIDDEN
     def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
 
+    ##
+    # ACL
+    ##
+
     def supportedPrivileges(self, request):
-        if not hasattr(ScheduleInboxFile, "_supportedSchedulePrivilegeSet"):
-            ScheduleInboxFile._supportedSchedulePrivilegeSet = davxml.SupportedPrivilegeSet(
-                davxml.SupportedPrivilege(
-                    davxml.Privilege(davxml.All()),
-                    davxml.Description("all privileges", **{"xml:lang": "en"}),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Description("read resource", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Write()),
-                        davxml.Description("write resource", **{"xml:lang": "en"}),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteProperties()),
-                            davxml.Description("write resource properties", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteContent()),
-                            davxml.Description("write resource content", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Bind()),
-                            davxml.Description("add child resource", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Unbind()),
-                            davxml.Description("remove child resource", **{"xml:lang": "en"}),
-                        ),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Unlock()),
-                        davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadACL()),
-                        davxml.Description("read resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.WriteACL()),
-                        davxml.Description("write resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(caldavxml.Schedule()),
-                        davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
-                    ),
-                ),
-            )
-            
-        return succeed(ScheduleInboxFile._supportedSchedulePrivilegeSet)
+        return succeed(schedulePrivilegeSet)
 
-class ScheduleOutboxFile (ScheduleOutboxResource, CalDAVFile):
+class ScheduleInboxFile (ScheduleInboxResource, ScheduleFile):
     """
-    L{CalDAVFile} calendar outbox collection resource.
+    L{CalDAVFile} calendar inbox collection resource.
     """
     def __repr__(self):
-        return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
+        return "<%s (calendar inbox collection): %s>" % (self.__class__.__name__, self.fp.path)
 
-    def index(self):
-        """
-        Obtains the index for an iTIP collection resource.
-        @return: the index object for this resource.
-        @raise AssertionError: if this resource is not a calendar collection
-            resource.
-        """
-        return IndexSchedule(self)
+    def provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.makedirs()
+            self.fp.restat(False)
 
-    def createSimilarFile(self, path):
-        if path == self.fp.path:
-            return self
-        else:
-            return CalDAVFile(path)
+            # FIXME: This should probably be a directory record option that
+            # maps to the property value directly without the need to store one.
+            if self._parent.record.recordType == "resource":
+                # Resources should have autorespond turned on by default,
+                # since they typically don't have someone responding for them.
+                self.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
 
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-    def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+    ##
+    # ACL
+    ##
 
-    def supportedPrivileges(self, request):
-        if not hasattr(ScheduleOutboxFile, "_supportedSchedulePrivilegeSet"):
-            ScheduleOutboxFile._supportedSchedulePrivilegeSet = davxml.SupportedPrivilegeSet(
-                davxml.SupportedPrivilege(
-                    davxml.Privilege(davxml.All()),
-                    davxml.Description("all privileges", **{"xml:lang": "en"}),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Description("read resource", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Write()),
-                        davxml.Description("write resource", **{"xml:lang": "en"}),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteProperties()),
-                            davxml.Description("write resource properties", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.WriteContent()),
-                            davxml.Description("write resource content", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Bind()),
-                            davxml.Description("add child resource", **{"xml:lang": "en"}),
-                        ),
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(davxml.Unbind()),
-                            davxml.Description("remove child resource", **{"xml:lang": "en"}),
-                        ),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.Unlock()),
-                        davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadACL()),
-                        davxml.Description("read resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.WriteACL()),
-                        davxml.Description("write resource access control list", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
-                    ),
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(caldavxml.Schedule()),
-                        davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
-                    ),
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # CalDAV:schedule for any authenticated user
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(caldavxml.Schedule()),
                 ),
-            )
-            
-        return succeed(ScheduleOutboxFile._supportedSchedulePrivilegeSet)
+            ),
+        )
 
-class CalendarHomeFile (CalDAVFile):
+class ScheduleOutboxFile (ScheduleOutboxResource, ScheduleFile):
     """
-    L{CalDAVFile} calendar home collection resource.
+    L{CalDAVFile} calendar outbox collection resource.
     """
-    
-    # A global quota limit for all calendar homes. Either a C{int} (size in bytes) to limit
-    # quota to that size, or C{None} for no limit.
-    quotaLimit = None
+    def __repr__(self):
+        return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
 
-    def __init__(self, path):
+class CalendarHomeProvisioningFile (ReadOnlyResourceMixIn, DAVFile):
+    """
+    L{CalDAVFile} resource which provisions calendar home collections as needed.    
+    """
+    def __init__(self, path, directory, url):
         """
         @param path: the path to the file which will back the resource.
+        @param directory: an L{IDirectoryService} to provision calendars from.
         """
-        super(CalendarHomeFile, self).__init__(path)
+        assert url.endswith("/"), "Collection URL must end in '/'"
 
-        assert self.exists(), "%s should exist" % (self,)
-        assert self.isCollection(), "%s should be a collection" % (self,)
+        super(CalendarHomeProvisioningFile, self).__init__(path)
 
+        self.directory = IDirectoryService(directory)
+        self._url = url
+
+        # FIXME: Smells like a hack
+        directory.calendarHomesCollection = self
+
+        # self.provision()
+
         # Create children
-        for name, clazz in (
-            ("inbox" , ScheduleInboxFile),
-            ("outbox", ScheduleOutboxFile),
-        ):
-            child_fp = self.fp.child(name)
-            if not child_fp.exists(): child_fp.makedirs()
-            self.putChild(name, clazz(child_fp.path))
+        for recordType in self.directory.recordTypes():
+            self.putChild(recordType, CalendarHomeTypeProvisioningFile(self.fp.child(recordType).path, self, recordType))
 
+    # def provision(self):
+    #     self.fp.restat(False)
+    #     if not self.fp.exists():
+    #         self.fp.makedirs()
+    #         self.fp.restat(False)
+
+    def url(self):
+        return self._url
+
     def createSimilarFile(self, path):
-        if path == self.fp.path:
-            return self
-        else:
-            return CalDAVFile(path)
+        raise HTTPError(responsecode.NOT_FOUND)
 
     def getChild(self, name):
-        # This avoids finding case variants of put children on case-insensitive filesystems.
-        if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
+        # self.provision()
+
+        children = self.putChildren
+        if name not in children and name.lower() in (x.lower() for x in children):
+            # This avoids finding case variants of put children on case-insensitive filesystems.
             return None
+        else:
+            return children.get(name, None)
 
-        return super(CalendarHomeFile, self).getChild(name)
+    def listChildren(self):
+        return self.directory.recordTypes()
 
+    def principalCollections(self):
+        # FIXME: directory.principalCollection smells like a hack
+        # See DirectoryPrincipalProvisioningResource.__init__()
+        return self.directory.principalCollection.principalCollections()
+
+    def homeForDirectoryRecord(self, record):
+        return self.getChild(record.recordType).getChild(record.shortName)
+
     ##
-    # Quota
+    # ACL
     ##
 
-    def hasQuotaRoot(self, request):
-        """
-        @return: a C{True} if this resource has quota root, C{False} otherwise.
-        """
-        return self.hasDeadProperty(TwistedQuotaRootProperty) or CalendarHomeFile.quotaLimit is not None
-    
-    def quotaRoot(self, request):
-        """
-        @return: a C{int} containing the maximum allowed bytes if this collection
-            is quota-controlled, or C{None} if not quota controlled.
-        """
-        if self.hasDeadProperty(TwistedQuotaRootProperty):
-            return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
-        else:
-            return CalendarHomeFile.quotaLimit
+    def defaultAccessControlList(self):
+        return readOnlyACL
 
-class CalendarHomeProvisioningFile (CalDAVFile):
+class CalendarHomeTypeProvisioningFile (ReadOnlyResourceMixIn, DAVFile):
     """
-    L{CalDAVFile} resource which provisions calendar home collections as needed.
+    L{CalDAVFile} resource which provisions calendar home collections of a specific
+    record type as needed.
     """
-    calendarHomeClass = CalendarHomeFile
-
-    def __init__(self, path):
+    def __init__(self, path, parent, recordType):
         """
         @param path: the path to the file which will back the resource.
+        @param directory: an L{IDirectoryService} to provision calendars from.
+        @param recordType: the directory record type to provision.
         """
-        super(CalendarHomeProvisioningFile, self).__init__(path)
+        super(CalendarHomeTypeProvisioningFile, self).__init__(path)
 
-    def hasChild(self, name):
-        """
-        @return: C{True} if this resource has a child with the given name,
-            C{False} otherwise.
-        """
-        return name in self.listChildren()
+        self.directory = parent.directory
+        self.recordType = recordType
+        self._parent = parent
 
-    def locateChild(self, request, segments):
-        return locateExistingChild(self, request, segments)
+        self.provision()
 
-    def getChild(self, name):
-        if name == "": return self
+    def provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.makedirs()
+            self.fp.restat(False)
 
-        # Avoid case variants when allocating resources
-        if not self.hasChild(name):
-            return None
+    def url(self):
+        return joinURL(self._parent.url(), self.recordType)
 
-        child_fp = self.fp.child(name)
-        if child_fp.exists():
-            assert child_fp.isdir()
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    def getChild(self, name, record=None):
+        self.provision()
+
+        if name == "":
+            return self
+
+        if record is None:
+            record = self.directory.recordWithShortName(self.recordType, name)
+            if record is None:
+                return None
         else:
-            assert self.exists()
-            assert self.isCollection()
+            assert name is None
+            name = record.shortName
 
-            child_fp.makedirs()
+        return CalendarHomeFile(self.fp.child(name).path, self, record)
 
-        return self.calendarHomeClass(child_fp.path)
+    def listChildren(self):
+        return (record.shortName for record in self.directory.listRecords(self.recordType))
 
-    def createSimilarFile(self, path):
-        raise NotImplementedError("Not allowed")
+    ##
+    # ACL
+    ##
 
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-    def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+    def defaultAccessControlList(self):
+        return readOnlyACL
 
-class CalendarPrincipalFile (CalendarPrincipalResource, CalDAVFile):
+    def principalCollections(self):
+        return self._parent.principalCollections()
+
+class CalendarHomeFile (CalDAVFile):
     """
-    Calendar principal resource.
+    L{CalDAVFile} calendar home collection resource.
     """
-    def __init__(self, path, url):
+    # A global quota limit for all calendar homes. Either a C{int} (size in bytes) to limit
+    # quota to that size, or C{None} for no limit.
+    quotaLimit = None
+
+    def __init__(self, path, parent, record):
         """
         @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(CalendarPrincipalFile, self).__init__(path)
+        super(CalendarHomeFile, self).__init__(path)
 
-        self._url = url
+        self.record = record
+        self._parent = parent
 
-    def createSimilarFile(self, path):
-        return self.__class__(path, self._url)
+        self.provision()
 
-    ##
-    # ACL
-    ##
+        # Cache children which must be of a specific type
+        for name, cls in (
+            ("inbox" , ScheduleInboxFile),
+            ("outbox", ScheduleOutboxFile),
+        ):
+            self.putChild(name, cls(self.fp.child(name).path, self))
 
-    def alternateURIs(self):
-        return ()
+    def provision(self):
+        self.fp.restat(False)
+        if not self.fp.exists():
+            assert self._parent.exists()
+            assert self._parent.isCollection()
+            self.fp.makedirs()
+            self.fp.restat(False)
 
-    def principalURL(self):
-        return self._url
+            # # Create a calendar collection
+            # calendarURLs = []
+            # for calendar in ("calendar",):
+            #     childURL = joinURL(self.url(), calendar)
+            #     child = CalDAVFile(os.path.join(self.fp.path, calendar))
+            #     c = child.createCalendarCollection()
+            #     assert c.called # FIXME: (This is not valid!)
+            #     c = c.result
+            #     calendarURLs.append(childURL)
+            #     child.setAccessControlList(
+            #         davxml.ACL(
+            #             davxml.ACE(
+            #                 davxml.Principal(davxml.Authenticated()),
+            #                 davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+            #                 TwistedACLInheritable(),
+            #             ),
+            #         )
+            #     )
+            # 
+            # # Set calendar-free-busy-set on inbox
+            # inbox = self.getChild("inbox")
+            # inbox.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in calendarURLs]))
 
-    def groupMembers(self):
-        return ()
+            # Do drop box
+            if self.record.recordType == "user":
+                from twistedcaldav.dropbox import DropBox
+                DropBox.provision(self)
+        
+    def url(self):
+        return joinURL(self._parent.url(), self.record.shortName)
 
-    def groupMemberships(self):
-        return ()
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return CalDAVFile(path, principalCollections=self.principalCollections())
 
-    ##
-    # CalDAV
-    ##
+    def getChild(self, name):
+        self.provision()
 
-    def principalUID(self):
-        """
-        @return: the user id for this principal.
-        """
-        return self.fp.basename()
+        # This avoids finding case variants of put children on case-insensitive filesystems.
+        if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
+            return None
 
-    def provisionCalendarAccount(self, name, pswd, resetacl, cuaddrs, cuhome, cuhomeacls, quota, cals, autorespond, allowdropbox):
-        """
-        Provision the principal and a calendar account for it.
-        
-        @param name: C{str} name (uid) of principal.
-        @param pswd: C{str} password for BASIC authentication, or C{None}.
-        @param resetacl: C{True} if ACLs on the principal resource should be reset.
-        @param cuaddrs: C{list} list of calendar user addresses, or C{None}
-        @param cuhome: C{tuple} of (C{str} - URI of calendar home root, L{DAVResource} - resource of home root)
-        @param cuhomeacls: L{ACL} acls to use on calendar home when resetting ACLs, or C{None} to use default set.
-        @param cals: C{list} list of calendar names to create in the calendar home for this prinicpal.
-        @param autorespond: C{True} if iTIP auto-response is required, C{False} otherwise.
-        @param allowdropbox: C{True} if drop box should be enabled for this user is drop box is supproted, C{False} otherwise.
-        """
-        
-        if pswd:
-            self.writeDeadProperty(TwistedPasswordProperty.fromString(pswd))
-        else:
-            self.removeDeadProperty(TwistedPasswordProperty())
-        if name:
-            self.writeDeadProperty(davxml.DisplayName.fromString(name))
-        else:
-            self.removeDeadProperty(davxml.DisplayName())
-        if cuaddrs:
-            self.writeDeadProperty(caldavxml.CalendarUserAddressSet(*[davxml.HRef(addr) for addr in cuaddrs]))
-        else:
-            self.removeDeadProperty(caldavxml.CalendarUserAddressSet())
+        return super(CalendarHomeFile, self).getChild(name)
 
-        if resetacl:
-            self.setAccessControlList(
-                davxml.ACL(
-                    davxml.ACE(
-                        davxml.Principal(davxml.HRef.fromString(self._url)),
-                        davxml.Grant(
-                            davxml.Privilege(davxml.Read()),
-                        ),
-                    ),
-                )
-            )
+    ##
+    # ACL
+    ##
 
-        # If the user does not have any calendar user addresses we do not create a calendar home for them
-        if not cuaddrs and not cals:
-            return
+    def defaultAccessControlList(self):
+        # FIXME: directory.principalCollection smells like a hack
+        # See DirectoryPrincipalProvisioningResource.__init__()
+        myPrincipal = self._parent._parent.directory.principalCollection.principalForRecord(self.record)
 
-        # Create calendar home if we already have the resource, otherwise simply record
-        # the URL as the calendar-home-set
-        if cuhome[1] is None:
-            self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(cuhome[0])))
-        else:
-            homeURL = joinURL(cuhome[0], self.principalUID())
-            home = FilePath(os.path.join(cuhome[1].fp.path, self.principalUID()))
-            home_exists = home.exists()
-            if not home_exists:
-                home.createDirectory()
-            home = CalendarHomeFile(home.path)
-    
-            if resetacl or not home_exists:
-                if cuhomeacls:
-                    home.setAccessControlList(cuhomeacls.acl)
-                else:
-                    home.setAccessControlList(
-                        davxml.ACL(
-                            davxml.ACE(
-                                davxml.Principal(davxml.Authenticated()),
-                                davxml.Grant(
-                                    davxml.Privilege(davxml.Read()),
-                                ),
-                            ),
-                            davxml.ACE(
-                                davxml.Principal(davxml.HRef.fromString(self._url)),
-                                davxml.Grant(
-                                    davxml.Privilege(davxml.All()),
-                                ),
-                                TwistedACLInheritable(),
-                            ),
-                        )
-                    )
-            
-            # Handle quota on calendar home
-            home.setQuotaRoot(None, quota)
-        
-            # Save the calendar-home-set, schedule-inbox and schedule-outbox properties
-            self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(homeURL + "/")))
-            self.writeDeadProperty(caldavxml.ScheduleInboxURL(davxml.HRef.fromString(joinURL(homeURL, "inbox/"))))
-            self.writeDeadProperty(caldavxml.ScheduleOutboxURL(davxml.HRef.fromString(joinURL(homeURL, "outbox/"))))
-            
-            # Set ACLs on inbox and outbox
-            if resetacl or not home_exists:
-                inbox = home.getChild("inbox")
-                inbox.setAccessControlList(
-                    davxml.ACL(
-                        davxml.ACE(
-                            davxml.Principal(davxml.Authenticated()),
-                            davxml.Grant(
-                                davxml.Privilege(caldavxml.Schedule()),
-                            ),
-                        ),
-                    )
-                )
-                if autorespond:
-                    inbox.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
-    
-                outbox = home.getChild("outbox")
-                if outbox.hasDeadProperty(davxml.ACL()):
-                    outbox.removeDeadProperty(davxml.ACL())
-    
-            calendars = []
-            for calendar in cals:
-                childURL = joinURL(homeURL, calendar)
-                child = CalDAVFile(os.path.join(home.fp.path, calendar))
-                child_exists = child.exists()
-                if not child_exists:
-                    c = child.createCalendarCollection()
-                    assert c.called
-                    c = c.result
-                calendars.append(childURL)
-                if (resetacl or not child_exists):
-                    child.setAccessControlList(
-                        davxml.ACL(
-                            davxml.ACE(
-                                davxml.Principal(davxml.Authenticated()),
-                                davxml.Grant(
-                                    davxml.Privilege(caldavxml.ReadFreeBusy()),
-                                ),
-                                TwistedACLInheritable(),
-                            ),
-                        )
-                    )
-            
-            # Set calendar-free-busy-set on Inbox if not already present
-            inbox = home.getChild("inbox")
-            if not inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet()):
-                fbset = caldavxml.CalendarFreeBusySet(*[davxml.HRef.fromString(uri) for uri in calendars])
-                inbox.writeDeadProperty(fbset)
-                
-            # Do drop box if requested
-            if allowdropbox:
-                from twistedcaldav.dropbox import DropBox
-                DropBox.provision(self, (homeURL, home))
+        return davxml.ACL(
+            # DAV:read access for authenticated users.
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(davxml.Privilege(davxml.Read())),
+            ),
+            # Inheritable DAV:all access for the resource's associated principal.
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
+                davxml.Grant(davxml.Privilege(davxml.All())),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ),
+        )
 
+    def principalCollections(self):
+        return self._parent.principalCollections()
 
-class CalendarPrincipalCollectionFile (CalendarPrincipalCollectionResource, DAVFile):
-    """
-    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.
-        """
-        CalendarPrincipalCollectionResource.__init__(self, url)
-        DAVFile.__init__(self, path)
+    ##
+    # Quota
+    ##
 
-    def initialize(self, homeuri, home):
+    def hasQuotaRoot(self, request):
         """
-        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.
+        @return: a C{True} if this resource has quota root, C{False} otherwise.
         """
-        pass
+        return self.hasDeadProperty(TwistedQuotaRootProperty) or CalendarHomeFile.quotaLimit is not None
     
-    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, joinURL(self.principalCollectionURL(), basename(path)))
-
-    def principalSearchPropertySet(self):
+    def quotaRoot(self, request):
         """
-        See L{IDAVResource.principalSearchPropertySet}.
-        
-        This implementation returns None. Principal collection resources MUST override
-        and return their own suitable response.
-        
+        @return: a C{int} containing the maximum allowed bytes if this collection
+            is quota-controlled, or C{None} if not quota controlled.
         """
-        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"}
-                ),
-            ),
-        )
+        if self.hasDeadProperty(TwistedQuotaRootProperty):
+            return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
+        else:
+            return CalendarHomeFile.quotaLimit
 
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-    def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
-
 ##
 # Utilities
 ##
@@ -951,6 +717,83 @@
     # Otherwise, there is no child
     return (None, ())
 
+# DAV:read access for authenticated users.
+readOnlyACL = davxml.ACL(
+    davxml.ACE(
+        davxml.Principal(davxml.Authenticated()),
+        davxml.Grant(davxml.Privilege(davxml.Read())),
+        davxml.Protected(),
+    ),
+)
+
+def _schedulePrivilegeSet():
+    edited = False
+
+    top_supported_privileges = []
+
+    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+        all_privilege = supported_privilege.childOfType(davxml.Privilege)
+        if isinstance(all_privilege.children[0], davxml.All):
+            all_description = supported_privilege.childOfType(davxml.Description)
+            all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+            all_supported_privileges.append(
+                davxml.SupportedPrivilege(
+                    davxml.Privilege(caldavxml.Schedule()),
+                    davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
+                ),
+            )
+            top_supported_privileges.append(
+                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+            )
+            edited = True
+        else:
+            top_supported_privileges.append(supported_privilege)
+
+    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
+
+    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+schedulePrivilegeSet = _schedulePrivilegeSet()
+
+def _calendarPrivilegeSet ():
+    edited = False
+
+    top_supported_privileges = []
+
+    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+        all_privilege = supported_privilege.childOfType(davxml.Privilege)
+        if isinstance(all_privilege.children[0], davxml.All):
+            all_description = supported_privilege.childOfType(davxml.Description)
+            all_supported_privileges = []
+            for all_supported_privilege in supported_privilege.childrenOfType(davxml.SupportedPrivilege):
+                read_privilege = all_supported_privilege.childOfType(davxml.Privilege)
+                if isinstance(read_privilege.children[0], davxml.Read):
+                    read_description = all_supported_privilege.childOfType(davxml.Description)
+                    read_supported_privileges = list(all_supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+                    read_supported_privileges.append(
+                        davxml.SupportedPrivilege(
+                            davxml.Privilege(caldavxml.ReadFreeBusy()),
+                            davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
+                        ),
+                    )
+                    all_supported_privileges.append(
+                        davxml.SupportedPrivilege(read_privilege, read_description, *read_supported_privileges)
+                    )
+                    edited = True
+                else:
+                    all_supported_privileges.append(all_supported_privilege)
+            top_supported_privileges.append(
+                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+            )
+        else:
+            top_supported_privileges.append(supported_privilege)
+
+    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for calendarPrivilegeSet"
+
+    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+calendarPrivilegeSet = _calendarPrivilegeSet()
+
 ##
 # Attach methods
 ##

Modified: CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2006-12-01 19:18:19 UTC (rev 637)
@@ -46,7 +46,8 @@
         uri  = "/calendar_make"
         path = os.path.join(self.docroot, uri[1:])
 
-        if os.path.exists(path): rmdir(path)
+        if os.path.exists(path):
+            rmdir(path)
 
         def do_test(response):
             response = IResponse(response)
@@ -72,7 +73,8 @@
         uri  = "/calendar_prop"
         path = os.path.join(self.docroot, uri[1:])
 
-        if os.path.exists(path): rmdir(path)
+        if os.path.exists(path):
+            rmdir(path)
 
         def do_test(response):
             response = IResponse(response)
@@ -120,10 +122,10 @@
         mk = caldavxml.MakeCalendar(
             davxml.Set(
                 davxml.PropertyContainer(
-                    davxml.DisplayName.fromString("Lisa's Events"),
-                    caldavxml.CalendarDescription.fromString("Calendar restricted to events."), # FIXME: lang=en
+                    davxml.DisplayName("Lisa's Events"),
+                    caldavxml.CalendarDescription("Calendar restricted to events."), # FIXME: lang=en
                     caldavxml.SupportedCalendarComponentSet(caldavxml.CalendarComponent(name="VEVENT")),
-                    caldavxml.CalendarTimeZone.fromString(
+                    caldavxml.CalendarTimeZone(
 """BEGIN:VCALENDAR
 PRODID:-//Example Corp.//CalDAV Client//EN
 VERSION:2.0

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


More information about the calendarserver-changes mailing list