[CalendarServer-changes] [519] CalendarServer/branches/users/wsanchez/provisioning-2

source_changes at macosforge.org source_changes at macosforge.org
Fri Nov 17 16:58:59 PST 2006


Revision: 519
          http://trac.macosforge.org/projects/calendarserver/changeset/519
Author:   wsanchez at apple.com
Date:     2006-11-17 16:58:58 -0800 (Fri, 17 Nov 2006)

Log Message:
-----------
merge /branches/users/wsanchez/provisioning forward

Modified Paths:
--------------
    CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.dtd
    CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.xml
    CalendarServer/branches/users/wsanchez/provisioning-2/doc/Repository/Directory Schema.graffle
    CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.auth.patch
    CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
    CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.server.patch
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/idirectory.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/resource.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/extensions.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/repository.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/resource.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/static.py

Added Paths:
-----------
    CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py

Removed Paths:
-------------
    CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-proxy.xml
    CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-static.xml
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/cred.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py
    CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-proxy.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-proxy.xml	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-proxy.xml	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/conf/repository-static.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-static.xml	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository-static.xml	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/conf/repository.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.dtd	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.dtd	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/conf/repository.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.xml	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/conf/repository.xml	2006-11-18 00:58:58 UTC (rev 519)
@@ -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,45 @@
         </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>
+        <collection name="principals" tag="principals">
+          <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
           <properties>
             <acl>
               <ace>
                 <principal><authenticated/></principal>
                 <grant><privilege><read/></privilege></grant>
-                <protected/>
+				<inheritable/>
               </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>
+        <collection name="calendars">
+          <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
           <properties>
             <acl>
               <ace>
                 <principal><authenticated/></principal>
                 <grant><privilege><read/></privilege></grant>
+				<inheritable/>
                 <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>
       </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/branches/users/wsanchez/provisioning-2/doc/Repository/Directory Schema.graffle
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/doc/Repository/Directory Schema.graffle	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/doc/Repository/Directory Schema.graffle	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.auth.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.auth.patch	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.auth.patch	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-11-18 00:58:58 UTC (rev 519)
@@ -2,7 +2,16 @@
 ===================================================================
 --- twisted/web2/dav/resource.py	(revision 18545)
 +++ twisted/web2/dav/resource.py	(working copy)
-@@ -130,6 +130,8 @@
+@@ -44,6 +44,8 @@
+ 
+ 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
+@@ -130,6 +132,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 +20,7 @@
  
          (twisted_dav_namespace, "resource-class"),
      )
-@@ -166,6 +168,14 @@
+@@ -166,6 +170,14 @@
          if qname[0] == twisted_private_namespace:
              return succeed(False)
  
@@ -26,16 +35,7 @@
          return succeed(qname in self.liveProperties or self.deadProperties().contains(qname))
  
      def readProperty(self, property, request):
-@@ -253,7 +263,7 @@
- 
-                 if name == "principal-collection-set":
-                     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
- 
-                 def ifAllowed(privileges, callback):
-@@ -286,7 +296,33 @@
+@@ -286,7 +298,33 @@
                          d.addCallback(gotACL)
                          return d
                      return ifAllowed((davxml.ReadACL(),), callback)
@@ -69,7 +69,7 @@
              elif namespace == twisted_dav_namespace:
                  if name == "resource-class":
                      class ResourceClass (davxml.WebDAVTextElement):
-@@ -366,12 +402,26 @@
+@@ -366,12 +404,26 @@
          # FIXME: A set would be better here, that that's a python 2.4+ feature.
          qnames = list(self.liveProperties)
  
@@ -97,7 +97,7 @@
      def listAllprop(self, request):
          """
          Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -509,6 +559,9 @@
+@@ -509,6 +561,9 @@
              reactor.callLater(0, getChild)
  
          def checkPrivileges(child):
@@ -107,7 +107,7 @@
              if privileges is None:
                  return child
     
-@@ -517,14 +570,17 @@
+@@ -517,14 +572,17 @@
              return d
  
          def gotChild(child, childpath):
@@ -132,7 +132,7 @@
  
              reactor.callLater(0, getChild)
  
-@@ -535,10 +591,10 @@
+@@ -535,10 +593,10 @@
                  completionDeferred.callback(None)
              else:
                  childpath = joinURL(basepath, childname)
@@ -147,16 +147,22 @@
  
          getChild()
  
-@@ -564,7 +620,7 @@
+@@ -564,19 +622,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 +174,17 @@
                      response = UnauthorizedResponse(request.credentialFactories,
                                                      request.remoteAddr)
                  else:
-@@ -600,16 +656,22 @@
+@@ -587,7 +647,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 +660,22 @@
+ 
      def authenticate(self, request):
          def loginSuccess(result):
 -            request.user = result[1]
@@ -195,28 +210,29 @@
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -625,9 +687,11 @@
+@@ -625,9 +691,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 +702,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 +244,7 @@
  
      ##
      # ACL
-@@ -650,10 +715,10 @@
+@@ -650,10 +719,10 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -242,7 +258,7 @@
          else:
              return unauthenticatedPrincipal
  
-@@ -666,32 +731,26 @@
+@@ -666,33 +735,28 @@
          present on this resource, it tries to get it from the parent, unless it
          is the root or has no parent.
          """
@@ -259,6 +275,7 @@
  
 -            principalCollections = []
 +        myURL = request.urlForResource(self)
++        assert myURL is not None, "Resource %s was not looked up via request" % (self,)
 +        if myURL == "/":
 +            return succeed(())
  
@@ -288,11 +305,43 @@
 -
 -    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:
