[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