+@@ -1146,49 +1221,95 @@
+ 
          This implementation returns an empty set.
          """
 -
@@ -320,18 +369,15 @@
              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,))
++        def gotAuthn(authnPrincipal):
++            if authnPrincipal is None:
++                log.msg("Could not find the principal resource for user id: %s" % (authid,))
 +                raise HTTPError(responsecode.FORBIDDEN)
 +
-+            authnPrincipal, authnURI = principal
++            def gotAuthz(authzPrincipal):
++                return (authnPrincipal, authzPrincipal)
 +
-+            def gotAuthz(principal):
-+                authzPrincipal, authzURI = principal
-+                return ((authnPrincipal, authnURI), (authzPrincipal, authzURI))
-+
-+            d = self.authorizationPrincipal(request, authid, authnPrincipal, authnURI)
++            d = self.authorizationPrincipal(request, authid, authnPrincipal)
 +            d.addCallback(gotAuthz)
 +            return d
 +
@@ -353,24 +399,33 @@
 +            that is found; {principalURI} is the C{str} URI of the principal.
 +            If not found return None.
 +        """
++        # FIXME: should self.principalCollections() return resources instead of URIs?
++
          # Try to match principals in each principal collection on the resource
          collections = waitForDeferred(self.principalCollections(request))
          yield collections
          collections = collections.getResult()
  
-         for collection in collections:
+-        for collection in collections:
 -            principalURI = joinURL(str(collection), authid)
-+            principalURI = joinURL(collection, authid)
++        for collectionURI in collections:
++            collection = waitForDeferred(request.locateResource(collectionURI))
++            yield collection
++            collection = collection.getResult()
  
-             principal = waitForDeferred(request.locateResource(principalURI))
+-            principal = waitForDeferred(request.locateResource(principalURI))
++            assert collection is not None, "Unable to locate principal collection %s" % (collectionURI,)
++
++            # FIXME: collection = IPrincipalCollectionResource(collection)
++            principal = collection.principalForUser(authid)
++
              yield principal
-             principal = principal.getResult()
- 
+-            principal = principal.getResult()
+-
 -            if isPrincipalResource(principal):
 -                yield (principal, principalURI)
-+            if isPrincipalResource(principal) and principal.exists():
-+                yield principal, principalURI
-                 return
+-                return
++            return
          else:
 -            principalCollections = waitForDeferred(self.principalCollections(request))
 -            yield principalCollections
@@ -386,7 +441,7 @@
 -
      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 +450,15 @@
 +        @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.
 +        """
-+        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 @@
+@@ -1511,6 +1632,265 @@
          return None
  
      ##
@@ -670,7 +724,7 @@
      # HTTP
      ##
  
-@@ -1558,7 +1922,7 @@
+@@ -1558,7 +1938,7 @@
      """
      DAV resource with no children.
      """
@@ -679,7 +733,7 @@
          return succeed(None)
  
  class DAVPrincipalResource (DAVLeafResource):
-@@ -1712,6 +2076,37 @@
+@@ -1712,6 +2092,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Modified: CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-11-18 00:58:58 UTC (rev 519)
@@ -96,13 +96,71 @@
      # Workarounds for issues with File
      ##
  
-@@ -142,53 +191,50 @@
-         directory contents that they have read permissions for.
-         """
-         if not self.fp.exists():
+@@ -134,61 +183,58 @@
+     def createSimilarFile(self, path):
+         return self.__class__(path, defaultType=self.defaultType, indexNames=self.indexNames[:])
+ 
+-    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
++    # 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():
++    #         return responsecode.NOT_FOUND
++    # 
++    #     if not self.fp.isdir():
++    #         # Do regular resource behavior from superclass
++    #         return super(DAVFile, self).render(request)
++    # 
++    #     #
++    #     # Do custom rendering of directory so that we can enforce ACLs.
++    #     #
++    # 
++    #     if request.uri[-1] != "/":
++    #         # Redirect to include trailing '/' in URI
++    #         return RedirectResponse(request.unparseURL(path=request.path+'/'))
++    # 
++    #     # 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
++    #     def findChildren(filtered_aces):
++    #         children = []
++    # 
++    #         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
  
 -        if self.fp.isdir():
 -            if request.uri[-1] != "/":
@@ -119,32 +177,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 +200,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
  #

Modified: CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.server.patch	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.server.patch	2006-11-18 00:58:58 UTC (rev 519)
@@ -97,7 +97,7 @@
  
      def locateResource(self, url):
          """
-@@ -385,7 +396,8 @@
+@@ -385,8 +396,13 @@
              The contained response will have a status code of
              L{responsecode.BAD_REQUEST}.
          """
@@ -105,18 +105,18 @@
 +        if url is None:
 +            return defer.succeed(None)
  
++        cached = self._urlsByResource.get(url, None)
++        if cached is not None:
++            return defer.succeed(cached)
++
          #
          # Parse the URL
-@@ -406,18 +418,70 @@
+         #
+@@ -406,19 +422,66 @@
                  "URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
              ))
  
 -        segments = path.split("/")
-+        # Looked for cached value
-+        cached = self._urlsByResource.get(path, None)
-+        if cached is not None:
-+            return defer.succeed(cached)
-+
 +        segments = unquote(path).split("/")
          assert segments[0] == "", "URL path didn't begin with '/': %s" % (path,)
          segments = segments[1:]
@@ -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

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/support/CalendarServer.tmproj)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,172 @@
+<?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>documents</key>
+	<array>
+		<dict>
+			<key>expanded</key>
+			<true/>
+			<key>name</key>
+			<string>twistedcaldav</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../twistedcaldav</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>web2</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../../Twisted/twisted/web2</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>twisted</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../../Twisted/twisted</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>PyOpenDirectory</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../../PyOpenDirectory</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>PyKerberos</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../../PyKerberos</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>conf</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../conf</string>
+		</dict>
+		<dict>
+			<key>name</key>
+			<string>bin</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../bin</string>
+		</dict>
+		<dict>
+			<key>expanded</key>
+			<true/>
+			<key>name</key>
+			<string>doc</string>
+			<key>regexFolderFilter</key>
+			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<key>sourceDirectory</key>
+			<string>../doc</string>
+		</dict>
+	</array>
+	<key>fileHierarchyDrawerWidth</key>
+	<integer>325</integer>
+	<key>metaData</key>
+	<dict>
+		<key>../../Twisted/twisted/web2/dav/element/base.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>0</integer>
+				<key>line</key>
+				<integer>106</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>193</integer>
+		</dict>
+		<key>../../Twisted/twisted/web2/dav/resource.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>0</integer>
+				<key>line</key>
+				<integer>1274</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>1220</integer>
+		</dict>
+		<key>../../Twisted/twisted/web2/dav/static.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>11</integer>
+				<key>line</key>
+				<integer>115</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>44</integer>
+		</dict>
+		<key>../../Twisted/twisted/web2/http.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>15</integer>
+				<key>line</key>
+				<integer>160</integer>
+			</dict>
+			<key>columnSelection</key>
+			<false/>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>118</integer>
+			<key>selectFrom</key>
+			<dict>
+				<key>column</key>
+				<integer>0</integer>
+				<key>line</key>
+				<integer>120</integer>
+			</dict>
+			<key>selectTo</key>
+			<dict>
+				<key>column</key>
+				<integer>0</integer>
+				<key>line</key>
+				<integer>161</integer>
+			</dict>
+		</dict>
+		<key>../../Twisted/twisted/web2/static.py</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>40</integer>
+				<key>line</key>
+				<integer>387</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>357</integer>
+		</dict>
+	</dict>
+	<key>showFileHierarchyDrawer</key>
+	<true/>
+	<key>windowFrame</key>
+	<string>{{468, 89}, {1019, 1050}}</string>
+</dict>
+</plist>

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/apache.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,181 @@
+##
+# 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):
+            yield entryShortName
+
+    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("Subclass should have handled 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,
+        )
+
+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 groupName in self.service.listRecords("group"):
+            group = self.service.recordWithShortName("group", groupName)
+            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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/appleopendirectory.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/appleopendirectory.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -13,101 +13,207 @@
 # 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
+                    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, memberGUIDs):
+        super(OpenDirectoryRecord, self).__init__(service, recordType, guid, shortName, fullName)
+        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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/cred.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/cred.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/cred.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/directory.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/directory.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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,99 @@
 """
 
 __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
+    def __repr__(self):
+        return "<%s[%s@%s] %s(%s) %r>" % (self.__class__.__name__, self.recordType, self.service, self.guid, self.shortName, self.fullName)
+
+    def __init__(self, service, recordType, guid, shortName, fullName=None):
+        self.service    = service
         self.recordType = recordType
         self.guid       = guid
         self.shortName  = shortName
         self.fullName   = fullName
 
-    def authenticate(credentials):
+    def __cmp__(self, other):
+        if not isinstance(other, DirectoryRecord):
+            return super(DirectoryRecord, self).__eq__(other)
+
+        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(credentials):
         return False
+
+class DirectoryError(RuntimeError):
+    """
+    Generic directory error.
+    """
+
+class UnknownRecordTypeError(DirectoryError):
+    """
+    Unknown directory record type.
+    """

Modified: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/idirectory.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/idirectory.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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,40 @@
         @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.")
+    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.")
 
-    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.

Modified: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/resource.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/resource.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/resource.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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,965 +21,261 @@
 """
 
 __all__ = [
-    "DirectoryPrincipalFile",
-    "DirectoryUserPrincipalProvisioningResource",
-    "DirectoryGroupPrincipalProvisioningResource",
-    "DirectoryResourcePrincipalProvisioningResource",
     "DirectoryPrincipalProvisioningResource",
+    "DirectoryPrincipalTypeResource",
+    "DirectoryPrincipalResource",
 ]
 
 from twisted.python import log
-from twisted.internet import reactor
-from twisted.internet import task
+from twisted.python.failure import Failure
 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.http import Response, HTTPError
+from twisted.web2.http_headers import MimeType
 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.extensions import ReadOnlyResourceMixIn
 from twistedcaldav.resource import CalendarPrincipalCollectionResource
 from twistedcaldav.static import CalendarPrincipalFile
+from twistedcaldav.directory.idirectory import IDirectoryService
 
-import dsattributes
-import opendirectory
-import os
-import unicodedata
+# FIXME: These should not be tied to DAVFile
 
-class DirectoryPrincipalFile (CalendarPrincipalFile):
+class DirectoryPrincipalProvisioningResource (ReadOnlyResourceMixIn, CalendarPrincipalCollectionResource, DAVFile):
     """
-    Directory principal resource.
+    Collection resource which provisions directory principals as its children.
     """
-    def __init__(self, parent, path, url):
+    def __init__(self, path, url, directory):
         """
         @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}.
+        @param url: the canonical URL for the resource.
+        @param directory: an L{IDirectoryService} to provision principals from.
         """
-        super(DirectoryPrincipalFile, self).__init__(path, url)
+        CalendarPrincipalCollectionResource.__init__(self, url)
+        DAVFile.__init__(self, path)
 
-        self._parent = parent
+        self.directory = IDirectoryService(directory)
 
-    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
-        """
+        # FIXME: Smells like a hack
+        directory.principalCollection = self
 
-        # If there is no calendar principal URI then the calendar user is disabled.
-        if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-            return False
+    # FIXME: Move this to __init__ after we remove the directory proxy hack from repository.py.
+    def _initChildren(self):
+        if len(self.putChildren) > 0:
+            return
 
-        if isinstance(creds, credentials.UsernamePassword):
-            return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
-        else:
-            return False
+        # Create children
+        for name in self.directory.recordTypes():
+            child_fp = self.fp.child(name)
+            if child_fp.exists():
+                assert child_fp.isdir()
+            else:
+                assert self.exists()
+                assert self.isCollection()
 
-    def directory(self):
-        """
-        Get the directory object used for directory operations.
-        
-        @return:      C{object} for the directory instance
-        """
+                child_fp.makedirs()
 
-        return self._parent.directory
+            self.putChild(name, DirectoryPrincipalTypeResource(child_fp.path, self, name))
 
-    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            
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
 
-        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)
+    def getChild(self, name):
+        self._initChildren()
+        # 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
         else:
-            return ""
-    
-    def setPropertyValue(self, str, cls):
-        """
-        Set the requested property value or remove it if the value is empty.
-        """
+            return self.putChildren.get(name, None)
 
-        if str:
-            self.writeDeadProperty(cls.fromString(str))
-        else:
-            self.removeDeadProperty(cls())
+    def listChildren(self):
+        self._initChildren()
+        return self.putChildren.keys()
 
-    def getGUID(self):
-        return self.getPropertyValue(customxml.TwistedGUIDProperty)
-    
-    def readProperty(self, property, request):
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
+    def principalForUser(self, user):
+        return self.getChild("user").getChild(user)
 
-        namespace, name = qname
+    def principalForRecord(self, record):
+        typeResource = self.getChild(record.recordType)
+        if typeResource is None:
+            return None
+        return typeResource.getChild(record.shortName)
 
-        if namespace == caldavxml.caldav_namespace:
-            if name == "calendar-user-address-set":
-                return succeed(caldavxml.CalendarUserAddressSet(
-                    *[davxml.HRef().fromString(uri) for uri in self.calendarUserAddresses()]
-                ))
+    def principalCollections(self, request):
+        return succeed((self.principalCollectionURL(),))
 
-        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):
+class DirectoryPrincipalTypeResource (ReadOnlyResourceMixIn, CalendarPrincipalCollectionResource, DAVFile):
     """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
-    
-    This includes a periodic task for refreshing the cached data.
+    Collection resource which provisions directory principals of a specific type as its children.
     """
-    periodicSyncIntervalSeconds = 60.0
-    
-    typeUnknown  = 0
-    typeUser     = 1
-    typeGroup    = 2
-    typeResource = 3
-
-    def __init__(self, path, url):
+    def __init__(self, path, parent, recordType):
         """
         @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
+        @param directory: an L{IDirectoryService} to provision calendars from.
+        @param recordType: the directory record type to provision.
         """
-        CalendarPrincipalCollectionResource.__init__(self, url)
+        CalendarPrincipalCollectionResource.__init__(self, joinURL(parent.principalCollectionURL(), recordType))
         DAVFile.__init__(self, path)
-        self.directory = None
-        self.calendarhomeroot = None
-        self.index = None
-        self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
 
-    def setup(self, directory):
-        self.directory = directory
+        self.directory = parent.directory
+        self.recordType = recordType
+        self._parent = parent
 
-    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)
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
 
-        #
-        # 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.
-        """
+    def getChild(self, name, record=None):
         if name == "":
             return self
 
-        child = self.putChildren.get(name, None)
-        if child: return child
+        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
 
         child_fp = self.fp.child(name)
         if child_fp.exists():
-            return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
+            assert child_fp.isfile()
         else:
-            return None
+            assert self.exists()
+            assert self.isCollection()
 
-    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"}
-                ),
-            ),
-        )
+            child_fp.open("w").close()
 
-    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, "")
+        return DirectoryPrincipalResource(child_fp.path, self, record)
 
-    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 listChildren(self):
+        return (record.shortName for record in self.directory.listRecords(self.recordType))
 
-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 principalCollections(self, request):
+        return self._parent.principalCollections(request)
 
-    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):
+class DirectoryPrincipalResource (ReadOnlyResourceMixIn, CalendarPrincipalFile):
     """
-    L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
-    as needed.
+    Directory principal resource.
     """
-    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 __init__(self, path, parent, record):
+        super(DirectoryPrincipalResource, self).__init__(path, joinURL(parent.principalCollectionURL(), record.shortName))
 
-    def listNames(self):
-        """
-        List all the names currently in the directory.
+        self.record = record
+        self._parent = parent
 
-        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
-        """
+    ##
+    # HTTP
+    ##
 
-        # 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.
+    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())
 
-        @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)
+        output = ("".join((
+            "Principal resource\n"
+            "------------------\n"
+            "\n"
+            "Directory service: %(service)s\n"
+            "Record type: %(recordType)s\n"
+            "GUID: %(guid)s\n"
+            "Short name: %(shortName)s\n"
+            "Full name: %(fullName)s\n"
+            % self.record.__dict__,
+            "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),
+        )))
 
-    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]
+        if type(output) == unicode:
+            output = output.encode("utf-8")
+            mime_params = {"charset": "utf-8"}
         else:
-            return None
+            mime_params = {}
 
-    def getTitle(self):
-        return "Group Principals"
+        response = Response(code=responsecode.OK, stream=output)
+        response.headers.setHeader("content-type", MimeType("text", "plain", mime_params))
 
-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
+        return response
 
-    def listNames(self):
-        """
-        List all the names currently in the directory.
+    ##
+    # ACL
+    ##
 
-        @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
-        """
+    def alternateURIs(self):
+        # FIXME: Add API to IDirectoryRecord for getting a record URI?
+        return ()
 
-        # 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.
+    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()
 
-        @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)
+        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)
 
-    def listCommonAttributes(self, names):
-        """
-        List specified names currently in the directory returning useful attributes.
+        return relatives
 
-        @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 groupMembers(self):
+        return self._getRelatives("members")
 
-    def validName(self, name):
-        """
-        Verify that the supplied name exists as an entry in the directory.
+    def groupMemberships(self):
+        return self._getRelatives("groups")
 
-        @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 principalCollections(self, request):
+        return self._parent.principalCollections(request)
 
-    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
+    ##
+    # CalDAV
+    ##
 
-    def getTitle(self):
-        return "Resource Principals"
+    def principalUID(self):
+        return self.record.shortName
 
-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)
+    def calendarHomeURLs(self):
+        # FIXME: self.directory.calendarHomesCollection smells like a hack
+        # See CalendarHomeProvisioningFile.__init__()
+        return (
+            self.record.service.calendarHomesCollection.homeForDirectoryRecord(self.record).url(),
+        )
 
-        assert self.exists(), "%s should exist" % (self,)
-        assert self.isCollection(), "%s should be a collection" % (self,)
+    def calendarUserAddresses(self):
+        return (
+            # Principal URL
+            self.principalURL(),
 
-        # 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")
+            # Need to implement GUID->record->principal resource lookup first
+            #"urn:uuid:%s" % (self.record.guid,)
 
-        # 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)
+            # Need to add email attribute to records if we want this
+            #"mailto:%s" % (self.record.emailAddress)
 
-    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()
+            # This one needs a valid scheme.  If we make up our own, need to check the RFC for character rules.
+            #"urn:calendarserver.macosforge.org:webdav:principal:%s:%s" % (self.record.recordType, self.record.shortName),
         )

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/sqldb.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,288 @@
+##
+# 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
+##
+from twistedcaldav.sql import AbstractSQLDatabase
+
+
+"""
+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
+
+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):
+        return self._db_values_for_sql("select UID from ACCOUNTS where TYPE = :1", recordType)
+
+    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]
+        pswd = result[1]
+        name = result[2]
+        members = []
+        groups = []
+
+        # 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])
+            
+        return uid, pswd, name, members, groups
+            
+    def _add_to_db(self, record):
+        # Do regular account entry
+        type = record.recordType
+        uid = record.uid
+        pswd = record.pswd
+        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, pswd, 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.cuaddrs:
+            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 name in self.manager.listRecords(recordType):
+            yield name
+
+    def recordWithShortName(self, recordType, shortName):
+        result = self.manager.getRecord(recordType, shortName)
+        if result:
+            return SQLDirectoryRecord(
+                service       = self,
+                recordType    = recordType,
+                shortName     = result[0],
+                pswd          = result[1],
+                name          = result[2],
+                members       = result[3],
+                groups        = result[4],
+            )
+
+        return None
+
+    def recordWithGUID(self, guid):
+        raise NotImplementedError()
+
+class SQLDirectoryRecord(DirectoryRecord):
+    """
+    XML based implementation implementation of L{IDirectoryRecord}.
+    """
+    def __init__(self, service, recordType, shortName, pswd, name, members, groups):
+        super(SQLDirectoryRecord, self).__init__(
+            service    = service,
+            recordType = recordType,
+            guid       = None,
+            shortName  = shortName,
+            fullName   = name,
+        )
+
+        self.password = pswd
+        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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test)

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/__init__.py	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/__init__.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.dtd)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/accounts.xml)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/basic	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,4 +0,0 @@
-wsanchez:Cytm0Bwm7CPJs
-cdaboo:I.Ef5FJl5GVh2
-dreid:LVhqAv4qSrYPs
-lecroy:/7/5VDrkrLxY.

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/basic)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,4 @@
+wsanchez:Cytm0Bwm7CPJs
+cdaboo:I.Ef5FJl5GVh2
+dreid:LVhqAv4qSrYPs
+lecroy:/7/5VDrkrLxY.

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/digest	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,4 +0,0 @@
-wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
-cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
-dreid:Test:8ee67801004b2752f72b84e7064889a6
-lecroy:Test:60d4feb424430953be045738041e51be

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/digest)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,4 @@
+wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
+cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
+dreid:Test:8ee67801004b2752f72b84e7064889a6
+lecroy:Test:60d4feb424430953be045738041e51be

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/groups	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,4 +0,0 @@
-managers: lecroy
-grunts: wsanchez, cdaboo, dreid
-right_coast: cdaboo
-left_coast: wsanchez, dreid, lecroy

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/groups)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,4 @@
+managers: lecroy
+grunts: wsanchez, cdaboo, dreid
+right_coast: cdaboo
+left_coast: wsanchez, dreid, lecroy

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_apache.py	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,81 +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
-
-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 Basic (twistedcaldav.directory.test.util.BasicTestCase):
-    """
-    Test Apache-Compatible UserFile/GroupFile directory implementation.
-    """
-    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"),
-    }
-
-    def basicUserFile(self):
-        if not hasattr(self, "_basicUserFile"):
-            self._basicUserFile = FilePath(self.mktemp())
-            basicUserFile.copyTo(self._basicUserFile)
-        return self._basicUserFile
-
-    def groupFile(self):
-        if not hasattr(self, "_groupFile"):
-            self._groupFile = FilePath(self.mktemp())
-            groupFile.copyTo(self._groupFile)
-        return self._groupFile
-
-    def service(self):
-        return BasicDirectoryService(self.basicUserFile(), self.groupFile())
-
-    def test_recordTypes_user(self):
-        """
-        IDirectoryService.recordTypes(userFile)
-        """
-        self.assertEquals(set(BasicDirectoryService(basicUserFile).recordTypes()), set(("user",)))
-
-    def test_changedUserFile(self):
-        self.basicUserFile().open("w").write("wsanchez:Cytm0Bwm7CPJs\n")
-        self.assertEquals(set(self.service().listRecords("user")), set(("wsanchez",)))
-
-    def test_changedGroupFile(self):
-        self.groupFile().open("w").write("grunts: wsanchez\n")
-        self.assertEquals(set(self.service().listRecords("group")), set(("grunts",)))

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_apache.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,81 @@
+##
+# 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
+
+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 Basic (twistedcaldav.directory.test.util.BasicTestCase):
+    """
+    Test Apache-Compatible UserFile/GroupFile directory implementation.
+    """
+    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"),
+    }
+
+    def basicUserFile(self):
+        if not hasattr(self, "_basicUserFile"):
+            self._basicUserFile = FilePath(self.mktemp())
+            basicUserFile.copyTo(self._basicUserFile)
+        return self._basicUserFile
+
+    def groupFile(self):
+        if not hasattr(self, "_groupFile"):
+            self._groupFile = FilePath(self.mktemp())
+            groupFile.copyTo(self._groupFile)
+        return self._groupFile
+
+    def service(self):
+        return BasicDirectoryService(self.basicUserFile(), self.groupFile())
+
+    def test_recordTypes_user(self):
+        """
+        IDirectoryService.recordTypes(userFile)
+        """
+        self.assertEquals(set(BasicDirectoryService(basicUserFile).recordTypes()), set(("user",)))
+
+    def test_changedUserFile(self):
+        self.basicUserFile().open("w").write("wsanchez:Cytm0Bwm7CPJs\n")
+        self.assertEquals(set(self.service().listRecords("user")), set(("wsanchez",)))
+
+    def test_changedGroupFile(self):
+        self.groupFile().open("w").write("grunts: wsanchez\n")
+        self.assertEquals(set(self.service().listRecords("group")), set(("grunts",)))

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_sqldb.py	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,67 +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
-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 Basic (twistedcaldav.directory.test.util.BasicTestCase):
-    """
-    Test SQL directory implementation.
-    """
-    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
-
-    def service(self):
-        return SQLDirectoryService(os.getcwd(), self.xmlFile())

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_sqldb.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,67 @@
+##
+# 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
+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 Basic (twistedcaldav.directory.test.util.BasicTestCase):
+    """
+    Test SQL directory implementation.
+    """
+    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
+
+    def service(self):
+        return SQLDirectoryService(os.getcwd(), self.xmlFile())

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,84 +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 XMLFile (twistedcaldav.directory.test.util.BasicTestCase, twistedcaldav.directory.test.util.DigestTestCase):
-    """
-    Test XML file based directory implementation.
-    """
-    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
-
-    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>
-"""
-        )
-        self.assertEquals(set(self.service().listRecords("user")), set(("admin",)))
-        self.assertEquals(set(self.service().listRecords("group")), set())
-        self.assertEquals(set(self.service().listRecords("resource")), set())

Copied: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/test_xmlfile.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,84 @@
+##
+# 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 XMLFile (twistedcaldav.directory.test.util.BasicTestCase, twistedcaldav.directory.test.util.DigestTestCase):
+    """
+    Test XML file based directory implementation.
+    """
+    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
+
+    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>
+"""
+        )
+        self.assertEquals(set(self.service().listRecords("user")), set(("admin",)))
+        self.assertEquals(set(self.service().listRecords("group")), set())
+        self.assertEquals(set(self.service().listRecords("resource")), set())

Deleted: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/util.py	2006-11-18 00:30:41 UTC (rev 517)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -1,190 +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.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()
-        """
-        self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
-
-    def test_listRecords_user(self):
-        """
-        IDirectoryService.listRecords("user")
-        """
-        self.assertEquals(set(self.service().listRecords("user")), set(self.users.keys()))
-
-    def test_listRecords_group(self):
-        """
-        IDirectoryService.listRecords("group")
-        """
-        self.assertEquals(set(self.service().listRecords("group")), set(self.groups.keys()))
-
-    def test_listRecords_resources(self):
-        """
-        IDirectoryService.listRecords("resources")
-        """
-        if len(self.resources):
-            self.assertEquals(set(self.service().listRecords("resource")), self.resources)
-
-    def test_recordWithShortName_user(self):
-        """
-        IDirectoryService.recordWithShortName("user")
-        """
-        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")
-        """
-        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")
-        """
-        service = self.service()
-        for resource in self.resources:
-            resourceRecord = service.recordWithShortName("resource", resource)
-            self.assertEquals(resourceRecord.shortName, resource)
-
-    def test_groupMembers(self):
-        """
-        IDirectoryRecord.members()
-        """
-        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()
-        """
-        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]))
-
-class BasicTestCase (DirectoryTestCase):
-    """
-    Tests a directory implementation with basic auth.
-    """
-    def test_verifyCredentials_basic(self):
-        """
-        IDirectoryRecord.verifyCredentials() with basic
-        """
-        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
-        """
-        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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/test/util.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,190 @@
+##
+# 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.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()
+        """
+        self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
+
+    def test_listRecords_user(self):
+        """
+        IDirectoryService.listRecords("user")
+        """
+        self.assertEquals(set(self.service().listRecords("user")), set(self.users.keys()))
+
+    def test_listRecords_group(self):
+        """
+        IDirectoryService.listRecords("group")
+        """
+        self.assertEquals(set(self.service().listRecords("group")), set(self.groups.keys()))
+
+    def test_listRecords_resources(self):
+        """
+        IDirectoryService.listRecords("resources")
+        """
+        if len(self.resources):
+            self.assertEquals(set(self.service().listRecords("resource")), self.resources)
+
+    def test_recordWithShortName_user(self):
+        """
+        IDirectoryService.recordWithShortName("user")
+        """
+        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")
+        """
+        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")
+        """
+        service = self.service()
+        for resource in self.resources:
+            resourceRecord = service.recordWithShortName("resource", resource)
+            self.assertEquals(resourceRecord.shortName, resource)
+
+    def test_groupMembers(self):
+        """
+        IDirectoryRecord.members()
+        """
+        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()
+        """
+        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]))
+
+class BasicTestCase (DirectoryTestCase):
+    """
+    Tests a directory implementation with basic auth.
+    """
+    def test_verifyCredentials_basic(self):
+        """
+        IDirectoryRecord.verifyCredentials() with basic
+        """
+        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
+        """
+        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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlaccountsparser.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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.pswd = None
+        self.name = None
+        self.members = []
+        self.groups = []
+        self.cuaddrs = []
+        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.pswd.find("%") != -1:
+            pswd = self.pswd % ctr
+        else:
+            pswd = self.pswd
+        if self.name.find("%") != -1:
+            name = self.name % ctr
+        else:
+            name = self.name
+        cuaddrs = []
+        for cuaddr in self.cuaddrs:
+            if cuaddr.find("%") != -1:
+                cuaddrs.append(cuaddr % ctr)
+            else:
+                cuaddrs.append(cuaddr)
+        
+        result = XMLAccountRecord(self.recordType)
+        result.uid = uid
+        result.pswd = pswd
+        result.name = name
+        result.members = self.members
+        result.cuaddrs = cuaddrs
+        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.pswd = 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.cuaddrs.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/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/directory/xmlfile.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -0,0 +1,118 @@
+##
+# 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
+##
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+
+"""
+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
+
+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 entryShortName
+
+    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,
+        )
+
+        self.password = xmlPrincipal.pswd
+        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/branches/users/wsanchez/provisioning-2/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/extensions.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/extensions.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/repository.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/repository.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/repository.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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
@@ -46,14 +54,9 @@
 from twistedcaldav.logging import RotatingFileAccessLoggingObserver
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.static import CalendarHomeFile, CalendarPrincipalFile
-from twistedcaldav.directory.cred import DirectoryCredentialsChecker
+from twistedcaldav.directory.idirectory import IDirectoryService
+from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
 
-import os
-
-from xml.dom.minidom import Element
-from xml.dom.minidom import Text
-import xml.dom.minidom
-
 ELEMENT_REPOSITORY = "repository"
 
 ELEMENT_DOCROOT = "docroot"
@@ -106,6 +109,7 @@
 ATTRIBUTE_ENABLE = "enable"
 ATTRIBUTE_ONLYSSL = "onlyssl"
 ATTRIBUTE_CREDENTIALS = "credentials"
+ATTRIBUTE_DIRECTORY_NODE = "node"
 
 ATTRIBUTE_VALUE_PROPERTY = "property"
 ATTRIBUTE_VALUE_DIRECTORY = "directory"
@@ -177,13 +181,55 @@
     # Turn on drop box support before building the repository
     DropBox.enable(dropbox, dropboxName, dropboxACLs, notifications, notifcationName)
 
+    class DirectoryServiceProxy(object):
+        # FIXME: This is a hack to make this config work for now
+        implements(IDirectoryService)
+
+        _service = None
+        _attrs = {}
+
+        def getService(self):
+            return self._service
+        def setService(self, service):
+            if self._service is not None:
+                raise AssertionError("bleargh")
+            for attr, value in self._attrs.items():
+                setattr(service, attr, value)
+            def set(name, value):
+                object.__setattr__(service, name, value)
+            def get(name):
+                object.__getattr__(service, name)
+            object.__setattr__(self, "_service", service)
+            object.__setattr__(self, "__setattr__", set)
+            object.__setattr__(self, "__getattr__", get)
+
+        service = property(getService, setService)
+
+        def __getattr__(self, name):
+            attr = getattr(self.service, name)
+
+            if type(attr) is type(self.__getattr__):
+                def m(*args, **kwargs):
+                    return attr(*args, **kwargs)
+                return m
+            else:
+                return attr
+
+        def __setattr__(self, name, value):
+            if name == "service":
+                object.__setattr__(self, name, value)
+            else:
+                self._attrs[name] = value
+
+    directory = DirectoryServiceProxy()
+
     # 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 +246,9 @@
         portal.registerChecker(auth.TwistedPropertyChecker())
         print "Using property-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_DIRECTORY:
-        portal.registerChecker(DirectoryCredentialsChecker())
+        service = OpenDirectoryService(authenticator.directoryNode)
+        directory.service = service
+        portal.registerChecker(service)
         print "Using directory-based password checker."
     elif authenticator.credentials == ATTRIBUTE_VALUE_KERBEROS:
         if authenticator.type == "basic":
@@ -285,24 +333,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 +406,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 +432,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 +514,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 +538,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, e:
+            log.err("Unable to instantiate Python class %r with arguments %r" % (resource_class, kwargs))
+            raise
 
         self.uri = myurl
         
@@ -510,7 +563,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 +757,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:
@@ -873,6 +926,8 @@
                 self.onlyssl = node.getAttribute(ATTRIBUTE_ONLYSSL) == ATTRIBUTE_VALUE_YES
             if node.hasAttribute(ATTRIBUTE_CREDENTIALS):
                 self.credentials = node.getAttribute(ATTRIBUTE_CREDENTIALS)
+            if node.hasAttribute(ATTRIBUTE_DIRECTORY_NODE):
+                self.directoryNode = node.getAttribute(ATTRIBUTE_DIRECTORY_NODE)
             for child in node._get_childNodes():
                 if child._get_localName() == ELEMENT_REALM:
                     if child.firstChild is not None:

Modified: CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/resource.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/resource.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -33,8 +33,6 @@
     "isScheduleOutboxResource",
 ]
 
-from weakref import WeakValueDictionary
-
 from zope.interface import implements
 
 from twisted.internet import reactor
@@ -233,7 +231,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 +240,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 +258,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 +277,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
@@ -592,6 +588,28 @@
         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"}
+                ),
+            ),
+        )
+
 def findAnyCalendarUser(request, address):
     """
     Find the calendar user principal associated with the specified calendar
@@ -604,14 +622,15 @@
     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()
+            collection = waitForDeferred(request.locateResource(url))
+            yield collection
+            collection = collection.getResult()
 
-            if isinstance(pcollection, CalendarPrincipalCollectionResource):
-                principal = waitForDeferred(pcollection.findCalendarUser(request, address))
+            if isinstance(collection, CalendarPrincipalCollectionResource):
+                principal = waitForDeferred(collection.findCalendarUser(request, address))
                 yield principal
                 principal = principal.getResult()
+
                 if principal is not None:
                     yield principal
                     return
@@ -655,9 +674,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/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py (from rev 517, CalendarServer/branches/users/wsanchez/provisioning/twistedcaldav/sql.py)
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py	                        (rev 0)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -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/branches/users/wsanchez/provisioning-2/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/static.py	2006-11-18 00:58:08 UTC (rev 518)
+++ CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/static.py	2006-11-18 00:58:58 UTC (rev 519)
@@ -32,7 +32,6 @@
 
 import os
 import errno
-from posixpath import basename
 from urlparse import urlsplit
 
 from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
@@ -45,25 +44,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 +211,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 +227,6 @@
         
         return super(CalDAVFile, self).supportedPrivileges(request)
 
-
     ##
     # Public additions
     ##
@@ -442,59 +388,7 @@
     def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
 
     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):
     """
@@ -525,87 +419,147 @@
     def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
 
     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"}),
-                    ),
-                ),
-            )
-            
-        return succeed(ScheduleOutboxFile._supportedSchedulePrivilegeSet)
+        return succeed(schedulePrivilegeSet)
 
+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(CalendarHomeProvisioningFile, self).__init__(path)
+
+        self.directory = IDirectoryService(directory)
+        self._url = url
+
+        # FIXME: Smells like a hack
+        directory.calendarHomesCollection = self
+
+    def url(self):
+        return self._url
+
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    def getChild(self, name):
+        if name == "":
+            return self
+
+        if name not in self.listChildren():
+            return None
+
+        child_fp = self.fp.child(name)
+        if child_fp.exists():
+            assert child_fp.isdir()
+        else:
+            assert self.exists()
+            assert self.isCollection()
+
+            child_fp.makedirs()
+
+        return CalendarHomeTypeProvisioningFile(child_fp.path, self, name)
+
+    def listChildren(self):
+        return self.directory.recordTypes()
+
+    def principalCollections(self, request):
+        # FIXME: directory.principalCollection smells like a hack
+        # See DirectoryPrincipalProvisioningResource.__init__()
+        return self.directory.principalCollection.principalCollections(request)
+
+    def homeForDirectoryRecord(self, record):
+        return self.getChild(record.recordType).getChild(record.shortName)
+
+class CalendarHomeTypeProvisioningFile (ReadOnlyResourceMixIn, DAVFile):
+    """
+    L{CalDAVFile} resource which provisions calendar home collections of a specific
+    record type as needed.
+    """
+    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(CalendarHomeTypeProvisioningFile, self).__init__(path)
+
+        self.directory = parent.directory
+        self.recordType = recordType
+        self._parent = parent
+
+    def url(self):
+        return joinURL(self._parent.url(), self.recordType)
+
+    def createSimilarFile(self, path):
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    def getChild(self, name, record=None):
+        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
+
+        child_fp = self.fp.child(name)
+        if child_fp.exists():
+            assert child_fp.isdir()
+        else:
+            assert self.exists()
+            assert self.isCollection()
+
+            child_fp.makedirs()
+
+        return CalendarHomeFile(child_fp.path, self, record)
+
+    def listChildren(self):
+        return (record.shortName for record in self.directory.listRecords(self.recordType))
+
+    def principalCollections(self, request):
+        return self._parent.principalCollections(request)
+
 class CalendarHomeFile (CalDAVFile):
     """
     L{CalDAVFile} calendar home 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 __init__(self, path):
+    def __init__(self, path, parent, record):
         """
         @param path: the path to the file which will back the resource.
         """
         super(CalendarHomeFile, self).__init__(path)
 
-        assert self.exists(), "%s should exist" % (self,)
-        assert self.isCollection(), "%s should be a collection" % (self,)
+        self.record = record
+        self._parent = parent
 
         # Create children
-        for name, clazz in (
+        for name, cls 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))
+            child = cls(child_fp.path)
+            if not child_fp.exists():
+                child_fp.makedirs()
+                if record.recordType == "resource" and child == "inbox":
+                    # Resources should have autorespond turned on by default,
+                    # since they typically don't have someone responding for them.
+                    child.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
+            self.putChild(name, child)
 
+    def url(self):
+        return joinURL(self._parent.url(), self.record.shortName)
+
     def createSimilarFile(self, path):
         if path == self.fp.path:
             return self
@@ -620,6 +574,30 @@
         return super(CalendarHomeFile, self).getChild(name)
 
     ##
+    # ACL
+    ##
+
+    def defaultAccessControlList(self):
+        # FIXME: directory.principalCollection smells like a hack
+        # See DirectoryPrincipalProvisioningResource.__init__()
+        myPrincipal = self._parent._parent.directory.principalCollection.principalForRecord(self.record)
+
+        return davxml.ACL(
+            # Read access for authenticated users.
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(davxml.Privilege(davxml.Read())),
+            ),
+            # Inheritable all access to resource's associated principal.
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
+                davxml.Grant(davxml.Privilege(davxml.All())),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ),
+        )
+
+    ##
     # Quota
     ##
 
@@ -639,53 +617,6 @@
         else:
             return CalendarHomeFile.quotaLimit
 
-class CalendarHomeProvisioningFile (CalDAVFile):
-    """
-    L{CalDAVFile} resource which provisions calendar home collections as needed.
-    """
-    calendarHomeClass = CalendarHomeFile
-
-    def __init__(self, path):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-        super(CalendarHomeProvisioningFile, 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()
-
-    def locateChild(self, request, segments):
-        return locateExistingChild(self, request, segments)
-
-    def getChild(self, name):
-        if name == "": return self
-
-        # Avoid case variants when allocating resources
-        if not self.hasChild(name):
-            return None
-
-        child_fp = self.fp.child(name)
-        if child_fp.exists():
-            assert child_fp.isdir()
-        else:
-            assert self.exists()
-            assert self.isCollection()
-
-            child_fp.makedirs()
-
-        return self.calendarHomeClass(child_fp.path)
-
-    def createSimilarFile(self, path):
-        raise NotImplementedError("Not allowed")
-
-    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 CalendarPrincipalFile (CalendarPrincipalResource, CalDAVFile):
     """
     Calendar principal resource.
@@ -869,71 +800,6 @@
                 from twistedcaldav.dropbox import DropBox
                 DropBox.provision(self, (homeURL, home))
 
-
-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)
-
-    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.
-        """
-        pass
-    
-    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):
-        """
-        See L{IDAVResource.principalSearchPropertySet}.
-        
-        This implementation returns None. Principal collection resources MUST override
-        and return their own suitable response.
-        
-        """
-        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 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 +817,74 @@
     # Otherwise, there is no child
     return (None, ())
 
+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
 ##

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


More information about the calendarserver-changes mailing list