[CalendarServer-changes] [637] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri Dec 1 11:18:20 PST 2006
Revision: 637
http://trac.macosforge.org/projects/calendarserver/changeset/637
Author: wsanchez at apple.com
Date: 2006-12-01 11:18:19 -0800 (Fri, 01 Dec 2006)
Log Message:
-----------
Merge branches/users/wsanchez/provisioning-2.
Modified Paths:
--------------
CalendarServer/trunk/bin/caldavd
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/conf/repository.dtd
CalendarServer/trunk/conf/repository.xml
CalendarServer/trunk/doc/Repository/Directory Schema.graffle
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
CalendarServer/trunk/run
CalendarServer/trunk/support/patchmaker.py
CalendarServer/trunk/support/submit
CalendarServer/trunk/test
CalendarServer/trunk/twistedcaldav/__init__.py
CalendarServer/trunk/twistedcaldav/directory/__init__.py
CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/idirectory.py
CalendarServer/trunk/twistedcaldav/dropbox.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
CalendarServer/trunk/twistedcaldav/repository.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
Added Paths:
-----------
CalendarServer/trunk/conf/accounts.dtd
CalendarServer/trunk/conf/accounts.xml
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch
CalendarServer/trunk/support/CalendarServer.tmproj
CalendarServer/trunk/support/CalendarServer.xcodeproj/
CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
CalendarServer/trunk/twistedcaldav/directory/apache.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/sqldb.py
CalendarServer/trunk/twistedcaldav/directory/test/
CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
CalendarServer/trunk/twistedcaldav/directory/test/basic
CalendarServer/trunk/twistedcaldav/directory/test/digest
CalendarServer/trunk/twistedcaldav/directory/test/groups
CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
CalendarServer/trunk/twistedcaldav/directory/test/util.py
CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py
CalendarServer/trunk/twistedcaldav/directory/xmlfile.py
CalendarServer/trunk/twistedcaldav/sql.py
Removed Paths:
-------------
CalendarServer/trunk/conf/caldavd-dev.plist
CalendarServer/trunk/conf/repository-proxy.xml
CalendarServer/trunk/conf/repository-static.xml
CalendarServer/trunk/support/CalDAV.xcodeproj/
CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
CalendarServer/trunk/twistedcaldav/directory/cred.py
CalendarServer/trunk/twistedcaldav/directory/resource.py
CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
CalendarServer/trunk/twistedcaldav/directory/test/basic
CalendarServer/trunk/twistedcaldav/directory/test/digest
CalendarServer/trunk/twistedcaldav/directory/test/groups
CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
CalendarServer/trunk/twistedcaldav/directory/test/util.py
Property Changed:
----------------
CalendarServer/trunk/bin/
CalendarServer/trunk/conf/
Property changes on: CalendarServer/trunk/bin
___________________________________________________________________
Name: svn:ignore
+ server.log.*
Modified: CalendarServer/trunk/bin/caldavd
===================================================================
--- CalendarServer/trunk/bin/caldavd 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/bin/caldavd 2006-12-01 19:18:19 UTC (rev 637)
@@ -65,11 +65,16 @@
self.certfile = "/etc/certificates/Default.crt"
self.manhole = 0
+ self.directoryservice = {
+ "type": "OpenDirectoryService",
+ "params": { "node": "/Search" },
+ }
+
self.dropbox = True
self.dropboxName = "dropbox"
self.dropboxACLs = True
self.notifications = False
- self.notifcationName = "notifications"
+ self.notificationName = "notifications"
self.serverlogfile = "/var/log/caldavd/server.log"
self.errorlogfile = "/var/log/caldavd/error.log"
@@ -102,11 +107,13 @@
print "Only Use SSL: %s" % (self.onlyssl,)
print "SSL Private Key File: %s" % (self.keyfile,)
print "SSL Certificate File: %s" % (self.certfile,)
+ print "Directory Service: %s" % (self.directoryservice["type"],)
+ print "Directory Service Parameters: %r" % (self.directoryservice["params"],)
print "Drop Box Enabled: %s" % (self.dropbox,)
print "Drop Box Name: %s" % (self.dropboxName,)
print "Drop Box ACLs are Inherited %s" % (self.dropboxACLs,)
print "Notifications Enabled: %s" % (self.notifications,)
- print "Notification Collection Name: %s" % (self.notifcationName,)
+ print "Notification Collection Name: %s" % (self.notificationName,)
print "Server Log File: %s" % (self.serverlogfile,)
print "Error Log File: %s" % (self.errorlogfile,)
print "PID File: %s" % (self.pidfile,)
@@ -270,7 +277,7 @@
self.action = args[0]
def parsePlist(self):
- print "Reading configuration file %s." % (self.plistfile,)
+ print "Reading configuration file %s." % (self.plistfile,)
root = readPlist(self.plistfile)
@@ -286,11 +293,12 @@
"SSLPrivateKey": "keyfile",
"SSLCertificate": "certfile",
"ManholePort": "manhole",
+ "DirectoryService": "directoryservice",
"DropBoxEnabled": "dropbox",
"DropBoxName": "dropboxName",
"DropBoxInheritedACLs": "dropboxACLs",
"NotificationsEnabled": "notifications",
- "NotificationCollectionName": "notifcationName",
+ "NotificationCollectionName": "notificationName",
"ServerLogFile": "serverlogfile",
"ErrorLogFile": "errorlogfile",
"PIDFile": "pidfile",
@@ -354,73 +362,54 @@
def generateTAC(self):
return """
-docroot = "%(docroot)s"
-repo = "%(repo)s"
-doacct = %(doacct)s
-doacl = %(doacl)s
-dossl = %(dossl)s
-keyfile = "%(keyfile)s"
-certfile = "%(certfile)s"
-onlyssl = %(onlyssl)s
-port = %(port)d
-sslport = %(sslport)d
-maxsize = %(maxsize)d
-quota = %(quota)d
-serverlogfile = "%(serverlogfile)s"
-dropbox = "%(dropbox)s"
-dropboxName = "%(dropboxName)s"
-dropboxACLs = "%(dropboxACLs)s"
-notifications = "%(notifications)s"
-notifcationName = "%(notifcationName)s"
-manhole = %(manhole)d
-
from twistedcaldav.repository import startServer
-application, site = startServer(docroot,
- repo,
- doacct,
- doacl,
- dossl,
- keyfile,
- certfile,
- onlyssl,
- port,
- sslport,
- maxsize,
- quota,
- serverlogfile,
- dropbox,
- dropboxName,
- dropboxACLs,
- notifications,
- notifcationName,
- manhole)
-
+application, site = startServer(
+ %(docroot)r,
+ %(repo)r,
+ %(doacct)s,
+ %(doacl)s,
+ %(dossl)s,
+ %(keyfile)r,
+ %(certfile)r,
+ %(onlyssl)s,
+ %(port)d,
+ %(sslport)d,
+ %(maxsize)d,
+ %(quota)d,
+ %(serverlogfile)r,
+ %(directoryservice)r,
+ %(dropbox)r,
+ %(dropboxName)r,
+ %(dropboxACLs)r,
+ %(notifications)r,
+ %(notificationName)r,
+ %(manhole)d,
+)
""" % {
- "docroot": self.docroot,
- "repo": self.repo,
- "doacct": self.doacct,
- "doacl": self.doacl,
- "dossl": self.dossl,
- "keyfile": self.keyfile,
- "certfile": self.certfile,
- "onlyssl": self.onlyssl,
- "port": self.port,
- "sslport": self.sslport,
- "maxsize": self.maxsize,
- "quota": self.quota,
- "serverlogfile": self.serverlogfile,
- "dropbox": self.dropbox,
- "dropboxName": self.dropboxName,
- "dropboxACLs": self.dropboxACLs,
- "notifications": self.notifications,
- "notifcationName": self.notifcationName,
- "manhole": self.manhole,
-
+ "docroot": self.docroot,
+ "repo": self.repo,
+ "doacct": self.doacct,
+ "doacl": self.doacl,
+ "dossl": self.dossl,
+ "keyfile": self.keyfile,
+ "certfile": self.certfile,
+ "onlyssl": self.onlyssl,
+ "port": self.port,
+ "sslport": self.sslport,
+ "maxsize": self.maxsize,
+ "quota": self.quota,
+ "serverlogfile": self.serverlogfile,
+ "directoryservice": self.directoryservice,
+ "dropbox": self.dropbox,
+ "dropboxName": self.dropboxName,
+ "dropboxACLs": self.dropboxACLs,
+ "notifications": self.notifications,
+ "notificationName": self.notificationName,
+ "manhole": self.manhole,
}
if __name__ == "__main__":
-
try:
caldavd().run()
except Exception, e:
Property changes on: CalendarServer/trunk/conf
___________________________________________________________________
Name: svn:ignore
- repository-dev.xml
+ caldavd-dev.plist
Copied: CalendarServer/trunk/conf/accounts.dtd (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/accounts.dtd)
===================================================================
--- CalendarServer/trunk/conf/accounts.dtd (rev 0)
+++ CalendarServer/trunk/conf/accounts.dtd 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,40 @@
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+DRI: Cyrus Daboo, cdaboo at apple.com
+ -->
+
+<!ELEMENT accounts (user*, group*, resource*) >
+ <!ATTLIST accounts realm CDATA "">
+
+ <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST user repeat CDATA "1">
+
+ <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
+ <!ATTLIST group repeat CDATA "1">
+
+ <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST resource repeat CDATA "1">
+
+ <!ELEMENT uid (#PCDATA)>
+ <!ELEMENT pswd (#PCDATA)>
+ <!ELEMENT name (#PCDATA)>
+ <!ELEMENT cuaddr (#PCDATA)>
+ <!ELEMENT calendar (#PCDATA)>
+ <!ELEMENT quota (#PCDATA)>
+ <!ELEMENT autorespond EMPTY>
+ <!ELEMENT canproxy EMPTY>
+ <!ELEMENT members (uid*)>
+
\ No newline at end of file
Copied: CalendarServer/trunk/conf/accounts.xml (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/accounts.xml)
===================================================================
--- CalendarServer/trunk/conf/accounts.xml (rev 0)
+++ CalendarServer/trunk/conf/accounts.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+ <user>
+ <uid>admin</uid>
+ <pswd>admin</pswd>
+ <name>Super User</name>
+ </user>
+ <user>
+ <uid>proxy</uid>
+ <pswd>proxy</pswd>
+ <name>User who can authorize as someone else</name>
+ <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
+ </user>
+ <user repeat="99">
+ <uid>user%02d</uid>
+ <pswd>user%02d</pswd>
+ <name>User %02d</name>
+ <cuaddr>mailto:user%02d at example.com</cuaddr>
+ </user>
+ <user repeat="10">
+ <uid>public%02d</uid>
+ <pswd>public%02d</pswd>
+ <name>Public %02d</name>
+ <cuaddr>mailto:public%02d at example.com</cuaddr>
+ </user>
+ <resource repeat="10">
+ <uid>resource%02d</uid>
+ <pswd>resource%02d</pswd>
+ <name>Room %02d</name>
+ </resource>
+</accounts>
Deleted: CalendarServer/trunk/conf/caldavd-dev.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-dev.plist 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/caldavd-dev.plist 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-
- <key>Verbose</key>
- <false/>
-
- <key>RunStandalone</key>
- <true/>
-
- <key>DocumentRoot</key>
- <string>twistedcaldav/test/data/</string>
-
- <key>Port</key>
- <integer>8008</integer>
-
- <key>SSLEnable</key>
- <true/>
-
- <key>SSLOnly</key>
- <false/>
-
- <key>SSLPort</key>
- <integer>8443</integer>
-
- <key>SSLPrivateKey</key>
- <string>conf/server.pem</string>
-
- <key>SSLCertificate</key>
- <string>conf/server.pem</string>
-
- <key>ServerLogFile</key>
- <string>server.log</string>
-
- <key>ErrorLogFile</key>
- <string>error.log</string>
-
- <key>PIDFile</key>
- <string>caldavd.pid</string>
-
- <key>Repository</key>
- <string>conf/repository-dev.xml</string>
-
- <key>CreateAccounts</key>
- <true/>
-
- <key>ResetAccountACLs</key>
- <true/>
-
- <key>DropBoxEnabled</key>
- <true/>
-
- <key>DropBoxName</key>
- <string>dropbox</string>
-
- <key>DropBoxInheritedACLs</key>
- <true/>
-
- <key>NotificationsEnabled</key>
- <true/>
-
- <key>NotificationCollectionName</key>
- <string>notifications</string>
-
- <key>twistdLocation</key>
- <string>../Twisted/bin/twistd</string>
-
- <key>MaximumAttachmentSizeBytes</key>
- <integer>1048576</integer><!-- 1Mb -->
-
- <key>UserQuotaBytes</key>
- <integer>104857600</integer><!-- 100Mb -->
-
-</dict>
-</plist>
Copied: CalendarServer/trunk/conf/caldavd-test.plist (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/conf/caldavd-test.plist)
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist (rev 0)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+
+ <key>Verbose</key>
+ <false/>
+
+ <key>RunStandalone</key>
+ <true/>
+
+ <key>DocumentRoot</key>
+ <string>twistedcaldav/test/data/</string>
+
+ <key>Port</key>
+ <integer>8008</integer>
+
+ <key>SSLPort</key>
+ <integer>8443</integer>
+
+ <key>SSLEnable</key>
+ <true/>
+
+ <key>SSLOnly</key>
+ <false/>
+
+ <key>SSLPrivateKey</key>
+ <string>conf/server.pem</string>
+
+ <key>SSLCertificate</key>
+ <string>conf/server.pem</string>
+
+ <key>ServerLogFile</key>
+ <string>server.log</string>
+
+ <key>ErrorLogFile</key>
+ <string>error.log</string>
+
+ <key>PIDFile</key>
+ <string>caldavd.pid</string>
+
+ <key>CreateAccounts</key>
+ <true/>
+
+ <key>ResetAccountACLs</key>
+ <true/>
+
+ <!-- XML File Directory Service -->
+ <key>DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>xmlFile</key>
+ <string>conf/accounts.xml</string>
+ </dict>
+ </dict>
+
+ <!-- SQL Directory Service -->
+ <!--
+ <key>twistedcaldav.directory.sqldb.DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>SQLDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbParentPath</key>
+ <string>twistedcaldav/test/data/</string>
+ <key>xmlFile</key>
+ <string>conf/accounts.xml</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- Open Directory Service -->
+ <!--
+ <key>DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>node</key>
+ <string>/Search</string>
+ </dict>
+ </dict>
+ -->
+
+ <key>MaximumAttachmentSizeBytes</key>
+ <integer>1048576</integer><!-- 1Mb -->
+
+ <key>UserQuotaBytes</key>
+ <integer>104857600</integer><!-- 100Mb -->
+
+ <key>DropBoxEnabled</key>
+ <true/>
+
+ <key>DropBoxName</key>
+ <string>dropbox</string>
+
+ <key>DropBoxInheritedACLs</key>
+ <true/>
+
+ <key>NotificationsEnabled</key>
+ <true/>
+
+ <key>NotificationCollectionName</key>
+ <string>notifications</string>
+
+ <key>Repository</key>
+ <string>conf/repository.xml</string>
+
+ <key>twistdLocation</key>
+ <string>../Twisted/bin/twistd</string>
+
+</dict>
+</plist>
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/caldavd.plist 2006-12-01 19:18:19 UTC (rev 637)
@@ -32,15 +32,15 @@
<key>Port</key>
<integer>8008</integer>
+ <key>SSLPort</key>
+ <integer>8443</integer>
+
<key>SSLEnable</key>
<true/>
<key>SSLOnly</key>
<false/>
- <key>SSLPort</key>
- <integer>8443</integer>
-
<key>SSLPrivateKey</key>
<string>/etc/certificates/Default.key</string>
@@ -56,15 +56,63 @@
<key>PIDFile</key>
<string>/var/log/caldavd/caldavd.pid</string>
- <key>Repository</key>
- <string>/etc/caldavd/repository.xml</string>
-
<key>CreateAccounts</key>
<true/>
<key>ResetAccountACLs</key>
<true/>
+ <!-- XML File Directory Service -->
+ <!--
+ <key>DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>xmlFile</key>
+ <string>/etc/caldavd/accounts.xml</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- SQL Directory Service -->
+ <!--
+ <key>twistedcaldav.directory.sqldb.DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>SQLDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>dbParentPath</key>
+ <string>/Library/CalendarServer/Documents</string>
+ <key>xmlFile</key>
+ <string>/etc/caldavd/accounts.xml</string>
+ </dict>
+ </dict>
+ -->
+
+ <!-- Open Directory Service -->
+ <key>DirectoryService</key>
+ <dict>
+ <key>type</key>
+ <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+
+ <key>params</key>
+ <dict>
+ <key>node</key>
+ <string>/Search</string>
+ </dict>
+ </dict>
+
+ <key>MaximumAttachmentSizeBytes</key>
+ <integer>1048576</integer><!-- 1Mb -->
+
+ <key>UserQuotaBytes</key>
+ <integer>104857600</integer><!-- 100Mb -->
+
<key>DropBoxEnabled</key>
<true/>
@@ -80,14 +128,11 @@
<key>NotificationCollectionName</key>
<string>notifications</string>
+ <key>Repository</key>
+ <string>/etc/caldavd/repository.xml</string>
+
<key>twistdLocation</key>
<string>/usr/share/caldavd/bin/twistd</string>
- <key>MaximumAttachmentSizeBytes</key>
- <integer>1048576</integer><!-- 1Mb -->
-
- <key>UserQuotaBytes</key>
- <integer>104857600</integer><!-- 100Mb -->
-
</dict>
</plist>
Deleted: CalendarServer/trunk/conf/repository-proxy.xml
===================================================================
--- CalendarServer/trunk/conf/repository-proxy.xml 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository-proxy.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,217 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<!DOCTYPE repository SYSTEM "repository.dtd">
-
-<repository>
-
- <docroot auto-principal-collection-set="yes">
- <collection>
- <pytype>twisted.web2.dav.static.DAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- <ace>
- <principal><href>/principals/users/admin</href></principal>
- <grant><privilege><all/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </properties>
- <members>
- <!--
- We must define the calendar home location before the
- principals as auto-provisioning of accounts occurs when the
- principal collections are created and we need to have the
- calendar home path setup by then.
- -->
- <collection name="calendars" tag="calendars">
- <pytype>twistedcaldav.static.CalDAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="groups">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="resources">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="public">
- <properties>
- <acl>
- <ace>
- <principal><unauthenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
- </collection>
- <collection name="principals" initialize="yes">
- <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
- <params>
- <param>
- <key>DirectoryNode</key>
- <value>/Search</value>
- </param>
- </params>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users" tag="principals">
- <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="groups" tag="principals">
- <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="resources" tag="principals">
- <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="localusers" tag="principals" account="yes">
- <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><href>/principals/users/admin</href></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
- </collection>
- </members>
- </collection>
- </docroot>
-
- <authentication>
- <basic enable="yes" onlyssl="yes" credentials="directory">
- <realm></realm>
- </basic>
- <digest enable="no" onlyssl="no" credentials="property">
- <realm></realm>
- </digest>
- <kerberos enable="no" onlyssl="no">
- <service></service>
- </kerberos>
- </authentication>
-
-<accounts>
- <user>
- <uid>proxy</uid>
- <pswd>proxy</pswd>
- <name>User who can authorize as someone else</name>
- <canproxy/>
- </user>
-</accounts>
-
-</repository>
Deleted: CalendarServer/trunk/conf/repository-static.xml
===================================================================
--- CalendarServer/trunk/conf/repository-static.xml 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository-static.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,180 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<!DOCTYPE repository SYSTEM "repository.dtd">
-
-<repository>
-
- <docroot>
- <collection>
- <pytype>twisted.web2.dav.static.DAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- <ace>
- <principal><href>/principals/users/admin</href></principal>
- <grant><privilege><all/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="principals">
- <pytype>twistedcaldav.static.CalDAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users" tag="principals" account="yes">
- <pytype>twistedcaldav.static.CalendarPrincipalCollectionFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
- </collection>
- <collection name="calendars">
- <pytype>twistedcaldav.static.CalDAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users" tag="calendars">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="shared">
- <properties>
- <acl>
- <ace>
- <principal><unauthenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
- </collection>
- </members>
- </collection>
- </docroot>
-
- <authentication>
- <basic enable="yes" onlyssl="yes" credentials="property">
- <realm></realm>
- </basic>
- <digest enable="no" onlyssl="no" credentials="property">
- <realm></realm>
- </digest>
- <kerberos enable="no" onlyssl="no">
- <service></service>
- </kerberos>
- </authentication>
-
- <accounts>
- <user>
- <uid>admin</uid>
- <pswd>admin</pswd>
- <name>Super User</name>
- </user>
- <user>
- <uid>proxy</uid>
- <pswd>proxy</pswd>
- <name>User who can authorize as someone else</name>
- <canproxy/>
- </user>
- <user repeat='99'>
- <uid>user%02d</uid>
- <pswd>user%02d</pswd>
- <name>User %02d</name>
- <calendar>calendar</calendar>
- </user>
- <user repeat='10'>
- <uid>public%02d</uid>
- <pswd/>
- <name>Public %02d</name>
- <calendar>calendar</calendar>
- <acl>
- <ace>
- <principal><all/></principal>
- <grant><privilege><all/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </user>
- <user repeat='10'>
- <uid>resource%02d</uid>
- <pswd/>
- <name>Room %02d</name>
- <calendar>calendar</calendar>
- <acl>
- <ace>
- <principal><all/></principal>
- <grant><privilege><all/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- <autorespond/>
- </user>
- </accounts>
-
-</repository>
Modified: CalendarServer/trunk/conf/repository.dtd
===================================================================
--- CalendarServer/trunk/conf/repository.dtd 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository.dtd 2006-12-01 19:18:19 UTC (rev 637)
@@ -21,7 +21,7 @@
<!ELEMENT docroot (collection) >
<!ATTLIST docroot auto-principal-collection-set (yes|no) "yes">
- <!ELEMENT collection (pytype?, params?, properties, members)>
+ <!ELEMENT collection (pytype, params?, properties, members)>
<!ATTLIST collection name CDATA ""
tag (none|principals|calendars) "none"
account (yes|no) "no"
@@ -54,7 +54,8 @@
<!ELEMENT basic (realm, service?)>
<!ATTLIST basic enable (yes|no) "yes"
onlyssl (yes|no) "yes"
- credentials (property|directory|kerberos) "property">
+ credentials (property|directory|kerberos) "property"
+ node CDATA "">
<!ELEMENT digest (realm)>
<!ATTLIST digest enable (yes|no) "no"
onlyssl (yes|no) "no"
Modified: CalendarServer/trunk/conf/repository.xml
===================================================================
--- CalendarServer/trunk/conf/repository.xml 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/conf/repository.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -26,12 +26,11 @@
<properties>
<acl>
<ace>
- <principal><authenticated/></principal>
+ <principal><all/></principal>
<grant><privilege><read/></privilege></grant>
- <protected/>
</ace>
<ace>
- <principal><href>/principals/users/admin</href></principal>
+ <principal><href>/principals/user/admin</href></principal>
<grant><privilege><all/></privilege></grant>
<protected/>
<inheritable/>
@@ -39,157 +38,30 @@
</acl>
</properties>
<members>
- <!--
- We must define the calendar home location before the
- principals as auto-provisioning of accounts occurs when the
- principal collections are created and we need to have the
- calendar home path setup by then.
- -->
- <collection name="calendars" tag="calendars">
- <pytype>twistedcaldav.static.CalDAVFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="groups">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="resources">
- <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="public">
- <properties>
- <acl>
- <ace>
- <principal><unauthenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- <inheritable/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
+ <collection name="principals">
+ <pytype>twistedcaldav.directory.principal.DirectoryPrincipalProvisioningResource</pytype>
+ <properties/>
+ <members/>
</collection>
- <collection name="principals" initialize="yes">
- <pytype>twistedcaldav.directory.resource.DirectoryPrincipalProvisioningResource</pytype>
- <params>
- <param>
- <key>DirectoryNode</key>
- <value>/Search</value>
- </param>
- </params>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members>
- <collection name="users" tag="principals" account="yes">
- <pytype>twistedcaldav.directory.resource.DirectoryUserPrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="groups" tag="principals">
- <pytype>twistedcaldav.directory.resource.DirectoryGroupPrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- <collection name="resources" tag="principals">
- <pytype>twistedcaldav.directory.resource.DirectoryResourcePrincipalProvisioningResource</pytype>
- <properties>
- <acl>
- <ace>
- <principal><authenticated/></principal>
- <grant><privilege><read/></privilege></grant>
- <protected/>
- </ace>
- </acl>
- </properties>
- <members/>
- </collection>
- </members>
+ <collection name="calendars">
+ <pytype>twistedcaldav.static.CalendarHomeProvisioningFile</pytype>
+ <properties/>
+ <members/>
</collection>
</members>
</collection>
</docroot>
<authentication>
- <basic enable="yes" onlyssl="yes" credentials="directory">
- <realm></realm>
- </basic>
- <digest enable="no" onlyssl="no" credentials="property">
- <realm></realm>
- </digest>
- <kerberos enable="no" onlyssl="no">
- <service></service>
- </kerberos>
+ <basic enable="yes" onlyssl="yes" credentials="directory" node="/Search">
+ <realm></realm>
+ </basic>
+ <digest enable="no" onlyssl="no" credentials="property">
+ <realm></realm>
+ </digest>
+ <kerberos enable="no" onlyssl="no">
+ <service></service>
+ </kerberos>
</authentication>
<accounts/>
Modified: CalendarServer/trunk/doc/Repository/Directory Schema.graffle
===================================================================
--- CalendarServer/trunk/doc/Repository/Directory Schema.graffle 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/doc/Repository/Directory Schema.graffle 2006-12-01 19:18:19 UTC (rev 637)
@@ -35,8 +35,240 @@
<key>Head</key>
<dict>
<key>ID</key>
+ <integer>92</integer>
+ </dict>
+ <key>ID</key>
+ <integer>93</integer>
+ <key>Points</key>
+ <array>
+ <string>{334.488, 329.167}</string>
+ <string>{386.334, 421.667}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>HeadArrow</key>
+ <string>FilledArrow</string>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
<integer>53</integer>
<key>Info</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{386.334, 413.167}, {22, 17}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>ID</key>
+ <integer>92</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{-0.5, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
+
+\f0\b\fs28 \cf0 ...}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>33</integer>
+ </dict>
+ <key>ID</key>
+ <integer>90</integer>
+ <key>Points</key>
+ <array>
+ <string>{334.488, 329.167}</string>
+ <string>{385.512, 351.811}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>HeadArrow</key>
+ <string>FilledArrow</string>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>53</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>TableGroup</string>
+ <key>Graphics</key>
+ <array>
+ <dict>
+ <key>Bounds</key>
+ <string>{{385.512, 329.512}, {90, 14}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>Vertical</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>ID</key>
+ <integer>34</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.263696</string>
+ <key>g</key>
+ <string>0.941637</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ <key>GradientAngle</key>
+ <real>304</real>
+ <key>GradientCenter</key>
+ <string>{-0.294118, -0.264706}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Machine}</string>
+ </dict>
+ <key>TextPlacement</key>
+ <integer>0</integer>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{385.512, 343.512}, {90, 56}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>Vertical</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>ID</key>
+ <integer>35</integer>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.263696</string>
+ <key>g</key>
+ <string>0.941637</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ <key>GradientAngle</key>
+ <real>304</real>
+ <key>GradientCenter</key>
+ <string>{-0.294118, -0.264706}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Align</key>
+ <integer>0</integer>
+ <key>Text</key>
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
+
+\f0\fs24 \cf0 guid\
+name\
+ip address\
+...}</string>
+ </dict>
+ <key>TextPlacement</key>
+ <integer>0</integer>
+ </dict>
+ </array>
+ <key>GridH</key>
+ <array>
+ <integer>34</integer>
+ <integer>35</integer>
+ <array/>
+ </array>
+ <key>GroupConnect</key>
+ <string>YES</string>
+ <key>ID</key>
+ <integer>33</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{-0.5, -0.181438}</string>
+ </array>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>53</integer>
+ <key>Info</key>
<integer>1</integer>
</dict>
<key>ID</key>
@@ -44,7 +276,7 @@
<key>Points</key>
<array>
<string>{147.402, 361.309}</string>
- <string>{192.756, 259.825}</string>
+ <string>{192.756, 257.948}</string>
</array>
<key>Style</key>
<dict>
@@ -77,7 +309,7 @@
<key>Points</key>
<array>
<string>{147.402, 259.008}</string>
- <string>{192.756, 259.825}</string>
+ <string>{192.756, 257.948}</string>
</array>
<key>Style</key>
<dict>
@@ -137,8 +369,8 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
@@ -184,7 +416,7 @@
<key>Align</key>
<integer>0</integer>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -254,8 +486,8 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
@@ -301,7 +533,7 @@
<key>Align</key>
<integer>0</integer>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -344,8 +576,8 @@
<integer>80</integer>
<key>Points</key>
<array>
- <string>{334.488, 300.336}</string>
- <string>{385.512, 300.472}</string>
+ <string>{334.488, 315.833}</string>
+ <string>{385.512, 221.103}</string>
</array>
<key>Style</key>
<dict>
@@ -379,8 +611,8 @@
<integer>79</integer>
<key>Points</key>
<array>
- <string>{334.488, 300.336}</string>
- <string>{385.512, 328.819}</string>
+ <string>{334.488, 315.833}</string>
+ <string>{385.512, 249.449}</string>
</array>
<key>Style</key>
<dict>
@@ -414,8 +646,8 @@
<integer>78</integer>
<key>Points</key>
<array>
- <string>{334.488, 300.336}</string>
- <string>{385.512, 357.165}</string>
+ <string>{334.488, 315.833}</string>
+ <string>{385.512, 277.796}</string>
</array>
<key>Style</key>
<dict>
@@ -449,8 +681,8 @@
<integer>77</integer>
<key>Points</key>
<array>
- <string>{334.488, 286.047}</string>
- <string>{385.512, 266.457}</string>
+ <string>{334.488, 301.667}</string>
+ <string>{385.512, 175.748}</string>
</array>
<key>Style</key>
<dict>
@@ -484,8 +716,8 @@
<integer>76</integer>
<key>Points</key>
<array>
- <string>{334.488, 300.336}</string>
- <string>{385.512, 385.512}</string>
+ <string>{334.488, 315.833}</string>
+ <string>{385.512, 306.142}</string>
</array>
<key>Style</key>
<dict>
@@ -509,7 +741,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{385.512, 374.173}, {204.094, 22.6772}}</string>
+ <string>{{385.512, 294.803}, {204.094, 22.6772}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -538,7 +770,7 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -548,7 +780,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{385.512, 345.827}, {204.094, 22.6772}}</string>
+ <string>{{385.512, 266.457}, {204.094, 22.6772}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -577,7 +809,7 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -587,7 +819,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{385.512, 317.48}, {204.094, 22.6772}}</string>
+ <string>{{385.512, 238.11}, {204.094, 22.6772}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -616,7 +848,7 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -626,87 +858,10 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{516.307, 209.764}, {22, 17}}</string>
+ <string>{{385.512, 209.764}, {204.094, 22.6772}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
- <key>FitText</key>
- <string>YES</string>
- <key>Flow</key>
- <string>Resize</string>
<key>ID</key>
- <integer>72</integer>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
- <dict>
- <key>fill</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- </dict>
- <key>stroke</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- </dict>
- </dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
-
-\f0\b\fs28 \cf0 ...}</string>
- </dict>
- <key>Wrap</key>
- <string>NO</string>
- </dict>
- <dict>
- <key>Class</key>
- <string>LineGraphic</string>
- <key>Head</key>
- <dict>
- <key>ID</key>
- <integer>72</integer>
- </dict>
- <key>ID</key>
- <integer>71</integer>
- <key>Points</key>
- <array>
- <string>{476.22, 159.963}</string>
- <string>{519.859, 209.764}</string>
- </array>
- <key>Style</key>
- <dict>
- <key>stroke</key>
- <dict>
- <key>HeadArrow</key>
- <string>FilledArrow</string>
- <key>LineType</key>
- <integer>1</integer>
- <key>TailArrow</key>
- <string>0</string>
- </dict>
- </dict>
- <key>Tail</key>
- <dict>
- <key>ID</key>
- <integer>65</integer>
- </dict>
- </dict>
- <dict>
- <key>Bounds</key>
- <string>{{385.512, 289.134}, {204.094, 22.6772}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
<integer>70</integer>
<key>Magnets</key>
<array>
@@ -732,7 +887,7 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -742,7 +897,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{385.512, 255.118}, {204.095, 22.6771}}</string>
+ <string>{{385.512, 164.41}, {204.095, 22.6771}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -771,7 +926,7 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -785,51 +940,14 @@
<key>Head</key>
<dict>
<key>ID</key>
- <integer>33</integer>
- <key>Info</key>
- <integer>1</integer>
- </dict>
- <key>ID</key>
- <integer>67</integer>
- <key>Points</key>
- <array>
- <string>{476.22, 159.963}</string>
- <string>{521.575, 159.922}</string>
- </array>
- <key>Style</key>
- <dict>
- <key>stroke</key>
- <dict>
- <key>HeadArrow</key>
- <string>FilledArrow</string>
- <key>LineType</key>
- <integer>1</integer>
- <key>TailArrow</key>
- <string>0</string>
- </dict>
- </dict>
- <key>Tail</key>
- <dict>
- <key>ID</key>
<integer>65</integer>
- <key>Info</key>
- <integer>1</integer>
</dict>
- </dict>
- <dict>
- <key>Class</key>
- <string>LineGraphic</string>
- <key>Head</key>
- <dict>
- <key>ID</key>
- <integer>65</integer>
- </dict>
<key>ID</key>
<integer>66</integer>
<key>Points</key>
<array>
- <string>{334.488, 273.11}</string>
- <string>{385.512, 172.913}</string>
+ <string>{334.488, 287.11}</string>
+ <string>{385.512, 104.882}</string>
</array>
<key>Style</key>
<dict>
@@ -851,7 +969,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{385.512, 119.055}, {90.7086, 107.716}}</string>
+ <string>{{385.512, 68.0315}, {181.417, 73.7008}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -883,138 +1001,21 @@
<key>Align</key>
<integer>0</integer>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
-\f0\fs24 \cf0 \{\
- scheme = (\
- (host, port),\
- ...\
- ),\
- ...\
-\}}</string>
+\f0\fs24 \cf0 (\
+ caldav:http:hostname:8008,\
+ caldav:https:hostname:8443,\
+ \'c9\
+)}</string>
</dict>
</dict>
<dict>
- <key>Class</key>
- <string>TableGroup</string>
- <key>Graphics</key>
- <array>
- <dict>
- <key>Bounds</key>
- <string>{{521.575, 137.756}, {90, 14}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>FitText</key>
- <string>Vertical</string>
- <key>Flow</key>
- <string>Resize</string>
- <key>ID</key>
- <integer>34</integer>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
- <dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.263696</string>
- <key>g</key>
- <string>0.941637</string>
- <key>r</key>
- <string>1</string>
- </dict>
- <key>GradientAngle</key>
- <real>304</real>
- <key>GradientCenter</key>
- <string>{-0.294118, -0.264706}</string>
- </dict>
- </dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
-
-\f0\b\fs24 \cf0 Machine}</string>
- </dict>
- <key>TextPlacement</key>
- <integer>0</integer>
- </dict>
- <dict>
- <key>Bounds</key>
- <string>{{521.575, 151.756}, {90, 42}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>FitText</key>
- <string>Vertical</string>
- <key>Flow</key>
- <string>Resize</string>
- <key>ID</key>
- <integer>35</integer>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
- <dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.263696</string>
- <key>g</key>
- <string>0.941637</string>
- <key>r</key>
- <string>1</string>
- </dict>
- <key>GradientAngle</key>
- <real>304</real>
- <key>GradientCenter</key>
- <string>{-0.294118, -0.264706}</string>
- </dict>
- </dict>
- <key>Text</key>
- <dict>
- <key>Align</key>
- <integer>0</integer>
- <key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
-{\colortbl;\red255\green255\blue255;}
-\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-
-\f0\fs24 \cf0 name\
-ip address\
-...}</string>
- </dict>
- <key>TextPlacement</key>
- <integer>0</integer>
- </dict>
- </array>
- <key>GridH</key>
- <array>
- <integer>34</integer>
- <integer>35</integer>
- <array/>
- </array>
- <key>GroupConnect</key>
- <string>YES</string>
- <key>ID</key>
- <integer>33</integer>
- <key>Magnets</key>
- <array>
- <string>{-0.5, -0.104178}</string>
- </array>
- </dict>
- <dict>
<key>Bounds</key>
- <string>{{193.157, 181.417}, {22, 17}}</string>
+ <string>{{183.333, 137.271}, {22, 17}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -1046,8 +1047,8 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
@@ -1069,7 +1070,7 @@
<key>Points</key>
<array>
<string>{147.402, 145.622}</string>
- <string>{193.266, 181.417}</string>
+ <string>{183.333, 145.736}</string>
</array>
<key>Style</key>
<dict>
@@ -1102,7 +1103,7 @@
<key>Points</key>
<array>
<string>{147.402, 145.622}</string>
- <string>{192.756, 259.825}</string>
+ <string>{192.756, 257.948}</string>
</array>
<key>Style</key>
<dict>
@@ -1164,8 +1165,8 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
@@ -1176,7 +1177,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{192.756, 252.11}, {141.732, 56}}</string>
+ <string>{{192.756, 252.11}, {141.732, 84}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -1211,15 +1212,17 @@
<key>Align</key>
<integer>0</integer>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
-\f0\fs24 \cf0 name\
-location\
+\f0\fs24 \cf0 guid\
+name\
+services\
principal path template\
-user address template}</string>
+user address template\
+machines}</string>
</dict>
<key>TextPlacement</key>
<integer>0</integer>
@@ -1237,10 +1240,11 @@
<integer>53</integer>
<key>Magnets</key>
<array>
- <string>{-0.5, -0.189787}</string>
- <string>{0.5, -2.38419e-07}</string>
- <string>{0.5, 0.388945}</string>
- <string>{0.5, 0.184814}</string>
+ <string>{-0.5, -0.297571}</string>
+ <string>{0.5, 0}</string>
+ <string>{0.5, 0.293093}</string>
+ <string>{0.5, 0.148535}</string>
+ <string>{0.5, 0.429147}</string>
</array>
</dict>
<dict>
@@ -1283,8 +1287,8 @@
<key>Text</key>
<dict>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
-{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;}
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
+{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
@@ -1330,7 +1334,7 @@
<key>Align</key>
<integer>0</integer>
<key>Text</key>
- <string>{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf410
+ <string>{\rtf1\mac\ansicpg10000\cocoartf881\cocoasubrtf100
{\fonttbl\f0\fswiss\fcharset77 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
@@ -1370,8 +1374,6 @@
<integer>10</integer>
<key>ShowsGrid</key>
<string>YES</string>
- <key>SnapsToGrid</key>
- <string>YES</string>
</dict>
<key>GuidesLocked</key>
<string>NO</string>
@@ -1479,7 +1481,7 @@
</dict>
</array>
<key>ModificationDate</key>
- <string>2006-10-02 17:36:29 -0700</string>
+ <string>2006-11-16 19:02:35 -0800</string>
<key>Modifier</key>
<string>Wilfredo Sánchez</string>
<key>NotesVisible</key>
@@ -1496,13 +1498,13 @@
<dict>
<key>NSBottomMargin</key>
<array>
- <string>float</string>
- <string>0</string>
+ <string>coded</string>
+ <string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
</array>
<key>NSLeftMargin</key>
<array>
- <string>float</string>
- <string>0</string>
+ <string>coded</string>
+ <string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
</array>
<key>NSOrientation</key>
<array>
@@ -1516,18 +1518,18 @@
</array>
<key>NSRightMargin</key>
<array>
- <string>float</string>
- <string>0</string>
+ <string>coded</string>
+ <string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
</array>
<key>NSScalingFactor</key>
<array>
- <string>float</string>
- <string>1.2</string>
+ <string>coded</string>
+ <string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFkl4M/8zMzQAAAAIY=</string>
</array>
<key>NSTopMargin</key>
<array>
- <string>float</string>
- <string>0</string>
+ <string>coded</string>
+ <string>BAt0eXBlZHN0cmVhbYED6IQBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFklwCG</string>
</array>
</dict>
<key>ReadOnly</key>
@@ -1567,7 +1569,7 @@
<key>ShowStatusBar</key>
<true/>
<key>VisibleRegion</key>
- <string>{{0, 0}, {640, 490.833}}</string>
+ <string>{{0, 1.19209e-06}, {640, 490.833}}</string>
<key>Zoom</key>
<string>1</string>
</dict>
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.auth.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -11,14 +11,14 @@
raise NotImplementedError("Only IPrincipal interface is supported")
-@@ -52,9 +52,23 @@
+@@ -52,33 +52,44 @@
class PrincipalCredentials(object):
implements(IPrincipalCredentials)
- def __init__(self, principal, principalURI, credentials):
- self.principal = principal
- self.principalURI = principalURI
-+ def __init__(self, authnPrincipal, authnURI, authzPrincipal, authzURI, credentials):
++ def __init__(self, authnPrincipal, authzPrincipal, credentials):
+ """
+ Initialize with both authentication and authorization values. Note that in most cases theses will be the same
+ since HTTP auth makes no distinction between the two - but we may be layering some addition auth on top of this
@@ -28,18 +28,20 @@
+ @param authnURI: C{str} containing the URI of the authenticated principal.
+ @param authzPrincipal: L{IDAVPrincipalResource} for the authorized principal.
+ @param authzURI: C{str} containing the URI of the authorized principal.
-+ @param credentials: L{IPrincipalCredentials} for the authentication credentials.
++ @param credentials: L{ICredentials} for the authentication credentials.
+ """
-+
+ self.authnPrincipal = authnPrincipal
-+ self.authnURI = authnURI
+ self.authzPrincipal = authzPrincipal
-+ self.authzURI = authzURI
self.credentials = credentials
def checkPassword(self, password):
-@@ -66,19 +80,20 @@
+ return self.credentials.checkPassword(password)
+
+-class TwistedPropertyChecker:
++class TwistedPropertyChecker(object):
+ implements(checkers.ICredentialsChecker)
+
credentialInterfaces = (IPrincipalCredentials,)
- def _cbPasswordMatch(self, matched, principalURI):
@@ -49,9 +51,9 @@
+ # We return both URIs
+ return principalURIs
else:
- raise error.UnauthorizedLogin(
+- raise error.UnauthorizedLogin(
- "Bad credentials for: %s" % (principalURI,))
-+ "Bad credentials for: %s" % (principalURIs[0],))
++ raise error.UnauthorizedLogin("Bad credentials for: %s" % (principalURIs[0],))
def requestAvatarId(self, credentials):
pcreds = IPrincipalCredentials(credentials)
@@ -60,7 +62,7 @@
d = defer.maybeDeferred(credentials.checkPassword, pswd)
- d.addCallback(self._cbPasswordMatch, pcreds.principalURI)
-+ d.addCallback(self._cbPasswordMatch, (pcreds.authnURI, pcreds.authzURI,))
++ d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
return d
##
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,7 @@
===================================================================
--- twisted/web2/dav/element/rfc4331.py (revision 0)
+++ twisted/web2/dav/element/rfc4331.py (revision 0)
-@@ -0,0 +1,110 @@
+@@ -0,0 +1,55 @@
+##
+# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+#
@@ -58,58 +58,3 @@
+ name = "quota-used-bytes"
+ hidden = True
+ protected = True
-+##
-+# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
-+#
-+# Permission is hereby granted, free of charge, to any person obtaining a copy
-+# of this software and associated documentation files (the "Software"), to deal
-+# in the Software without restriction, including without limitation the rights
-+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+# copies of the Software, and to permit persons to whom the Software is
-+# furnished to do so, subject to the following conditions:
-+#
-+# The above copyright notice and this permission notice shall be included in all
-+# copies or substantial portions of the Software.
-+#
-+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-+# SOFTWARE.
-+#
-+# DRI: Cyrus Daboo, cdaboo at apple.com
-+##
-+
-+"""
-+RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
-+
-+This module provides XML element definitions for use with WebDAV.
-+
-+See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
-+"""
-+
-+from twisted.web2.dav.element.base import WebDAVTextElement
-+
-+##
-+# Section 3 & 4 (Quota Properties)
-+##
-+
-+class QuotaAvailableBytes (WebDAVTextElement):
-+ """
-+ Property which contains the the number of bytes available under the
-+ current quota to store data in a collection (RFC 4331, section 3)
-+ """
-+ name = "quota-available-bytes"
-+ hidden = True
-+ protected = True
-+
-+class QuotaUsedBytes (WebDAVTextElement):
-+ """
-+ Property which contains the the number of bytes used under the
-+ current quota to store data in a collection (RFC 4331, section 4)
-+ """
-+ name = "quota-used-bytes"
-+ hidden = True
-+ protected = True
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -20,7 +20,26 @@
@return: An L{Deferred} that fires when all the children have been found
"""
-@@ -180,6 +182,80 @@
+@@ -125,15 +127,10 @@
+ L{responsecode.UNAUTHORIZED}) if not authorized.
+ """
+
+- def principalCollections(request):
++ def principalCollections():
+ """
+- Provides the DAV:HRef's of collection resources which contain principal
+- resources which may be used in access control entries on this resource.
+- (RFC 3744, section 5.8)
+- @param request: the request being processed.
+- @return: a deferred sequence of L{davxml.HRef}s referring to
+- collection resources which implement the
+- C{DAV:principal-property-search} C{REPORT}.
++ @return: an interable of L{IDAVPrincipalCollectionResource}s which
++ contain principals used in ACLs for this resource.
+ """
+
+ def setAccessControlList(acl):
+@@ -180,6 +177,80 @@
the specified principal.
"""
@@ -101,3 +120,18 @@
class IDAVPrincipalResource (IDAVResource):
"""
WebDAV principal resource. (RFC 3744, section 2)
+@@ -212,3 +283,14 @@
+ directly a member. (RFC 3744, section 4.4)
+ @return: a iterable of group principal URLs.
+ """
++
++class IDAVPrincipalCollectionResource (IDAVResource):
++ """
++ WebDAV principal collection resource. (RFC 3744, section 5.8)
++ """
++ def principalCollectionURL():
++ """
++ Provides a URL for this resource which may be used to identify this
++ resource in ACL requests. (RFC 3744, section 5.8)
++ @return: a URL.
++ """
Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.report_principal_property_search.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,20 @@
+Index: twisted/web2/dav/method/report_principal_property_search.py
+===================================================================
+--- twisted/web2/dav/method/report_principal_property_search.py (revision 18545)
++++ twisted/web2/dav/method/report_principal_property_search.py (working copy)
+@@ -127,13 +127,8 @@
+ matchcount = 0
+
+ if applyTo:
+- # Get the principal collection set
+- pset = waitForDeferred(self.principalCollections(request))
+- yield pset
+- pset = pset.getResult()
+-
+- for phref in pset:
+- uri = str(phref)
++ for principalCollection in self.principalCollections():
++ uri = principalCollection.principalCollectionURL()
+ resource = waitForDeferred(request.locateResource(uri))
+ yield resource
+ resource = resource.getResult()
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,35 @@
===================================================================
--- twisted/web2/dav/resource.py (revision 18545)
+++ twisted/web2/dav/resource.py (working copy)
-@@ -130,6 +130,8 @@
+@@ -40,10 +40,18 @@
+ "unauthenticatedPrincipal",
+ ]
+
++import __builtin__
++if not hasattr(__builtin__, "set"):
++ import sets.Set as set
++if not hasattr(__builtin__, "frozenset"):
++ import sets.ImmutableSet as frozenset
++
+ import urllib
+
+ from zope.interface import implements
+ from twisted.python import log
++from twisted.python.failure import Failure
++from twisted.cred.error import UnauthorizedLogin
+ from twisted.internet.defer import Deferred, maybeDeferred, succeed
+ from twisted.internet.defer import waitForDeferred, deferredGenerator
+ from twisted.internet import reactor
+@@ -57,7 +65,7 @@
+ from twisted.web2.dav import davxml
+ from twisted.web2.dav.davxml import dav_namespace, lookupElement
+ from twisted.web2.dav.davxml import twisted_dav_namespace, twisted_private_namespace
+-from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource
++from twisted.web2.dav.idav import IDAVResource, IDAVPrincipalResource, IDAVPrincipalCollectionResource
+ from twisted.web2.dav.http import NeedPrivilegesResponse
+ from twisted.web2.dav.noneprops import NonePropertyStore
+ from twisted.web2.dav.util import unimplemented, parentForURL, joinURL
+@@ -130,6 +138,8 @@
(dav_namespace, "acl-restrictions" ), # RFC 3744, section 5.6
(dav_namespace, "inherited-acl-set" ), # RFC 3744, section 5.7
(dav_namespace, "principal-collection-set" ), # RFC 3744, section 5.8
@@ -11,7 +39,7 @@
(twisted_dav_namespace, "resource-class"),
)
-@@ -166,6 +168,14 @@
+@@ -166,6 +176,14 @@
if qname[0] == twisted_private_namespace:
return succeed(False)
@@ -26,16 +54,34 @@
return succeed(qname in self.liveProperties or self.deadProperties().contains(qname))
def readProperty(self, property, request):
-@@ -253,7 +263,7 @@
+@@ -239,8 +257,10 @@
+ )
+ if name == "supported-report-set":
+- supported = [davxml.SupportedReport(report,) for report in self.supportedReports()]
+- return davxml.SupportedReportSet(*supported)
++ return davxml.SupportedReportSet(*[
++ davxml.SupportedReport(report,)
++ for report in self.supportedReports()
++ ])
+
+ if name == "supported-privilege-set":
+ return self.supportedPrivileges(request)
+@@ -252,9 +272,10 @@
+ return davxml.InheritedACLSet(*self.inheritedACLSet())
+
if name == "principal-collection-set":
- d = self.principalCollections(request)
+- d = self.principalCollections(request)
- d.addCallback(lambda collections: davxml.PrincipalCollectionSet(*collections))
-+ d.addCallback(lambda collections: davxml.PrincipalCollectionSet(*[davxml.HRef.fromString(uri) for uri in collections]))
- return d
+- return d
++ return davxml.PrincipalCollectionSet(*[
++ davxml.HRef(principalCollection.principalCollectionURL())
++ for principalCollection in self.principalCollections()
++ ])
def ifAllowed(privileges, callback):
-@@ -286,7 +296,33 @@
+ def onError(failure):
+@@ -286,7 +307,33 @@
d.addCallback(gotACL)
return d
return ifAllowed((davxml.ReadACL(),), callback)
@@ -69,9 +115,13 @@
elif namespace == twisted_dav_namespace:
if name == "resource-class":
class ResourceClass (davxml.WebDAVTextElement):
-@@ -366,12 +402,26 @@
- # FIXME: A set would be better here, that that's a python 2.4+ feature.
- qnames = list(self.liveProperties)
+@@ -363,15 +410,28 @@
+ """
+ See L{IDAVResource.listProperties}.
+ """
+- # FIXME: A set would be better here, that that's a python 2.4+ feature.
+- qnames = list(self.liveProperties)
++ qnames = set(self.liveProperties)
+ # Add dynamic live properties that exist
+ dynamicLiveProperties = (
@@ -97,7 +147,30 @@
def listAllprop(self, request):
"""
Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -509,6 +559,9 @@
+@@ -465,8 +525,22 @@
+ return super(DAVPropertyMixIn, self).displayName()
+
+ class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
++ """
++ WebDAV resource.
++ """
+ implements(IDAVResource)
+
++ def __init__(self, principalCollections=None):
++ """
++ @param principalCollections: an iterable of L{IDAVPrincipalCollectionResource}s
++ which contain principals to be used in ACLs for this resource.
++ """
++ if principalCollections is not None:
++ self._principalCollections = frozenset([
++ IDAVPrincipalCollectionResource(principalCollection)
++ for principalCollection in principalCollections
++ ])
++
+ ##
+ # DAV
+ ##
+@@ -509,6 +583,9 @@
reactor.callLater(0, getChild)
def checkPrivileges(child):
@@ -107,7 +180,7 @@
if privileges is None:
return child
-@@ -517,14 +570,17 @@
+@@ -517,14 +594,17 @@
return d
def gotChild(child, childpath):
@@ -132,7 +205,7 @@
reactor.callLater(0, getChild)
-@@ -535,10 +591,10 @@
+@@ -535,10 +615,10 @@
completionDeferred.callback(None)
else:
childpath = joinURL(basepath, childname)
@@ -147,16 +220,22 @@
getChild()
-@@ -564,7 +620,7 @@
+@@ -564,19 +644,21 @@
See L{IDAVResource.authorize}.
"""
def onError(failure):
- log.err("Invalid authentication details: %s" % (request,))
-+ log.err("Invalid authentication details: %s" % (failure,))
- raise HTTPError(UnauthorizedResponse(
+- raise HTTPError(UnauthorizedResponse(
++ failure.trap(UnauthorizedLogin)
++
++ log.err("Authentication failed: %s" % (failure.value,))
++ return Failure(HTTPError(UnauthorizedResponse(
request.credentialFactories,
request.remoteAddr
-@@ -574,9 +630,9 @@
+- ))
++ )))
+
+ def onAuth(result):
def onErrors(failure):
failure.trap(AccessDeniedError)
@@ -168,8 +247,17 @@
response = UnauthorizedResponse(request.credentialFactories,
request.remoteAddr)
else:
-@@ -600,16 +656,22 @@
+@@ -587,7 +669,7 @@
+ # class is supposed to be a FORBIDDEN status code and
+ # "Authorization will not help" according to RFC2616
+ #
+- raise HTTPError(response)
++ return Failure(HTTPError(response))
+ d = self.checkPrivileges(request, privileges, recurse)
+ d.addErrback(onErrors)
+@@ -600,16 +682,21 @@
+
def authenticate(self, request):
def loginSuccess(result):
- request.user = result[1]
@@ -177,7 +265,6 @@
+ """
+ @param result: returned tuple from auth.DAVRealm.requestAvatar.
+ """
-+
+ request.authnUser = result[1]
+ request.authzUser = result[2]
+ return (request.authnUser, request.authzUser,)
@@ -195,28 +282,29 @@
authHeader = request.headers.getHeader('authorization')
-@@ -625,9 +687,11 @@
+@@ -625,9 +712,10 @@
# Try to match principals in each principal collection on the resource
def gotDetails(details):
- principal = IDAVPrincipalResource(details[0])
- principalURI = details[1]
- return PrincipalCredentials(principal, principalURI, creds)
-+ authnPrincipal = IDAVPrincipalResource(details[0][0])
-+ authnURI = details[0][1]
-+ authzPrincipal = IDAVPrincipalResource(details[1][0])
-+ authzURI = details[1][1]
-+ return PrincipalCredentials(authnPrincipal, authnURI, authzPrincipal, authzURI, creds)
++ authnPrincipal, authzPrincipal = details
++ authnPrincipal = IDAVPrincipalResource(authnPrincipal)
++ authzPrincipal = IDAVPrincipalResource(authzPrincipal)
++ return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
def login(pcreds):
d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -635,13 +699,14 @@
+@@ -635,13 +723,15 @@
return d
- d = self.findPrincipalForAuthID(request, creds.username)
+- d.addCallback(gotDetails).addCallback(login)
+ d = self.principalsForAuthID(request, creds.username)
- d.addCallback(gotDetails).addCallback(login)
++ d.addCallback(gotDetails)
++ d.addCallback(login)
return d
else:
@@ -228,7 +316,7 @@
##
# ACL
-@@ -650,10 +715,10 @@
+@@ -650,49 +740,23 @@
def currentPrincipal(self, request):
"""
@param request: the request being processed.
@@ -242,43 +330,37 @@
else:
return unauthenticatedPrincipal
-@@ -666,32 +731,26 @@
- present on this resource, it tries to get it from the parent, unless it
- is the root or has no parent.
+- def principalCollections(self, request):
++ def principalCollections(self):
"""
+ See L{IDAVResource.accessControlList}.
+-
+- This implementation tries to read the L{davxml.PrincipalCollectionSet}
+- from the dead property store of this resource and uses that. If not
+- present on this resource, it tries to get it from the parent, unless it
+- is the root or has no parent.
+ """
- try:
- principalCollections = self.readDeadProperty(davxml.PrincipalCollectionSet).childrenOfType(davxml.HRef)
- except HTTPError, e:
- if e.response.code != responsecode.NOT_FOUND:
- raise
-+ if self.hasDeadProperty(davxml.PrincipalCollectionSet):
-+ return succeed([
-+ str(href) for href in
-+ self.readDeadProperty(davxml.PrincipalCollectionSet).childrenOfType(davxml.HRef)
-+ ])
++ if hasattr(self, "_principalCollections"):
++ return self._principalCollections
++ else:
++ return ()
- principalCollections = []
-+ myURL = request.urlForResource(self)
-+ if myURL == "/":
-+ return succeed(())
-
+-
- # Try the parent
- myURL = request.urlForResource(self)
- if myURL != "/":
- parentURL = parentForURL(myURL)
-+ def gotParent(parent):
-+ if parent is None:
-+ return ()
-+ else:
-+ return parent.principalCollections(request)
-
+-
- parent = waitForDeferred(request.locateResource(parentURL))
- yield parent
- parent = parent.getResult()
-+ d = request.locateResource(parentForURL(myURL))
-+ d.addCallback(gotParent)
-+ return d
-
+-
- if parent:
- principalCollections = waitForDeferred(parent.principalCollections(request))
- yield principalCollections
@@ -288,11 +370,61 @@
-
- principalCollections = deferredGenerator(principalCollections)
-
- def defaultAccessControlList(self):
+- def defaultAccessControlList(self):
++ def defaultRootAccessControlList(self):
"""
@return: the L{davxml.ACL} element containing the default access control
-@@ -1146,49 +1205,95 @@
+ list for this resource.
+@@ -704,6 +768,17 @@
+ #
+ return readonlyACL
++ def defaultAccessControlList(self):
++ """
++ @return: the L{davxml.ACL} element containing the default access control
++ list for this resource.
++ """
++ #
++ # The default behaviour is no ACL; we should inherrit from the parent
++ # collection.
++ #
++ return davxml.ACL()
++
+ def setAccessControlList(self, acl):
+ """
+ See L{IDAVResource.setAccessControlList}.
+@@ -1032,9 +1107,9 @@
+
+ if myURL == "/":
+ # If we get to the root without any ACLs, then use the default.
++ acl = self.defaultRootAccessControlList()
++ else:
+ acl = self.defaultAccessControlList()
+- else:
+- acl = davxml.ACL()
+
+ # Dynamically update privileges for those ace's that are inherited.
+ if inheritance:
+@@ -1070,7 +1145,7 @@
+ # Adjust ACE for inherit on this resource
+ children = list(ace.children)
+ children.remove(TwistedACLInheritable())
+- children.append(davxml.Inherited(davxml.HRef.fromString(parentURL)))
++ children.append(davxml.Inherited(davxml.HRef(parentURL)))
+ aces.append(davxml.ACE(*children))
+ else:
+ aces.extend(inherited_aces)
+@@ -1122,7 +1197,7 @@
+ # Adjust ACE for inherit on this resource
+ children = list(ace.children)
+ children.remove(TwistedACLInheritable())
+- children.append(davxml.Inherited(davxml.HRef.fromString(request.urlForResource(self))))
++ children.append(davxml.Inherited(davxml.HRef(request.urlForResource(self))))
+ aces.append(davxml.ACE(*children))
+
+ # Filter out those that do not have a principal match with the current principal
+@@ -1146,49 +1221,69 @@
+
This implementation returns an empty set.
"""
-
@@ -320,73 +452,59 @@
It will errback with an HTTPError(responsecode.FORBIDDEN) if
the principal isn't found.
"""
-+ def gotAuthn(principal):
-+ if principal is None:
-+ log.msg("Could not find principal matching user id: %s" % (authid,))
-+ raise HTTPError(responsecode.FORBIDDEN)
-+
-+ authnPrincipal, authnURI = principal
-+
-+ def gotAuthz(principal):
-+ authzPrincipal, authzURI = principal
-+ return ((authnPrincipal, authnURI), (authzPrincipal, authzURI))
-+
-+ d = self.authorizationPrincipal(request, authid, authnPrincipal, authnURI)
-+ d.addCallback(gotAuthz)
-+ return d
-+
-+ d = self.findPrincipalForAuthID(request, authid)
-+ d.addCallback(gotAuthn)
-+ return d
-+
-+ def findPrincipalForAuthID(self, request, authid):
-+ """
-+ Return authentication and authoirization prinicipal identifiers for the
-+ authentication identifer passed in. In this implementation authn and authz
-+ principals are the same.
-+
-+ @param request: the L{IRequest} for the request in progress.
-+ @param authid: a string containing the
-+ authentication/authorization identifier for the principal
-+ to lookup.
-+ @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
-+ that is found; {principalURI} is the C{str} URI of the principal.
-+ If not found return None.
-+ """
- # Try to match principals in each principal collection on the resource
- collections = waitForDeferred(self.principalCollections(request))
- yield collections
- collections = collections.getResult()
+- # Try to match principals in each principal collection on the resource
+- collections = waitForDeferred(self.principalCollections(request))
+- yield collections
+- collections = collections.getResult()
++ authnPrincipal = self.findPrincipalForAuthID(authid)
- for collection in collections:
+- for collection in collections:
- principalURI = joinURL(str(collection), authid)
-+ principalURI = joinURL(collection, authid)
++ if authnPrincipal is None:
++ log.msg("Could not find the principal resource for user id: %s" % (authid,))
++ raise HTTPError(responsecode.FORBIDDEN)
- principal = waitForDeferred(request.locateResource(principalURI))
- yield principal
- principal = principal.getResult()
+- principal = waitForDeferred(request.locateResource(principalURI))
+- yield principal
+- principal = principal.getResult()
++ d = self.authorizationPrincipal(request, authid, authnPrincipal)
++ d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
++ return d
- if isPrincipalResource(principal):
- yield (principal, principalURI)
-+ if isPrincipalResource(principal) and principal.exists():
-+ yield principal, principalURI
- return
- else:
+- return
+- else:
- principalCollections = waitForDeferred(self.principalCollections(request))
- yield principalCollections
- principalCollections = principalCollections.getResult()
-+ yield None
-+ return
++ def findPrincipalForAuthID(self, authid):
++ """
++ Return authentication and authoirization prinicipal identifiers for the
++ authentication identifer passed in. In this implementation authn and authz
++ principals are the same.
- if len(principalCollections) == 0:
- log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
- else:
- log.msg("Could not find principal matching user id: %s" % authid)
- raise HTTPError(responsecode.FORBIDDEN)
++ @param authid: a string containing the
++ authentication/authorization identifier for the principal
++ to lookup.
++ @return: a tuple of C{(principal, principalURI)} where: C{principal} is the L{Principal}
++ that is found; {principalURI} is the C{str} URI of the principal.
++ If not found return None.
++ """
++ for collection in self.principalCollections():
++ principal = collection.principalForUser(authid)
++ if principal is not None:
++ return principal
++ return None
+
+- findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
-
- findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
-
-+ def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
++ def authorizationPrincipal(self, request, authid, authnPrincipal):
+ """
+ Determine the authorization principal for the given request and authentication principal.
+ This implementation simply uses aht authentication principalk as the authoization principal.
@@ -395,16 +513,50 @@
+ @param authid: a string containing the uthentication/authorization identifier
+ for the principal to lookup.
+ @param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
-+ @param authnURI: a C{str} containing the URI of the authenticated principal
-+ @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
++ @return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
+ resource and URI respectively.
+ """
-+ return succeed((authnPrincipal, authnURI,))
++ return succeed(authnPrincipal)
+
def samePrincipal(self, principal1, principal2):
"""
Check whether the two prinicpals are exactly the same in terms of
-@@ -1511,6 +1616,265 @@
+@@ -1213,7 +1308,6 @@
+ return False
+
+ def matchPrincipal(self, principal1, principal2, request):
+-
+ """
+ Check whether the principal1 is a principal in the set defined by
+ principal2.
+@@ -1238,6 +1332,9 @@
+ if isinstance(principal1, davxml.Unauthenticated):
+ yield False
+ return
++ elif isinstance(principal1, davxml.All):
++ yield False
++ return
+ else:
+ yield True
+ return
+@@ -1265,7 +1362,6 @@
+
+ assert principal2 is not None, "principal2 is None"
+
+-
+ # Compare two HRefs and do group membership test as well
+ if principal1 == principal2:
+ yield True
+@@ -1426,7 +1522,7 @@
+ log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
+ yield None
+ return
+- principal = davxml.HRef.fromString(self.principalURL())
++ principal = davxml.HRef(self.principalURL())
+
+ if isinstance(principal, davxml.HRef):
+ yield principal
+@@ -1511,6 +1607,265 @@
return None
##
@@ -550,7 +702,7 @@
+ assert maxsize is None or isinstance(maxsize, int), "maxsize must be an int or None"
+
+ if maxsize is not None:
-+ self.writeDeadProperty(TwistedQuotaRootProperty.fromString(str(maxsize)))
++ self.writeDeadProperty(TwistedQuotaRootProperty(str(maxsize)))
+ else:
+ # Remove both the root and the cached used value
+ self.removeDeadProperty(TwistedQuotaRootProperty)
@@ -640,7 +792,7 @@
+ else:
+ # Do brute force size determination and cache the result in the private property
+ def _defer(result):
-+ self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(result)))
++ self.writeDeadProperty(TwistedQuotaUsedProperty(str(result)))
+ return result
+ d = self.quotaSize(request)
+ d.addCallback(_defer)
@@ -660,7 +812,7 @@
+ # Get current value
+ def _defer(size):
+ size += adjust
-+ self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(size)))
++ self.writeDeadProperty(TwistedQuotaUsedProperty(str(size)))
+
+ d = self.currentQuotaUse(request)
+ d.addCallback(_defer)
@@ -670,7 +822,7 @@
# HTTP
##
-@@ -1558,7 +1922,7 @@
+@@ -1558,7 +1913,7 @@
"""
DAV resource with no children.
"""
@@ -679,7 +831,32 @@
return succeed(None)
class DAVPrincipalResource (DAVLeafResource):
-@@ -1712,6 +2076,37 @@
+@@ -1673,6 +2028,24 @@
+ else:
+ return uri in self.groupMembers()
+
++class DAVPrincipalCollectionResource (DAVResource):
++ """
++ WebDAV principal collection resource. (RFC 3744, section 5.8)
++ """
++ implements(IDAVPrincipalCollectionResource)
++
++ def __init__(self, url, principalCollections=()):
++ """
++ @param url: This resource's URL.
++ """
++ DAVResource.__init__(self, principalCollections=principalCollections)
++
++ assert url.endswith("/"), "Collection URL must end in '/'"
++ self._url = url
++
++ def principalCollectionURL(self):
++ return self._url
++
+ class AccessDeniedError(Exception):
+ def __init__(self, errors):
+ """
+@@ -1712,6 +2085,37 @@
davxml.registerElement(TwistedACLInheritable)
davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -24,7 +24,42 @@
try:
from twisted.web2.dav.xattrprops import xattrPropertyStore as DeadPropertyStore
-@@ -75,6 +75,12 @@
+@@ -52,9 +52,11 @@
+
+ Extends twisted.web2.static.File to handle WebDAV methods.
+ """
+- def __init__(self, path,
+- defaultType="text/plain",
+- indexNames=None):
++ def __init__(
++ self, path,
++ defaultType="text/plain", indexNames=None,
++ principalCollections=()
++ ):
+ """
+ @param path: the path of the file backing this resource.
+ @param defaultType: the default mime type (as a string) for this
+@@ -62,11 +64,14 @@
+ @param indexNames: a sequence of index file names.
+ @param acl: an L{IDAVAccessControlList} with the .
+ """
+- super(DAVFile, self).__init__(path,
+- defaultType = defaultType,
+- ignoredExts = (),
+- processors = None,
+- indexNames = indexNames)
++ File.__init__(
++ self, path,
++ defaultType = defaultType,
++ ignoredExts = (),
++ processors = None,
++ indexNames = indexNames,
++ )
++ DAVResource.__init__(self, principalCollections=principalCollections)
+
+ def __repr__(self):
+ return "<%s: %s>" % (self.__class__.__name__, self.fp.path)
+@@ -75,6 +80,12 @@
# WebDAV
##
@@ -37,7 +72,7 @@
def davComplianceClasses(self):
return ("1", "access-control") # Add "2" when we have locking
-@@ -87,7 +93,6 @@
+@@ -87,7 +98,6 @@
"""
See L{IDAVResource.isCollection}.
"""
@@ -45,7 +80,7 @@
return self.fp.isdir()
##
-@@ -98,6 +103,50 @@
+@@ -98,6 +108,50 @@
return succeed(davPrivilegeSet)
##
@@ -96,14 +131,27 @@
# Workarounds for issues with File
##
-@@ -142,53 +191,50 @@
- directory contents that they have read permissions for.
- """
- if not self.fp.exists():
+@@ -132,63 +186,11 @@
+ return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
+
+ def createSimilarFile(self, path):
+- return self.__class__(path, defaultType=self.defaultType, indexNames=self.indexNames[:])
++ return self.__class__(
++ path, self.defaultType, self.indexNames[:],
++ principalCollections=self.principalCollections()
++ )
+
+- def render(self, request):
+- """
+- This is a direct copy of web2.static.render with the
+- listChildren behavior replaced with findChildren to ensure
+- that the current authenticated principal can only list
+- directory contents that they have read permissions for.
+- """
+- if not self.fp.exists():
- yield responsecode.NOT_FOUND
- return
-+ return responsecode.NOT_FOUND
-
+-
- if self.fp.isdir():
- if request.uri[-1] != "/":
- # Redirect to include trailing '/' in URI
@@ -119,32 +167,19 @@
- filtered_aces = waitForDeferred(self.inheritedACEsforChildren(request))
- yield filtered_aces
- filtered_aces = filtered_aces.getResult()
-+ if not self.fp.isdir():
-+ # Do regular resource behavior from superclass
-+ return super(DAVFile, self).render(request)
-
+-
- children = []
-+ #
-+ # Do custom rendering of directory so that we can enforce ACLs.
-+ #
-
+-
- def found(request, uri):
- children.append(uri.split("/")[-1].rstrip("/"))
-+ if request.uri[-1] != "/":
-+ # Redirect to include trailing '/' in URI
-+ return RedirectResponse(request.unparseURL(path=request.path+'/'))
-
+-
- x = waitForDeferred(
- self.findChildren("1", request, found, (davxml.Read(),),
- inherited_aces=filtered_aces)
- )
- yield x
- x = x.getResult()
-+ # Render from the index file, if we have one
-+ index_fp = self.fp.childSearchPreauth(*self.indexNames)
-+ if index_fp:
-+ return self.createSimilarFile(index_fp.path).render(request)
-
+-
- # Render from a DirectoryLister
- standin = dirlist.DirectoryLister(
- self.fp.path,
@@ -155,35 +190,12 @@
- )
- yield standin.render(request)
- return
-+ # Render from a DirectoryLister
-+ def findChildren(filtered_aces):
-+ children = []
-
+-
- # Do regular resource behavior from superclass
- yield super(DAVFile, self).render(request)
-
- render = deferredGenerator(render)
-+ def found(request, uri):
-+ children.append(uri.rstrip("/").split("/")[-1])
-
-+ d = self.findChildren("1", request, found,
-+ (davxml.Read(),), inherited_aces=filtered_aces)
-+ d.addCallback(render, children)
-+ return d
-+
-+ def render(_, children):
-+ return dirlist.DirectoryLister(
-+ self.fp.path,
-+ children,
-+ self.contentTypes,
-+ self.contentEncodings,
-+ self.defaultType
-+ ).render(request)
-+
-+ d = self.inheritedACEsforChildren(request)
-+ d.addCallback(findChildren)
-+ return d
-+
+-
#
# Attach method handlers to DAVFile
#
Copied: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch)
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_acl.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,69 @@
+Index: twisted/web2/dav/test/test_acl.py
+===================================================================
+--- twisted/web2/dav/test/test_acl.py (revision 18545)
++++ twisted/web2/dav/test/test_acl.py (working copy)
+@@ -30,6 +30,7 @@
+ from twisted.web2.auth import basic
+ from twisted.web2.stream import MemoryStream
+ from twisted.web2.dav import davxml
++from twisted.web2.dav.resource import DAVPrincipalCollectionResource
+ from twisted.web2.dav.util import davXMLFromStream
+ from twisted.web2.dav.auth import TwistedPasswordProperty, IPrincipal, DavRealm, TwistedPropertyChecker, AuthenticationWrapper
+
+@@ -38,6 +39,11 @@
+ from twisted.web2.dav.test.util import Site, serialize
+ from twisted.web2.dav.test.test_resource import TestResource, TestDAVPrincipalResource
+
++class TestPrincipalsCollection(DAVPrincipalCollectionResource, TestResource):
++ def __init__(self, url, children):
++ DAVPrincipalCollectionResource.__init__(self, url)
++ TestResource.__init__(self, url, children, principalCollections=(self,))
++
+ class ACL(twisted.web2.dav.test.util.TestCase):
+ """
+ RFC 3744 (WebDAV ACL) tests.
+@@ -46,8 +52,14 @@
+ if not hasattr(self, "docroot"):
+ self.docroot = self.mktemp()
+ os.mkdir(self.docroot)
+- rootresource = self.resource_class(self.docroot)
+
++ userResource = TestDAVPrincipalResource("/principals/user01")
++ userResource.writeDeadProperty(TwistedPasswordProperty("user01"))
++
++ principalCollection = TestPrincipalsCollection("/principals/", children={"user01": userResource})
++
++ rootResource = self.resource_class(self.docroot, principalCollections=(principalCollection,))
++
+ portal = Portal(DavRealm())
+ portal.registerChecker(TwistedPropertyChecker())
+
+@@ -56,26 +68,14 @@
+ loginInterfaces = (IPrincipal,)
+
+ self.site = Site(AuthenticationWrapper(
+- rootresource,
++ rootResource,
+ portal,
+ credentialFactories,
+ loginInterfaces
+ ))
+
+- rootresource.setAccessControlList(self.grant(davxml.All()))
++ rootResource.setAccessControlList(self.grant(davxml.All()))
+
+- userresource = TestDAVPrincipalResource("/principals/user01")
+- userresource.writeDeadProperty(TwistedPasswordProperty.fromString("user01"))
+-
+- rootresource.putChild(
+- "principals",
+- TestResource("/principals", {"user01": userresource})
+- )
+-
+- rootresource.writeDeadProperty(
+- davxml.PrincipalCollectionSet(davxml.HRef("/principals/"))
+- )
+-
+ for name, acl in (
+ ("none" , self.grant()),
+ ("read" , self.grant(davxml.Read())),
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_resource.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -2,7 +2,23 @@
===================================================================
--- twisted/web2/dav/test/test_resource.py (revision 18545)
+++ twisted/web2/dav/test/test_resource.py (working copy)
-@@ -282,7 +282,8 @@
+@@ -192,13 +192,10 @@
+ class AccessTests(TestCase):
+ def setUp(self):
+ gooduser = TestDAVPrincipalResource('/users/gooduser')
++ gooduser.writeDeadProperty(TwistedPasswordProperty('goodpass'))
+
+- gooduser.writeDeadProperty(
+- TwistedPasswordProperty.fromString('goodpass'))
+-
+ baduser = TestDAVPrincipalResource('/users/baduser')
+- baduser.writeDeadProperty(
+- TwistedPasswordProperty.fromString('badpass'))
++ baduser.writeDeadProperty(TwistedPasswordProperty('badpass'))
+
+ protected = TestResource('/protected')
+ protected.setAccessControlList(davxml.ACL(
+@@ -282,7 +279,8 @@
# Has auth; should allow
request = SimpleRequest(site, "GET", "/")
@@ -12,7 +28,22 @@
d = request.locateResource('/')
d.addCallback(_checkPrivileges)
d.addCallback(expectOK)
-@@ -380,8 +381,8 @@
+@@ -348,12 +346,12 @@
+ davxml.Grant(davxml.Privilege(davxml.All())),
+ davxml.Protected()))
+
+- def __init__(self, uri=None, children=None):
++ def __init__(self, uri=None, children=None, principalCollections=()):
+ """
+ @param uri: A string respresenting the URI of the given resource
+ @param children: a dictionary of names to Resources
+ """
+-
++ DAVResource.__init__(self, principalCollections=principalCollections)
+ self.children = children
+ self.uri = uri
+
+@@ -380,8 +378,8 @@
return succeed(davPrivilegeSet)
def currentPrincipal(self, request):
@@ -23,3 +54,32 @@
else:
return davxml.Principal(davxml.Unauthenticated())
+@@ -400,17 +398,23 @@
+ def accessControlList(self, request, **kwargs):
+ return succeed(self.acl)
+
++ def principalForUser(self, user):
++ return self.children[user]
+
+ class AuthAllResource (TestResource):
+- """Give Authenticated principals all privileges deny everything else
+ """
++ Give Authenticated principals all privileges and deny everyone else.
++ """
+ acl = davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(davxml.All())),
+- davxml.Protected()))
+-
++ davxml.Protected()
++ )
++ )
+
+ class TestDAVPrincipalResource(DAVPrincipalResource, TestResource):
+- """Get deadProperties from TestResource
+- """
++ # Get dead properties from TestResource
++
++ def principalURL(self):
++ return self.uri
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch 2006-12-01 19:18:19 UTC (rev 637)
@@ -107,7 +107,7 @@
#
# Parse the URL
-@@ -406,18 +418,70 @@
+@@ -406,19 +418,71 @@
"URL is not on this site (%s://%s/): %s" % (scheme, self.headers.getHeader("host"), url)
))
@@ -124,15 +124,18 @@
def notFound(f):
f.trap(http.HTTPError)
+- if f.response.code != responsecode.NOT_FOUND:
+- raise f
+ if f.value.response.code != responsecode.NOT_FOUND:
+ return f
-+ return None
-+
+ return None
+
+- return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
+ d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
+ d.addCallback(self._rememberResource, path)
+ d.addErrback(notFound)
+ return d
-+
+
+ def locateChildResource(self, parent, child_name):
+ """
+ Looks up the child resource with the given name given the parent
@@ -169,16 +172,15 @@
+
+ def notFound(f):
+ f.trap(http.HTTPError)
- if f.response.code != responsecode.NOT_FOUND:
-- raise f
++ if f.value.response.code != responsecode.NOT_FOUND:
+ return f
- return None
-
-- return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
++ return None
++
+ d = defer.maybeDeferred(self._getChild, None, parent, [segment], updatepaths=False)
+ d.addCallback(self._rememberResource, url)
+ d.addErrback(notFound)
+ return d
-
++
def _processingFailed(self, reason):
if reason.check(http.HTTPError) is not None:
+ # If the exception was an HTTPError, leave it alone
Modified: CalendarServer/trunk/run
===================================================================
--- CalendarServer/trunk/run 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/run 2006-12-01 19:18:19 UTC (rev 637)
@@ -161,6 +161,12 @@
fi;
if ! "${setup_only}"; then
+ if [ ! -f "${config}" ]; then
+ echo "Missing config file: ${config}";
+ echo "You might want to copy conf/caldavd-test.plist to conf/caldavd-dev.plist and start from there.";
+ exit 1;
+ fi;
+
cd "${wd}";
exec "${python}" "${caldav}/bin/caldavd" ${daemonize} \
-f "${config}" \
Copied: CalendarServer/trunk/support/CalendarServer.tmproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.tmproj)
===================================================================
--- CalendarServer/trunk/support/CalendarServer.tmproj (rev 0)
+++ CalendarServer/trunk/support/CalendarServer.tmproj 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>currentDocument</key>
+ <string>../../Twisted/twisted/web2/dav/resource.py</string>
+ <key>documents</key>
+ <array>
+ <dict>
+ <key>expanded</key>
+ <true/>
+ <key>name</key>
+ <string>twistedcaldav</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../twistedcaldav</string>
+ </dict>
+ <dict>
+ <key>expanded</key>
+ <true/>
+ <key>name</key>
+ <string>web2</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../../Twisted/twisted/web2</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>twisted</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../../Twisted/twisted</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PyOpenDirectory</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../../PyOpenDirectory</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>PyKerberos</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../../PyKerberos</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>conf</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../conf</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>bin</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../bin</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>doc</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*)$</string>
+ <key>sourceDirectory</key>
+ <string>../doc</string>
+ </dict>
+ <dict>
+ <key>name</key>
+ <string>support</string>
+ <key>regexFolderFilter</key>
+ <string>!.*/(\.[^/]*|.*\.xcodeproj)$</string>
+ <key>sourceDirectory</key>
+ <string></string>
+ </dict>
+ <dict>
+ <key>children</key>
+ <array>
+ <dict>
+ <key>filename</key>
+ <string>../run</string>
+ <key>lastUsed</key>
+ <date>2006-11-28T19:36:50Z</date>
+ </dict>
+ <dict>
+ <key>filename</key>
+ <string>../test</string>
+ <key>lastUsed</key>
+ <date>2006-11-27T23:16:03Z</date>
+ </dict>
+ <dict>
+ <key>filename</key>
+ <string>../testcaldav</string>
+ <key>lastUsed</key>
+ <date>2006-11-27T23:16:01Z</date>
+ </dict>
+ <dict>
+ <key>filename</key>
+ <string>../setup.py</string>
+ <key>lastUsed</key>
+ <date>2006-11-27T23:18:10Z</date>
+ </dict>
+ <dict>
+ <key>filename</key>
+ <string>../LICENSE</string>
+ <key>lastUsed</key>
+ <date>2006-11-27T23:18:09Z</date>
+ </dict>
+ <dict>
+ <key>filename</key>
+ <string>../README</string>
+ </dict>
+ </array>
+ <key>name</key>
+ <string>topfiles</string>
+ </dict>
+ </array>
+ <key>fileHierarchyDrawerWidth</key>
+ <integer>325</integer>
+ <key>metaData</key>
+ <dict>
+ <key>../../Twisted/twisted/web2/dav/resource.py</key>
+ <dict>
+ <key>caret</key>
+ <dict>
+ <key>column</key>
+ <integer>56</integer>
+ <key>line</key>
+ <integer>1265</integer>
+ </dict>
+ <key>firstVisibleColumn</key>
+ <integer>0</integer>
+ <key>firstVisibleLine</key>
+ <integer>1246</integer>
+ </dict>
+ <key>../twistedcaldav/resource.py</key>
+ <dict>
+ <key>caret</key>
+ <dict>
+ <key>column</key>
+ <integer>80</integer>
+ <key>line</key>
+ <integer>261</integer>
+ </dict>
+ <key>columnSelection</key>
+ <false/>
+ <key>firstVisibleColumn</key>
+ <integer>0</integer>
+ <key>firstVisibleLine</key>
+ <integer>231</integer>
+ <key>selectFrom</key>
+ <dict>
+ <key>column</key>
+ <integer>58</integer>
+ <key>line</key>
+ <integer>261</integer>
+ </dict>
+ <key>selectTo</key>
+ <dict>
+ <key>column</key>
+ <integer>80</integer>
+ <key>line</key>
+ <integer>261</integer>
+ </dict>
+ </dict>
+ </dict>
+ <key>openDocuments</key>
+ <array>
+ <string>../../Twisted/twisted/web2/dav/resource.py</string>
+ <string>../twistedcaldav/resource.py</string>
+ </array>
+ <key>showFileHierarchyDrawer</key>
+ <true/>
+ <key>windowFrame</key>
+ <string>{{705, 64}, {1019, 1050}}</string>
+</dict>
+</plist>
Copied: CalendarServer/trunk/support/CalendarServer.xcodeproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj)
Property changes on: CalendarServer/trunk/support/CalendarServer.xcodeproj
___________________________________________________________________
Name: svn:ignore
+ *.mode*
*.pbxuser
Deleted: CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj/project.pbxproj 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,329 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 42;
- objects = {
-
-/* Begin PBXFileReference section */
- 35069C170922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
- 35069C190922B96300389D48 /* caldavxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = caldavxml.py; sourceTree = "<group>"; };
- 35069C1B0922B96300389D48 /* index.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = index.py; sourceTree = "<group>"; };
- 35069C1E0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
- 35069C200922B96300389D48 /* mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcalendar.py; sourceTree = "<group>"; };
- 35069C220922B96300389D48 /* mkcol.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcol.py; sourceTree = "<group>"; };
- 35069C240922B96300389D48 /* put.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put.py; sourceTree = "<group>"; };
- 35069C260922B96300389D48 /* report_calquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_calquery.py; sourceTree = "<group>"; };
- 35069C2A0922B96300389D48 /* static.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = static.py; sourceTree = "<group>"; };
- 35069C2D0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
- 35069C790922B96300389D48 /* test_calendarquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_calendarquery.py; sourceTree = "<group>"; };
- 35069C7B0922B96300389D48 /* test_collectioncontents.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_collectioncontents.py; sourceTree = "<group>"; };
- 35069C7D0922B96300389D48 /* test_mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_mkcalendar.py; sourceTree = "<group>"; };
- 35069C7F0922B96300389D48 /* test_options.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_options.py; sourceTree = "<group>"; };
- 35069C820922B96300389D48 /* util.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = util.py; sourceTree = "<group>"; };
- 35069C840922B96300389D48 /* ical.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = ical.py; sourceTree = "<group>"; };
- 35069C870922BA1600389D48 /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; path = data; sourceTree = "<group>"; };
- 35069DDE0922BCCE00389D48 /* twisted */ = {isa = PBXFileReference; lastKnownFileType = folder; path = twisted; sourceTree = "<group>"; };
- 3506A2EE0922BD0C00389D48 /* web2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = web2; path = twisted/web2; sourceTree = "<group>"; };
- 3506A2F10922BD2700389D48 /* dav */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dav; path = twisted/web2/dav; sourceTree = "<group>"; };
- 3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = wrapper.xcdatamodel; path = CalendarIndex.xcdatamodel; sourceTree = "<group>"; };
- 3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-desruisseaux-caldav-sched.txt"; sourceTree = "<group>"; };
- 3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-dusseault-caldav.txt"; sourceTree = "<group>"; };
- 350781A2096DF5A4004A4366 /* dateops.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = dateops.py; sourceTree = "<group>"; };
- 3508925D0ABA0AC100F9995A /* caldavd-dev.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "caldavd-dev.plist"; sourceTree = "<group>"; };
- 3508925E0ABA0AC100F9995A /* caldavd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = caldavd.plist; sourceTree = "<group>"; };
- 3508925F0ABA0AC100F9995A /* launchd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = launchd.plist; sourceTree = "<group>"; };
- 350892600ABA0AC100F9995A /* repository-dev.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-dev.xml"; sourceTree = "<group>"; };
- 350892610ABA0AC100F9995A /* repository-static.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-static.xml"; sourceTree = "<group>"; };
- 350892620ABA0AC100F9995A /* repository.dtd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.dtd; sourceTree = "<group>"; };
- 3524532E098982D900B9179C /* test_DAV.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_DAV.py; sourceTree = "<group>"; };
- 353696D1092BB6500075CE69 /* test_icalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_icalendar.py; sourceTree = "<group>"; };
- 353A557C099153D900A08D28 /* setup.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = setup.py; path = ../setup.py; sourceTree = "<group>"; };
- 353A63BF0994448C00A08D28 /* caldavd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; path = caldavd; sourceTree = "<group>"; };
- 356E29FC0AC301C900F46D07 /* authkerb.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = authkerb.py; sourceTree = "<group>"; };
- 356E29FE0AC301C900F46D07 /* customxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = customxml.py; sourceTree = "<group>"; };
- 356E29FF0AC301C900F46D07 /* db.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = db.py; sourceTree = "<group>"; };
- 356E2A000AC301C900F46D07 /* directory.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = directory.py; sourceTree = "<group>"; };
- 356E2A010AC301C900F46D07 /* itip.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = itip.py; sourceTree = "<group>"; };
- 356E2A020AC301C900F46D07 /* principalindex.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = principalindex.py; sourceTree = "<group>"; };
- 356E2A440AC3057F00F46D07 /* post.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = post.py; sourceTree = "<group>"; };
- 356E2A450AC3057F00F46D07 /* schedule_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule_common.py; sourceTree = "<group>"; };
- 356E2A480AC3490100F46D07 /* caldavd.8 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = caldavd.8; sourceTree = "<group>"; };
- 356E2A490AC3490100F46D07 /* twisted.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twisted.cfg; sourceTree = "<group>"; };
- 356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twistedcaldav.cfg; sourceTree = "<group>"; };
- 356E2A510AC3495700F46D07 /* Directory Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "Directory Repository.graffle"; sourceTree = "<group>"; };
- 356E2A520AC3495700F46D07 /* XML Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "XML Repository.graffle"; sourceTree = "<group>"; };
- 359CD65C0946136A002E3A15 /* test_xml.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_xml.py; sourceTree = "<group>"; };
- 35A15ED40985C14800D404FF /* run */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = run; path = ../run; sourceTree = "<group>"; };
- 35A15ED60985C14800D404FF /* test */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = test; path = ../test; sourceTree = "<group>"; };
- 35B48642095CA1D000AB3411 /* rfc2518.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc2518.txt; sourceTree = "<group>"; };
- 35B48643095CA1D000AB3411 /* rfc3253.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3253.txt; sourceTree = "<group>"; };
- 35B48644095CA1D000AB3411 /* rfc3744.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3744.txt; sourceTree = "<group>"; };
- 35B71A24097C3A3000E65B22 /* instance.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = instance.py; sourceTree = "<group>"; };
- 35B71A25097C3B2C00E65B22 /* copymove.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = copymove.py; sourceTree = "<group>"; };
- 35B71A26097C3B2C00E65B22 /* delete.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = delete.py; sourceTree = "<group>"; };
- 35B71A27097C3B2C00E65B22 /* report_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_multiget.py; sourceTree = "<group>"; };
- 35B8AA660A0BFE60005547E5 /* http.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = http.py; sourceTree = "<group>"; };
- 35B8AA670A0BFE60005547E5 /* repository.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = repository.py; sourceTree = "<group>"; };
- 35CF70B50A0FF59100993B2A /* repository.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.xml; sourceTree = "<group>"; };
- 35CF70B60A0FF59100993B2A /* server.pem */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = server.pem; sourceTree = "<group>"; };
- 35E2ACFF09BF6D3400BC8CB9 /* logging.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = logging.py; sourceTree = "<group>"; };
- 35E2AF1409C2491600BC8CB9 /* put_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put_common.py; sourceTree = "<group>"; };
- 35E2AF1509C2491600BC8CB9 /* schedule.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule.py; sourceTree = "<group>"; };
- 35E2B38109C7C2DF00BC8CB9 /* lib-patches */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "lib-patches"; sourceTree = "<group>"; };
- 35E8059B0981C33F000981A6 /* test_props.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_props.py; sourceTree = "<group>"; };
- 35F36BAF09B3E8AD00A3D736 /* resource.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = resource.py; sourceTree = "<group>"; };
- 35F36C0E09B4FA6A00A3D736 /* icaldav.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = icaldav.py; sourceTree = "<group>"; };
- 35FC85A009ABEC0600586387 /* test_freebusyquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_freebusyquery.py; sourceTree = "<group>"; };
- 35FC85A109ABEC0600586387 /* test_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_multiget.py; sourceTree = "<group>"; };
- 35FC85A209ABEC9700586387 /* report_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_common.py; sourceTree = "<group>"; };
- 35FC85A309ABEC9700586387 /* report_freebusy.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_freebusy.py; sourceTree = "<group>"; };
- 35FC85A809ABED2B00586387 /* version.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; name = version.py; path = ../twistedcaldav/version.py; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXGroup section */
- 35069C090922B94100389D48 = {
- isa = PBXGroup;
- children = (
- 35069CD60922BA8600389D48 /* CalendarServer */,
- 35069CF50922BACA00389D48 /* Twisted */,
- 3506A86B0922BE1500389D48 /* Documentation */,
- 353A63B50994444700A08D28 /* Scripts */,
- 35A15ED30985C12C00D404FF /* Support */,
- 350892570ABA0A8C00F9995A /* Configuration */,
- );
- sourceTree = "<group>";
- };
- 35069C160922B96300389D48 /* twistedcaldav */ = {
- isa = PBXGroup;
- children = (
- 35F36C0E09B4FA6A00A3D736 /* icaldav.py */,
- 35F36BAF09B3E8AD00A3D736 /* resource.py */,
- 35069C2A0922B96300389D48 /* static.py */,
- 356E2A000AC301C900F46D07 /* directory.py */,
- 35B8AA660A0BFE60005547E5 /* http.py */,
- 35069C190922B96300389D48 /* caldavxml.py */,
- 356E29FE0AC301C900F46D07 /* customxml.py */,
- 356E29FC0AC301C900F46D07 /* authkerb.py */,
- 35069C840922B96300389D48 /* ical.py */,
- 356E2A010AC301C900F46D07 /* itip.py */,
- 35B71A24097C3A3000E65B22 /* instance.py */,
- 350781A2096DF5A4004A4366 /* dateops.py */,
- 35069C1B0922B96300389D48 /* index.py */,
- 356E2A020AC301C900F46D07 /* principalindex.py */,
- 356E29FF0AC301C900F46D07 /* db.py */,
- 35B8AA670A0BFE60005547E5 /* repository.py */,
- 35E2ACFF09BF6D3400BC8CB9 /* logging.py */,
- 35069C170922B96300389D48 /* __init__.py */,
- 35069C1D0922B96300389D48 /* method */,
- 35069C2C0922B96300389D48 /* test */,
- );
- path = twistedcaldav;
- sourceTree = "<group>";
- };
- 35069C1D0922B96300389D48 /* method */ = {
- isa = PBXGroup;
- children = (
- 356E2A450AC3057F00F46D07 /* schedule_common.py */,
- 35B71A25097C3B2C00E65B22 /* copymove.py */,
- 35B71A26097C3B2C00E65B22 /* delete.py */,
- 35069C200922B96300389D48 /* mkcalendar.py */,
- 35069C220922B96300389D48 /* mkcol.py */,
- 356E2A440AC3057F00F46D07 /* post.py */,
- 35069C240922B96300389D48 /* put.py */,
- 35E2AF1409C2491600BC8CB9 /* put_common.py */,
- 35069C260922B96300389D48 /* report_calquery.py */,
- 35FC85A209ABEC9700586387 /* report_common.py */,
- 35FC85A309ABEC9700586387 /* report_freebusy.py */,
- 35B71A27097C3B2C00E65B22 /* report_multiget.py */,
- 35E2AF1509C2491600BC8CB9 /* schedule.py */,
- 35069C1E0922B96300389D48 /* __init__.py */,
- );
- path = method;
- sourceTree = "<group>";
- };
- 35069C2C0922B96300389D48 /* test */ = {
- isa = PBXGroup;
- children = (
- 3524532E098982D900B9179C /* test_DAV.py */,
- 35069C790922B96300389D48 /* test_calendarquery.py */,
- 35069C7B0922B96300389D48 /* test_collectioncontents.py */,
- 35FC85A009ABEC0600586387 /* test_freebusyquery.py */,
- 353696D1092BB6500075CE69 /* test_icalendar.py */,
- 35069C7D0922B96300389D48 /* test_mkcalendar.py */,
- 35FC85A109ABEC0600586387 /* test_multiget.py */,
- 35069C7F0922B96300389D48 /* test_options.py */,
- 35E8059B0981C33F000981A6 /* test_props.py */,
- 359CD65C0946136A002E3A15 /* test_xml.py */,
- 35069C820922B96300389D48 /* util.py */,
- 35069C2D0922B96300389D48 /* __init__.py */,
- 35069C870922BA1600389D48 /* data */,
- );
- path = test;
- sourceTree = "<group>";
- };
- 35069CD60922BA8600389D48 /* CalendarServer */ = {
- isa = PBXGroup;
- children = (
- 35069C160922B96300389D48 /* twistedcaldav */,
- 35E2B38109C7C2DF00BC8CB9 /* lib-patches */,
- );
- name = CalendarServer;
- path = ..;
- sourceTree = "<group>";
- };
- 35069CF50922BACA00389D48 /* Twisted */ = {
- isa = PBXGroup;
- children = (
- 3506A2F10922BD2700389D48 /* dav */,
- 3506A2EE0922BD0C00389D48 /* web2 */,
- 35069DDE0922BCCE00389D48 /* twisted */,
- );
- name = Twisted;
- path = ../../Twisted;
- sourceTree = "<group>";
- };
- 3506A86B0922BE1500389D48 /* Documentation */ = {
- isa = PBXGroup;
- children = (
- 3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */,
- 356E2A500AC3492D00F46D07 /* Repository */,
- 3506A86D0922BE1500389D48 /* RFC */,
- 356E2A480AC3490100F46D07 /* caldavd.8 */,
- 356E2A490AC3490100F46D07 /* twisted.cfg */,
- 356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */,
- );
- name = Documentation;
- path = ../doc;
- sourceTree = "<group>";
- };
- 3506A86D0922BE1500389D48 /* RFC */ = {
- isa = PBXGroup;
- children = (
- 35B48642095CA1D000AB3411 /* rfc2518.txt */,
- 35B48643095CA1D000AB3411 /* rfc3253.txt */,
- 35B48644095CA1D000AB3411 /* rfc3744.txt */,
- 3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */,
- 3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */,
- );
- path = RFC;
- sourceTree = "<group>";
- };
- 350892570ABA0A8C00F9995A /* Configuration */ = {
- isa = PBXGroup;
- children = (
- 350892800ABA0B8900F9995A /* Development */,
- 3508927F0ABA0B8300F9995A /* Open Directory */,
- 350892810ABA0BB700F9995A /* Static XML */,
- 350892620ABA0AC100F9995A /* repository.dtd */,
- 35CF70B60A0FF59100993B2A /* server.pem */,
- 3508925F0ABA0AC100F9995A /* launchd.plist */,
- );
- name = Configuration;
- path = ../conf;
- sourceTree = "<group>";
- };
- 3508927F0ABA0B8300F9995A /* Open Directory */ = {
- isa = PBXGroup;
- children = (
- 3508925E0ABA0AC100F9995A /* caldavd.plist */,
- 35CF70B50A0FF59100993B2A /* repository.xml */,
- );
- name = "Open Directory";
- sourceTree = "<group>";
- };
- 350892800ABA0B8900F9995A /* Development */ = {
- isa = PBXGroup;
- children = (
- 3508925D0ABA0AC100F9995A /* caldavd-dev.plist */,
- 350892600ABA0AC100F9995A /* repository-dev.xml */,
- );
- name = Development;
- sourceTree = "<group>";
- };
- 350892810ABA0BB700F9995A /* Static XML */ = {
- isa = PBXGroup;
- children = (
- 350892610ABA0AC100F9995A /* repository-static.xml */,
- );
- name = "Static XML";
- sourceTree = "<group>";
- };
- 353A63B50994444700A08D28 /* Scripts */ = {
- isa = PBXGroup;
- children = (
- 353A63BF0994448C00A08D28 /* caldavd */,
- );
- name = Scripts;
- path = ../bin;
- sourceTree = "<group>";
- };
- 356E2A500AC3492D00F46D07 /* Repository */ = {
- isa = PBXGroup;
- children = (
- 356E2A510AC3495700F46D07 /* Directory Repository.graffle */,
- 356E2A520AC3495700F46D07 /* XML Repository.graffle */,
- );
- path = Repository;
- sourceTree = "<group>";
- };
- 35A15ED30985C12C00D404FF /* Support */ = {
- isa = PBXGroup;
- children = (
- 35A15ED40985C14800D404FF /* run */,
- 35A15ED60985C14800D404FF /* test */,
- 353A557C099153D900A08D28 /* setup.py */,
- 35FC85A809ABED2B00586387 /* version.py */,
- );
- name = Support;
- sourceTree = "<group>";
- };
-/* End PBXGroup section */
-
-/* Begin PBXProject section */
- 35069C0B0922B94100389D48 /* Project object */ = {
- isa = PBXProject;
- buildConfigurationList = 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */;
- compatibilityVersion = "Xcode 2.4";
- hasScannedForEncodings = 0;
- mainGroup = 35069C090922B94100389D48;
- projectDirPath = "";
- projectRoot = "";
- shouldCheckCompatibility = 1;
- targets = (
- );
- };
-/* End PBXProject section */
-
-/* Begin XCBuildConfiguration section */
- 35069C0D0922B94100389D48 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- COPY_PHASE_STRIP = NO;
- };
- name = Debug;
- };
- 35069C0E0922B94100389D48 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- COPY_PHASE_STRIP = YES;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 35069C0D0922B94100389D48 /* Debug */,
- 35069C0E0922B94100389D48 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 35069C0B0922B94100389D48 /* Project object */;
-}
Copied: CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/support/CalendarServer.xcodeproj/project.pbxproj)
===================================================================
--- CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj (rev 0)
+++ CalendarServer/trunk/support/CalendarServer.xcodeproj/project.pbxproj 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,329 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 42;
+ objects = {
+
+/* Begin PBXFileReference section */
+ 35069C170922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+ 35069C190922B96300389D48 /* caldavxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = caldavxml.py; sourceTree = "<group>"; };
+ 35069C1B0922B96300389D48 /* index.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = index.py; sourceTree = "<group>"; };
+ 35069C1E0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+ 35069C200922B96300389D48 /* mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcalendar.py; sourceTree = "<group>"; };
+ 35069C220922B96300389D48 /* mkcol.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = mkcol.py; sourceTree = "<group>"; };
+ 35069C240922B96300389D48 /* put.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put.py; sourceTree = "<group>"; };
+ 35069C260922B96300389D48 /* report_calquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_calquery.py; sourceTree = "<group>"; };
+ 35069C2A0922B96300389D48 /* static.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = static.py; sourceTree = "<group>"; };
+ 35069C2D0922B96300389D48 /* __init__.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = __init__.py; sourceTree = "<group>"; };
+ 35069C790922B96300389D48 /* test_calendarquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_calendarquery.py; sourceTree = "<group>"; };
+ 35069C7B0922B96300389D48 /* test_collectioncontents.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_collectioncontents.py; sourceTree = "<group>"; };
+ 35069C7D0922B96300389D48 /* test_mkcalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_mkcalendar.py; sourceTree = "<group>"; };
+ 35069C7F0922B96300389D48 /* test_options.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_options.py; sourceTree = "<group>"; };
+ 35069C820922B96300389D48 /* util.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = util.py; sourceTree = "<group>"; };
+ 35069C840922B96300389D48 /* ical.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = ical.py; sourceTree = "<group>"; };
+ 35069C870922BA1600389D48 /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; path = data; sourceTree = "<group>"; };
+ 35069DDE0922BCCE00389D48 /* twisted */ = {isa = PBXFileReference; lastKnownFileType = folder; path = twisted; sourceTree = "<group>"; };
+ 3506A2EE0922BD0C00389D48 /* web2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = web2; path = twisted/web2; sourceTree = "<group>"; };
+ 3506A2F10922BD2700389D48 /* dav */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dav; path = twisted/web2/dav; sourceTree = "<group>"; };
+ 3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = wrapper.xcdatamodel; path = CalendarIndex.xcdatamodel; sourceTree = "<group>"; };
+ 3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-desruisseaux-caldav-sched.txt"; sourceTree = "<group>"; };
+ 3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "draft-dusseault-caldav.txt"; sourceTree = "<group>"; };
+ 350781A2096DF5A4004A4366 /* dateops.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = dateops.py; sourceTree = "<group>"; };
+ 3508925D0ABA0AC100F9995A /* caldavd-dev.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "caldavd-dev.plist"; sourceTree = "<group>"; };
+ 3508925E0ABA0AC100F9995A /* caldavd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = caldavd.plist; sourceTree = "<group>"; };
+ 3508925F0ABA0AC100F9995A /* launchd.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = launchd.plist; sourceTree = "<group>"; };
+ 350892600ABA0AC100F9995A /* repository-dev.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-dev.xml"; sourceTree = "<group>"; };
+ 350892610ABA0AC100F9995A /* repository-static.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "repository-static.xml"; sourceTree = "<group>"; };
+ 350892620ABA0AC100F9995A /* repository.dtd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.dtd; sourceTree = "<group>"; };
+ 3524532E098982D900B9179C /* test_DAV.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_DAV.py; sourceTree = "<group>"; };
+ 353696D1092BB6500075CE69 /* test_icalendar.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_icalendar.py; sourceTree = "<group>"; };
+ 353A557C099153D900A08D28 /* setup.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = setup.py; path = ../setup.py; sourceTree = "<group>"; };
+ 353A63BF0994448C00A08D28 /* caldavd */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; path = caldavd; sourceTree = "<group>"; };
+ 356E29FC0AC301C900F46D07 /* authkerb.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = authkerb.py; sourceTree = "<group>"; };
+ 356E29FE0AC301C900F46D07 /* customxml.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = customxml.py; sourceTree = "<group>"; };
+ 356E29FF0AC301C900F46D07 /* db.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = db.py; sourceTree = "<group>"; };
+ 356E2A000AC301C900F46D07 /* directory.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = directory.py; sourceTree = "<group>"; };
+ 356E2A010AC301C900F46D07 /* itip.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = itip.py; sourceTree = "<group>"; };
+ 356E2A020AC301C900F46D07 /* principalindex.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = principalindex.py; sourceTree = "<group>"; };
+ 356E2A440AC3057F00F46D07 /* post.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = post.py; sourceTree = "<group>"; };
+ 356E2A450AC3057F00F46D07 /* schedule_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule_common.py; sourceTree = "<group>"; };
+ 356E2A480AC3490100F46D07 /* caldavd.8 */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = caldavd.8; sourceTree = "<group>"; };
+ 356E2A490AC3490100F46D07 /* twisted.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twisted.cfg; sourceTree = "<group>"; };
+ 356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = twistedcaldav.cfg; sourceTree = "<group>"; };
+ 356E2A510AC3495700F46D07 /* Directory Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "Directory Repository.graffle"; sourceTree = "<group>"; };
+ 356E2A520AC3495700F46D07 /* XML Repository.graffle */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "XML Repository.graffle"; sourceTree = "<group>"; };
+ 359CD65C0946136A002E3A15 /* test_xml.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_xml.py; sourceTree = "<group>"; };
+ 35A15ED40985C14800D404FF /* run */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = run; path = ../run; sourceTree = "<group>"; };
+ 35A15ED60985C14800D404FF /* test */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.sh; name = test; path = ../test; sourceTree = "<group>"; };
+ 35B48642095CA1D000AB3411 /* rfc2518.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc2518.txt; sourceTree = "<group>"; };
+ 35B48643095CA1D000AB3411 /* rfc3253.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3253.txt; sourceTree = "<group>"; };
+ 35B48644095CA1D000AB3411 /* rfc3744.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = rfc3744.txt; sourceTree = "<group>"; };
+ 35B71A24097C3A3000E65B22 /* instance.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = instance.py; sourceTree = "<group>"; };
+ 35B71A25097C3B2C00E65B22 /* copymove.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = copymove.py; sourceTree = "<group>"; };
+ 35B71A26097C3B2C00E65B22 /* delete.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = delete.py; sourceTree = "<group>"; };
+ 35B71A27097C3B2C00E65B22 /* report_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_multiget.py; sourceTree = "<group>"; };
+ 35B8AA660A0BFE60005547E5 /* http.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = http.py; sourceTree = "<group>"; };
+ 35B8AA670A0BFE60005547E5 /* repository.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = repository.py; sourceTree = "<group>"; };
+ 35CF70B50A0FF59100993B2A /* repository.xml */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = repository.xml; sourceTree = "<group>"; };
+ 35CF70B60A0FF59100993B2A /* server.pem */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = server.pem; sourceTree = "<group>"; };
+ 35E2ACFF09BF6D3400BC8CB9 /* logging.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = logging.py; sourceTree = "<group>"; };
+ 35E2AF1409C2491600BC8CB9 /* put_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = put_common.py; sourceTree = "<group>"; };
+ 35E2AF1509C2491600BC8CB9 /* schedule.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = schedule.py; sourceTree = "<group>"; };
+ 35E2B38109C7C2DF00BC8CB9 /* lib-patches */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "lib-patches"; sourceTree = "<group>"; };
+ 35E8059B0981C33F000981A6 /* test_props.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = test_props.py; sourceTree = "<group>"; };
+ 35F36BAF09B3E8AD00A3D736 /* resource.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = resource.py; sourceTree = "<group>"; };
+ 35F36C0E09B4FA6A00A3D736 /* icaldav.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = icaldav.py; sourceTree = "<group>"; };
+ 35FC85A009ABEC0600586387 /* test_freebusyquery.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_freebusyquery.py; sourceTree = "<group>"; };
+ 35FC85A109ABEC0600586387 /* test_multiget.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = test_multiget.py; sourceTree = "<group>"; };
+ 35FC85A209ABEC9700586387 /* report_common.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_common.py; sourceTree = "<group>"; };
+ 35FC85A309ABEC9700586387 /* report_freebusy.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; path = report_freebusy.py; sourceTree = "<group>"; };
+ 35FC85A809ABED2B00586387 /* version.py */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.script.python; name = version.py; path = ../twistedcaldav/version.py; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXGroup section */
+ 35069C090922B94100389D48 = {
+ isa = PBXGroup;
+ children = (
+ 35069CD60922BA8600389D48 /* CalendarServer */,
+ 35069CF50922BACA00389D48 /* Twisted */,
+ 3506A86B0922BE1500389D48 /* Documentation */,
+ 353A63B50994444700A08D28 /* Scripts */,
+ 35A15ED30985C12C00D404FF /* Support */,
+ 350892570ABA0A8C00F9995A /* Configuration */,
+ );
+ sourceTree = "<group>";
+ };
+ 35069C160922B96300389D48 /* twistedcaldav */ = {
+ isa = PBXGroup;
+ children = (
+ 35F36C0E09B4FA6A00A3D736 /* icaldav.py */,
+ 35F36BAF09B3E8AD00A3D736 /* resource.py */,
+ 35069C2A0922B96300389D48 /* static.py */,
+ 356E2A000AC301C900F46D07 /* directory.py */,
+ 35B8AA660A0BFE60005547E5 /* http.py */,
+ 35069C190922B96300389D48 /* caldavxml.py */,
+ 356E29FE0AC301C900F46D07 /* customxml.py */,
+ 356E29FC0AC301C900F46D07 /* authkerb.py */,
+ 35069C840922B96300389D48 /* ical.py */,
+ 356E2A010AC301C900F46D07 /* itip.py */,
+ 35B71A24097C3A3000E65B22 /* instance.py */,
+ 350781A2096DF5A4004A4366 /* dateops.py */,
+ 35069C1B0922B96300389D48 /* index.py */,
+ 356E2A020AC301C900F46D07 /* principalindex.py */,
+ 356E29FF0AC301C900F46D07 /* db.py */,
+ 35B8AA670A0BFE60005547E5 /* repository.py */,
+ 35E2ACFF09BF6D3400BC8CB9 /* logging.py */,
+ 35069C170922B96300389D48 /* __init__.py */,
+ 35069C1D0922B96300389D48 /* method */,
+ 35069C2C0922B96300389D48 /* test */,
+ );
+ path = twistedcaldav;
+ sourceTree = "<group>";
+ };
+ 35069C1D0922B96300389D48 /* method */ = {
+ isa = PBXGroup;
+ children = (
+ 356E2A450AC3057F00F46D07 /* schedule_common.py */,
+ 35B71A25097C3B2C00E65B22 /* copymove.py */,
+ 35B71A26097C3B2C00E65B22 /* delete.py */,
+ 35069C200922B96300389D48 /* mkcalendar.py */,
+ 35069C220922B96300389D48 /* mkcol.py */,
+ 356E2A440AC3057F00F46D07 /* post.py */,
+ 35069C240922B96300389D48 /* put.py */,
+ 35E2AF1409C2491600BC8CB9 /* put_common.py */,
+ 35069C260922B96300389D48 /* report_calquery.py */,
+ 35FC85A209ABEC9700586387 /* report_common.py */,
+ 35FC85A309ABEC9700586387 /* report_freebusy.py */,
+ 35B71A27097C3B2C00E65B22 /* report_multiget.py */,
+ 35E2AF1509C2491600BC8CB9 /* schedule.py */,
+ 35069C1E0922B96300389D48 /* __init__.py */,
+ );
+ path = method;
+ sourceTree = "<group>";
+ };
+ 35069C2C0922B96300389D48 /* test */ = {
+ isa = PBXGroup;
+ children = (
+ 3524532E098982D900B9179C /* test_DAV.py */,
+ 35069C790922B96300389D48 /* test_calendarquery.py */,
+ 35069C7B0922B96300389D48 /* test_collectioncontents.py */,
+ 35FC85A009ABEC0600586387 /* test_freebusyquery.py */,
+ 353696D1092BB6500075CE69 /* test_icalendar.py */,
+ 35069C7D0922B96300389D48 /* test_mkcalendar.py */,
+ 35FC85A109ABEC0600586387 /* test_multiget.py */,
+ 35069C7F0922B96300389D48 /* test_options.py */,
+ 35E8059B0981C33F000981A6 /* test_props.py */,
+ 359CD65C0946136A002E3A15 /* test_xml.py */,
+ 35069C820922B96300389D48 /* util.py */,
+ 35069C2D0922B96300389D48 /* __init__.py */,
+ 35069C870922BA1600389D48 /* data */,
+ );
+ path = test;
+ sourceTree = "<group>";
+ };
+ 35069CD60922BA8600389D48 /* CalendarServer */ = {
+ isa = PBXGroup;
+ children = (
+ 35069C160922B96300389D48 /* twistedcaldav */,
+ 35E2B38109C7C2DF00BC8CB9 /* lib-patches */,
+ );
+ name = CalendarServer;
+ path = ..;
+ sourceTree = "<group>";
+ };
+ 35069CF50922BACA00389D48 /* Twisted */ = {
+ isa = PBXGroup;
+ children = (
+ 3506A2F10922BD2700389D48 /* dav */,
+ 3506A2EE0922BD0C00389D48 /* web2 */,
+ 35069DDE0922BCCE00389D48 /* twisted */,
+ );
+ name = Twisted;
+ path = ../../Twisted;
+ sourceTree = "<group>";
+ };
+ 3506A86B0922BE1500389D48 /* Documentation */ = {
+ isa = PBXGroup;
+ children = (
+ 3506A86C0922BE1500389D48 /* CalendarIndex.xcdatamodel */,
+ 356E2A500AC3492D00F46D07 /* Repository */,
+ 3506A86D0922BE1500389D48 /* RFC */,
+ 356E2A480AC3490100F46D07 /* caldavd.8 */,
+ 356E2A490AC3490100F46D07 /* twisted.cfg */,
+ 356E2A4A0AC3490100F46D07 /* twistedcaldav.cfg */,
+ );
+ name = Documentation;
+ path = ../doc;
+ sourceTree = "<group>";
+ };
+ 3506A86D0922BE1500389D48 /* RFC */ = {
+ isa = PBXGroup;
+ children = (
+ 35B48642095CA1D000AB3411 /* rfc2518.txt */,
+ 35B48643095CA1D000AB3411 /* rfc3253.txt */,
+ 35B48644095CA1D000AB3411 /* rfc3744.txt */,
+ 3506A86F0922BE1500389D48 /* draft-dusseault-caldav.txt */,
+ 3506A86E0922BE1500389D48 /* draft-desruisseaux-caldav-sched.txt */,
+ );
+ path = RFC;
+ sourceTree = "<group>";
+ };
+ 350892570ABA0A8C00F9995A /* Configuration */ = {
+ isa = PBXGroup;
+ children = (
+ 350892800ABA0B8900F9995A /* Development */,
+ 3508927F0ABA0B8300F9995A /* Open Directory */,
+ 350892810ABA0BB700F9995A /* Static XML */,
+ 350892620ABA0AC100F9995A /* repository.dtd */,
+ 35CF70B60A0FF59100993B2A /* server.pem */,
+ 3508925F0ABA0AC100F9995A /* launchd.plist */,
+ );
+ name = Configuration;
+ path = ../conf;
+ sourceTree = "<group>";
+ };
+ 3508927F0ABA0B8300F9995A /* Open Directory */ = {
+ isa = PBXGroup;
+ children = (
+ 3508925E0ABA0AC100F9995A /* caldavd.plist */,
+ 35CF70B50A0FF59100993B2A /* repository.xml */,
+ );
+ name = "Open Directory";
+ sourceTree = "<group>";
+ };
+ 350892800ABA0B8900F9995A /* Development */ = {
+ isa = PBXGroup;
+ children = (
+ 3508925D0ABA0AC100F9995A /* caldavd-dev.plist */,
+ 350892600ABA0AC100F9995A /* repository-dev.xml */,
+ );
+ name = Development;
+ sourceTree = "<group>";
+ };
+ 350892810ABA0BB700F9995A /* Static XML */ = {
+ isa = PBXGroup;
+ children = (
+ 350892610ABA0AC100F9995A /* repository-static.xml */,
+ );
+ name = "Static XML";
+ sourceTree = "<group>";
+ };
+ 353A63B50994444700A08D28 /* Scripts */ = {
+ isa = PBXGroup;
+ children = (
+ 353A63BF0994448C00A08D28 /* caldavd */,
+ );
+ name = Scripts;
+ path = ../bin;
+ sourceTree = "<group>";
+ };
+ 356E2A500AC3492D00F46D07 /* Repository */ = {
+ isa = PBXGroup;
+ children = (
+ 356E2A510AC3495700F46D07 /* Directory Repository.graffle */,
+ 356E2A520AC3495700F46D07 /* XML Repository.graffle */,
+ );
+ path = Repository;
+ sourceTree = "<group>";
+ };
+ 35A15ED30985C12C00D404FF /* Support */ = {
+ isa = PBXGroup;
+ children = (
+ 35A15ED40985C14800D404FF /* run */,
+ 35A15ED60985C14800D404FF /* test */,
+ 353A557C099153D900A08D28 /* setup.py */,
+ 35FC85A809ABED2B00586387 /* version.py */,
+ );
+ name = Support;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXProject section */
+ 35069C0B0922B94100389D48 /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */;
+ compatibilityVersion = "Xcode 2.4";
+ hasScannedForEncodings = 0;
+ mainGroup = 35069C090922B94100389D48;
+ projectDirPath = "";
+ projectRoot = "";
+ shouldCheckCompatibility = 1;
+ targets = (
+ );
+ };
+/* End PBXProject section */
+
+/* Begin XCBuildConfiguration section */
+ 35069C0D0922B94100389D48 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ };
+ name = Debug;
+ };
+ 35069C0E0922B94100389D48 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COPY_PHASE_STRIP = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 35069C0C0922B94100389D48 /* Build configuration list for PBXProject "CalendarServer" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 35069C0D0922B94100389D48 /* Debug */,
+ 35069C0E0922B94100389D48 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 35069C0B0922B94100389D48 /* Project object */;
+}
Modified: CalendarServer/trunk/support/patchmaker.py
===================================================================
--- CalendarServer/trunk/support/patchmaker.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/patchmaker.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -30,7 +30,7 @@
cwd = os.getcwd()
libpatches = os.path.join(cwd, "lib-patches")
-svn = "/usr/local/subversion/bin/svn"
+svn = "/usr/bin/svn"
# Stuff we have to manually ignore because our ignore logic cannot cope
ignores = set((
Modified: CalendarServer/trunk/support/submit
===================================================================
--- CalendarServer/trunk/support/submit 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/support/submit 2006-12-01 19:18:19 UTC (rev 637)
@@ -19,7 +19,7 @@
##
##
-# Submit project to B&I (Apple Internal Build)
+# Submit project to B&I (Apple internal build)
##
set -e
@@ -34,6 +34,8 @@
# Command line
##
+build=false;
+
usage ()
{
program="$(basename "$0")";
@@ -41,23 +43,29 @@
if [ "${1-}" != "-" ]; then echo "$1"; echo; fi;
echo "Usage: ${program} release";
+ echo " ${program} -b";
+ echo "";
+ echo "Options:";
+ echo " -b Run buildit";
if [ "${1-}" == "-" ]; then return 0; fi;
exit 64;
}
-while getopts 'h' option; do
+while getopts 'hb' option; do
case "$option" in
'?') usage; ;;
'h') usage -; exit 0; ;;
+ 'b') build=true; ;;
esac;
done;
shift $((${OPTIND} - 1));
-if [ $# == 0 ]; then usage "No release specified"; fi;
+if ! "${build}"; then
+ if [ $# == 0 ]; then usage "No release specified"; fi;
+ release="$1"; shift;
+fi;
-release="$1"; shift;
-
if [ $# != 0 ]; then usage "Unrecognized arguments:" "$@"; fi;
project="CalendarServer";
@@ -123,8 +131,15 @@
echo "Preparing sources for ${project_version}...";
make -C "${wc}" prep;
-echo "";
-echo "Submitting sources for ${project_version}...";
-~rc/bin/submitproject "${wc}" "${release}";
+if "${build}"; then
+ echo "";
+ echo "Building ${project_version}...";
+ sudo ~rc/bin/buildit "${wc}" -arch ppc -arch i386;
+else
+ echo "";
+ echo "Submitting sources for ${project_version}...";
+ ~rc/bin/submitproject "${wc}" "${release}";
+fi;
+
rm -rf "${tmp}";
Modified: CalendarServer/trunk/test
===================================================================
--- CalendarServer/trunk/test 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/test 2006-12-01 19:18:19 UTC (rev 637)
@@ -29,7 +29,7 @@
if [ $# -gt 0 ]; then
test_modules="$@";
else
- test_modules="twistedcaldav.test";
+ test_modules="twistedcaldav";
fi;
cd "${wd}" && "${twisted}/bin/trial" ${test_modules};
Modified: CalendarServer/trunk/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/__init__.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/__init__.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -36,6 +36,7 @@
"instance",
"principalindex",
"resource",
+ "sql",
"static",
]
Modified: CalendarServer/trunk/twistedcaldav/directory/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/__init__.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/__init__.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -26,4 +26,6 @@
"directory",
"idirectory",
"resource",
+ "sqldb",
+ "xmlfile",
]
Copied: CalendarServer/trunk/twistedcaldav/directory/apache.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/apache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/apache.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/apache.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,198 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Apache UserFile/GroupFile compatible directory service implementation.
+"""
+
+__all__ = [
+ "BasicDirectoryService",
+ "DigestDirectoryService",
+]
+
+from crypt import crypt
+
+from twisted.python.filepath import FilePath
+from twisted.cred.credentials import UsernamePassword
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.directory import UnknownRecordTypeError
+
+class AbstractDirectoryService(DirectoryService):
+ """
+ Abstract Apache-compatible implementation of L{IDirectoryService}.
+ """
+ def __repr__(self):
+ return "<%s %r %r>" % (self.__class__.__name__, self.userFile, self.groupFile)
+
+ def __init__(self, userFile, groupFile=None):
+ super(AbstractDirectoryService, self).__init__()
+
+ if type(userFile) is str:
+ userFile = FilePath(userFile)
+ if type(groupFile) is str:
+ groupFile = FilePath(groupFile)
+
+ self.userFile = userFile
+ self.groupFile = groupFile
+
+ def recordTypes(self):
+ recordTypes = ("user",)
+ if self.groupFile is not None:
+ recordTypes += ("group",)
+ return recordTypes
+
+ def listRecords(self, recordType):
+ for entryShortName, entryData in self.entriesForRecordType(recordType):
+ if recordType == "user":
+ yield self.userRecordClass(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ cryptPassword = entryData,
+ )
+
+ elif recordType == "group":
+ yield GroupRecord(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ members = entryData,
+ )
+
+ else:
+ # Subclass should cover the remaining record types
+ raise AssertionError("Unknown record type: %r" % (recordType,))
+
+ def recordWithShortName(self, recordType, shortName):
+ for entryShortName, entryData in self.entriesForRecordType(recordType):
+ if entryShortName == shortName:
+ if recordType == "user":
+ return self.userRecordClass(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ cryptPassword = entryData,
+ )
+
+ if recordType == "group":
+ return GroupRecord(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ members = entryData,
+ )
+
+ # Subclass should cover the remaining record types
+ raise AssertionError("Unknown record type: %r" % (recordType,))
+
+ return None
+
+ def recordWithGUID(self, guid):
+ raise NotImplementedError()
+
+ def entriesForRecordType(self, recordType):
+ if recordType == "user":
+ recordFile = self.userFile
+ elif recordType == "group":
+ recordFile = self.groupFile
+ else:
+ raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
+
+ if recordFile is None:
+ return
+
+ for entry in recordFile.open():
+ if entry and entry[0] != "#":
+ shortName, rest = entry.rstrip("\n").split(":", 1)
+ yield shortName, rest
+
+class AbstractDirectoryRecord(DirectoryRecord):
+ """
+ Abstract Apache-compatible implementation of L{IDirectoryRecord}.
+ """
+ def __init__(self, service, recordType, shortName):
+ super(AbstractDirectoryRecord, self).__init__(
+ service = service,
+ recordType = recordType,
+ guid = None,
+ shortName = shortName,
+ fullName = None,
+ calendarUserAddresses = (),
+ )
+
+class AbstractUserRecord(AbstractDirectoryRecord):
+ def __init__(self, service, recordType, shortName, cryptPassword=None):
+ super(AbstractUserRecord, self).__init__(service, recordType, shortName)
+
+ self._cryptPassword = cryptPassword
+
+ def groups(self):
+ for group in self.service.listRecords("group"):
+ for member in group.members():
+ if member == self:
+ yield group
+ continue
+
+class BasicUserRecord(AbstractUserRecord):
+ """
+ Apache UserFile implementation of L{IDirectoryRecord}.
+ """
+ def verifyCredentials(self, credentials):
+ if self._cryptPassword in ("", "*", "x"):
+ return False
+
+ if isinstance(credentials, UsernamePassword):
+ return crypt(credentials.password, self._cryptPassword) == self._cryptPassword
+
+ return super(BasicUserRecord, self).verifyCredentials(credentials)
+
+class BasicDirectoryService(AbstractDirectoryService):
+ """
+ Apache UserFile/GroupFile implementation of L{IDirectoryService}.
+ """
+ userRecordClass = BasicUserRecord
+
+class DigestUserRecord(AbstractUserRecord):
+ """
+ Apache DigestUserFile implementation of L{IDirectoryRecord}.
+ """
+ def verifyCredentials(self, credentials):
+ raise NotImplementedError()
+
+class DigestDirectoryService(AbstractDirectoryService):
+ """
+ Apache DigestUserFile/GroupFile implementation of L{IDirectoryService}.
+ """
+ userRecordClass = DigestUserRecord
+
+class GroupRecord(AbstractDirectoryRecord):
+ """
+ Apache GroupFile implementation of L{IDirectoryRecord}.
+ """
+ def __init__(self, service, recordType, shortName, members=()):
+ super(GroupRecord, self).__init__(service, recordType, shortName)
+
+ if type(members) is str:
+ members = tuple(m.strip() for m in members.split(","))
+
+ self._members = members
+
+ def members(self):
+ for shortName in self._members:
+ yield self.service.recordWithShortName("user", shortName)
Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -13,101 +13,215 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-# DRI: Cyrus Daboo, cdaboo at apple.com
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
##
"""
-Apple Open Directory implementation.
+Apple Open Directory directory service implementation.
"""
__all__ = [
"OpenDirectoryService",
- "OpenDirectoryRecord",
+ "OpenDirectoryInitError",
]
+import sys
+
import opendirectory
import dsattributes
+from twisted.python import log
+from twisted.internet import reactor
+from twisted.cred.credentials import UsernamePassword
+
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
+recordListCacheTimeout = 60 * 5 # 5 minutes
+
class OpenDirectoryService(DirectoryService):
"""
Open Directory implementation of L{IDirectoryService}.
"""
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.node)
+
def __init__(self, node="/Search"):
+ """
+ @param node: an OpenDirectory node name to bind to.
+ """
directory = opendirectory.odInit(node)
if directory is None:
- raise ValueError("Failed to open Open Directory Node: %s" % (node,))
+ raise OpenDirectoryInitError("Failed to open Open Directory Node: %s" % (node,))
- self._directory = directory
+ self.directory = directory
+ self.node = node
+ self._records = {}
+ def __cmp__(self, other):
+ if not isinstance(other, DirectoryRecord):
+ return super(DirectoryRecord, self).__eq__(other)
+
+ for attr in ("directory", "node"):
+ diff = cmp(getattr(self, attr), getattr(other, attr))
+ if diff != 0:
+ return diff
+ return 0
+
+ def __hash__(self):
+ h = hash(self.__class__)
+ for attr in ("directory", "node"):
+ h = (h + hash(getattr(self, attr))) & sys.maxint
+ return h
+
def recordTypes(self):
return ("user", "group", "resource")
- def listRecords(self, recordType):
- def makeRecord(shortName, guid, lastModified, principalURI):
- if not guid:
- return None
+ def _cacheRecords(self, recordType):
+ if recordType not in self._records:
+ log.msg("Reloading %s record cache" % (recordType,))
- ##
- # FIXME: Also verify that principalURI is on this server
- # Which probably means that the host information needs to be on
- # the site object, and that we need the site object passed to
- # __init__() here.
- ##
+ if recordType == "user":
+ listRecords = opendirectory.listUsers
+ elif recordType == "group":
+ listRecords = opendirectory.listGroups
+ elif recordType == "resource":
+ listRecords = opendirectory.listResources
+ else:
+ raise UnknownRecordTypeError("Unknown Open Directory record type: %s" % (recordType,))
- return OpenDirectoryRecord(
- directory = self,
- recordType = recordType,
- guid = guid,
- shortName = shortName,
- fullName = None,
- )
+ records = {}
- if recordType == "user":
- for data in opendirectory.listUsers(self._directory):
- yield makeRecord(*data)
- return
+ for shortName, guid, lastModified, principalURI in listRecords(self.directory):
+ if not guid:
+ continue
- if recordType == "group":
- for data in opendirectory.listGroups(self._directory):
- yield makeRecord(*data)
- return
+ # FIXME: This is a second directory lookup; we should have gotten everything in one pass...
+ if recordType == "group":
+ result = opendirectory.listGroupsWithAttributes(self.directory, [shortName])
+ if result is None or shortName not in result:
+ log.err("Group %s exists and then doesn't." % (shortName,))
+ continue
+ result = result[shortName]
- if recordType == "resource":
- for data in opendirectory.listResources(self._directory):
- yield makeRecord(*data)
- return
+ memberGUIDs = result.get(dsattributes.attrGroupMembers, None)
+ if memberGUIDs is None:
+ memberGUIDs = ()
+ elif type(memberGUIDs) is str:
+ memberGUIDs = (memberGUIDs,)
+ else:
+ memberGUIDs = ()
- raise AssertionError("Unknown Open Directory record type: %s" % (recordType,))
+ records[shortName] = OpenDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ guid = guid,
+ shortName = shortName,
+ fullName = None, # FIXME: Need to get this attribute
+ calendarUserAddresses = (), # FIXME: Should be able to look up email, etc.
+ memberGUIDs = memberGUIDs,
+ )
+ if records:
+ self._records[recordType] = records
+
+ def flush():
+ log.msg("Flushing %s record cache" % (recordType,))
+ del self._records[recordType]
+ reactor.callLater(recordListCacheTimeout, flush)
+ else:
+ # records is empty. This may mean the directory went down.
+ # Don't cache this result, so that we keep checking the directory.
+ return records
+
+ return self._records[recordType]
+
+ def listRecords(self, recordType):
+ return self._cacheRecords(recordType).itervalues()
+
def recordWithShortName(self, recordType, shortName):
- if recordType == "user":
- result = opendirectory.listUsersWithAttributes(self._directory, [shortName])
- if shortName not in result:
- return None
- result = result[shortName]
- elif recordType == "group":
- result = opendirectory.groupAttributes(self._directory, shortName)
- elif recordType == "resource":
- result = opendirectory.resourceAttributes(self._directory, shortName)
- else:
- raise ValueError("Unknown record type: %s" % (recordType,))
+ return self._cacheRecords(recordType).get(shortName, None)
- return OpenDirectoryRecord(
- directory = self,
- recordType = recordType,
- guid = result[dsattributes.attrGUID],
- shortName = shortName,
- fullName = result[dsattributes.attrRealName],
- )
+ def recordWithGUID(self, guid):
+ for recordType in self.recordTypes():
+ for record in self._cacheRecords(recordType).itervalues():
+ if record.guid == guid:
+ return record
+ return None
+# def recordWithShortName(self, recordType, shortName):
+# if recordType == "user":
+# listRecords = opendirectory.listUsersWithAttributes
+# elif recordType == "group":
+# listRecords = opendirectory.listGroupsWithAttributes
+# elif recordType == "resource":
+# listRecords = opendirectory.listResourcesWithAttributes
+# else:
+# raise UnknownRecordTypeError("Unknown record type: %s" % (recordType,))
+#
+# result = listRecords(self.directory, [shortName])
+# if result is None or shortName not in result:
+# return None
+# else:
+# result = result[shortName]
+#
+# if dsattributes.attrGUID in result:
+# guid = result[dsattributes.attrGUID]
+# else:
+# raise DirectoryError("Found OpenDirectory record %s of type %s with no GUID attribute"
+# % (shortName, recordType))
+#
+# if dsattributes.attrRealName in result:
+# fullName = result[dsattributes.attrRealName]
+# else:
+# fullName = None
+#
+# return OpenDirectoryRecord(
+# service = self,
+# recordType = recordType,
+# guid = guid,
+# shortName = shortName,
+# fullName = fullName,
+# )
+
class OpenDirectoryRecord(DirectoryRecord):
"""
Open Directory implementation of L{IDirectoryRecord}.
"""
- def authenticate(self, credentials):
- if isinstance(credentials, credentials.UsernamePassword):
- return opendirectory.authenticateUser(self.directory, self.shortName, credentials.password)
+ def __init__(self, service, recordType, guid, shortName, fullName, calendarUserAddresses, memberGUIDs):
+ super(OpenDirectoryRecord, self).__init__(
+ service = service,
+ recordType = recordType,
+ guid = guid,
+ shortName = shortName,
+ fullName = fullName,
+ calendarUserAddresses = calendarUserAddresses,
+ )
+ self._memberGUIDs = tuple(memberGUIDs)
- return False
+ def members(self):
+ if self.recordType != "group":
+ return
+
+ for guid in self._memberGUIDs:
+ userRecord = self.service.recordWithGUID(guid)
+ if userRecord is None:
+ log.err("No record for member of group %s with GUID %s" % (self.shortName, guid))
+ else:
+ yield userRecord
+
+ def groups(self):
+ for groupRecord in self.service._cacheRecords("group").itervalues():
+ if self.guid in groupRecord._memberGUIDs:
+ yield groupRecord
+
+ def verifyCredentials(self, credentials):
+ if isinstance(credentials, UsernamePassword):
+ return opendirectory.authenticateUser(self.service.directory, self.shortName, credentials.password)
+
+ return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
+
+class OpenDirectoryInitError(DirectoryError):
+ """
+ OpenDirectory initialization error.
+ """
Deleted: CalendarServer/trunk/twistedcaldav/directory/cred.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/cred.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/cred.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,79 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Cyrus Daboo, cdaboo at apple.com
-##
-
-"""
-Implements a directory-backed principal hierarchy.
-"""
-
-__all__ = [
- "DirectoryCredentialsChecker",
-]
-
-from twisted.internet.defer import succeed
-from twisted.cred.credentials import UsernamePassword
-from twisted.cred.error import UnauthorizedLogin
-from twisted.web2.dav.auth import IPrincipalCredentials
-from twisted.web2.dav.auth import TwistedPropertyChecker
-
-import opendirectory
-
-from twistedcaldav import customxml
-
-class DirectoryCredentialsChecker (TwistedPropertyChecker):
-
- def requestAvatarId(self, credentials):
-
- # If there is no calendar principal URI then the calendar user is disabled.
- pcreds = IPrincipalCredentials(credentials)
- if not pcreds.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
- # Try regular password check
- return TwistedPropertyChecker.requestAvatarId(self, credentials)
-
- creds = pcreds.credentials
- if isinstance(creds, UsernamePassword):
- user = creds.username
- pswd = creds.password
- if opendirectory.authenticateUser(pcreds.authnPrincipal.directory(), user, pswd):
- return succeed((pcreds.authnURI, pcreds.authzURI,))
-
- raise UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
-
-#class DirectoryCredentialsChecker (TwistedPropertyChecker):
-# def __init__(self, service):
-# """
-# @param service: an L{IDirectoryService} provider.
-# """
-# self.service = service
-#
-# def requestAvatarId(self, credentials):
-# # If there is no calendar principal URI then the calendar user is disabled.
-# credentials = IPrincipalCredentials(credentials)
-# if not credentials.authnPrincipal.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
-# # Try regular password check
-# return TwistedPropertyChecker.requestAvatarId(self, credentials)
-#
-# user = self.service.userWithShortName(credentials.credentials.username)
-# raise UnauthorizedLogin("Unknown credentials type for principal: %s" % (credentials.authnURI,))
-#
-# if not user:
-# raise UnauthorizedLogin("No such user: %s" % (user,))
-#
-# if user.authenticate(credentials.credentials):
-# return succeed((credentials.authnURI, credentials.authzURI))
-# else:
-# raise UnauthorizedLogin("Incorrect credentials for user: %s" % (user,))
Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-# DRI: Cyrus Daboo, cdaboo at apple.com
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
##
"""
@@ -21,25 +21,108 @@
"""
__all__ = [
+ "DirectoryService",
"DirectoryRecord",
+ "DirectoryError",
+ "UnknownRecordError",
+ "UnknownRecordTypeError",
]
+import sys
+
from zope.interface import implements
+from twisted.cred.error import UnauthorizedLogin
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.web2.dav.auth import IPrincipalCredentials
+
from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
class DirectoryService(object):
- implements(IDirectoryService)
+ implements(IDirectoryService, ICredentialsChecker)
+ ##
+ # IDirectoryService
+ ##
+
+ realmName = None
+
+ ##
+ # ICredentialsChecker
+ ##
+
+ # For ICredentialsChecker
+ credentialInterfaces = (IPrincipalCredentials,)
+
+ def requestAvatarId(self, credentials):
+ credentials = IPrincipalCredentials(credentials)
+
+ # FIXME: ?
+ # We were checking if principal is enabled; seems unnecessary in current
+ # implementation because you shouldn't have a principal object for a
+ # disabled directory principal.
+
+ user = self.recordWithShortName("user", credentials.credentials.username)
+ if user is None:
+ raise UnauthorizedLogin("No such user: %s" % (user,))
+
+ if user.verifyCredentials(credentials.credentials):
+ return (credentials.authnPrincipal.principalURL(), credentials.authzPrincipal.principalURL())
+ else:
+ raise UnauthorizedLogin("Incorrect credentials for %s" % (user,))
+
class DirectoryRecord(object):
implements(IDirectoryRecord)
- def __init__(self, directory, recordType, guid, shortName, fullName=None):
- self.directory = directory
- self.recordType = recordType
- self.guid = guid
- self.shortName = shortName
- self.fullName = fullName
+ def __repr__(self):
+ return "<%s[%s@%s] %s(%s) %r>" % (
+ self.__class__.__name__,
+ self.recordType,
+ self.service,
+ self.guid,
+ self.shortName,
+ self.fullName
+ )
- def authenticate(credentials):
+ def __init__(self, service, recordType, guid, shortName, fullName, calendarUserAddresses):
+ self.service = service
+ self.recordType = recordType
+ self.guid = guid
+ self.shortName = shortName
+ self.fullName = fullName
+ self.calendarUserAddresses = calendarUserAddresses
+
+ def __cmp__(self, other):
+ if not isinstance(other, DirectoryRecord):
+ return NotImplemented
+
+ for attr in ("service", "recordType", "shortName", "guid"):
+ diff = cmp(getattr(self, attr), getattr(other, attr))
+ if diff != 0:
+ return diff
+ return 0
+
+ def __hash__(self):
+ h = hash(self.__class__)
+ for attr in ("service", "recordType", "shortName", "guid"):
+ h = (h + hash(getattr(self, attr))) & sys.maxint
+ return h
+
+ def members(self):
+ return ()
+
+ def groups(self):
+ return ()
+
+ def verifyCredentials(self, credentials):
return False
+
+class DirectoryError(RuntimeError):
+ """
+ Generic directory error.
+ """
+
+class UnknownRecordTypeError(DirectoryError):
+ """
+ Unknown directory record type.
+ """
Modified: CalendarServer/trunk/twistedcaldav/directory/idirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/idirectory.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/idirectory.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -31,6 +31,8 @@
"""
Directory Service
"""
+ realmName = Attribute("The name of the authentication realm this service represents.")
+
def recordTypes():
"""
@return: a sequence of strings denoting the record types that are kept
@@ -48,21 +50,41 @@
@param recordType: the type of the record to look up.
@param shortName: the short name of the record to look up.
@return: an L{IDirectoryRecord} provider with the given short name, or
- C{None} of no such record exists.
+ C{None} if no such record exists.
"""
+ def recordWithGUID(guid):
+ """
+ @param shortName: the GUID of the record to look up.
+ @return: an L{IDirectoryRecord} provider with the given GUID, or C{None}
+ if no such record exists.
+ """
+
class IDirectoryRecord(Interface):
"""
Directory Record
"""
- directory = Attribute("The L{IDirectoryService} this record exists in.")
- recordType = Attribute("The type of this record.")
- guid = Attribute("The GUID of this record.")
- shortName = Attribute("The name of this record.")
- fullName = Attribute("The full name of this record.")
+ service = Attribute("The L{IDirectoryService} this record exists in.")
+ recordType = Attribute("The type of this record.")
+ guid = Attribute("The GUID of this record.")
+ shortName = Attribute("The name of this record.")
+ fullName = Attribute("The full name of this record.")
+ calendarUserAddresses = Attribute("The list of calendar user addresses for this record.")
- def authenticate(credentials):
+ def members():
"""
+ @return: an iterable of L{IDirectoryRecord}s for the members of this
+ (group) record.
+ """
+
+ def group():
+ """
+ @return: an iterable of L{IDirectoryRecord}s for the groups this
+ record is a member of.
+ """
+
+ def verifyCredentials(credentials):
+ """
Verify that the given credentials can authenticate the principal
represented by this record.
@param credentials: the credentials to authenticate with.
Copied: CalendarServer/trunk/twistedcaldav/directory/principal.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/principal.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,350 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+"""
+Implements a directory-backed principal hierarchy.
+"""
+
+__all__ = [
+ "DirectoryPrincipalProvisioningResource",
+ "DirectoryPrincipalTypeResource",
+ "DirectoryPrincipalResource",
+]
+
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.internet.defer import succeed
+from twisted.web2 import responsecode
+from twisted.web2.http import Response, HTTPError
+from twisted.web2.http_headers import MimeType
+from twisted.web2.dav import davxml
+from twisted.web2.dav.resource import TwistedACLInheritable
+from twisted.web2.dav.static import DAVFile
+from twisted.web2.dav.util import joinURL
+
+from twistedcaldav.extensions import ReadOnlyResourceMixIn
+from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
+from twistedcaldav.directory.idirectory import IDirectoryService
+
+# FIXME: These should not be tied to DAVFile
+# The reason that they is that web2.dav only implements DAV methods on
+# DAVFile instead of DAVResource. That should change.
+
+class PermissionsMixIn (ReadOnlyResourceMixIn):
+ def defaultAccessControlList(self):
+ return authReadACL
+
+ def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+ # Permissions here are fixed, and are not subject to inherritance rules, etc.
+ return succeed(self.defaultAccessControlList())
+
+class DirectoryPrincipalProvisioningResource (PermissionsMixIn, CalendarPrincipalCollectionResource, DAVFile):
+ """
+ Collection resource which provisions directory principals as its children.
+ """
+ def __init__(self, path, url, directory):
+ """
+ @param path: the path to the file which will back the resource.
+ @param url: the canonical URL for the resource.
+ @param directory: an L{IDirectoryService} to provision principals from.
+ """
+ assert url.endswith("/"), "Collection URL must end in '/'"
+
+ CalendarPrincipalCollectionResource.__init__(self, url)
+ DAVFile.__init__(self, path)
+
+ self.directory = IDirectoryService(directory)
+
+ # FIXME: Smells like a hack
+ directory.principalCollection = self
+
+ self._provision()
+
+ # Create children
+ for recordType in self.directory.recordTypes():
+ self.putChild(recordType, DirectoryPrincipalTypeResource(self.fp.child(recordType).path, self, recordType))
+
+ def _provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ self.fp.makedirs()
+ self.fp.restat(False)
+
+ def principalForUser(self, user):
+ return self.getChild("user").getChild(user)
+
+ def principalForRecord(self, record):
+ typeResource = self.getChild(record.recordType)
+ if typeResource is None:
+ return None
+ return typeResource.getChild(record.shortName)
+
+ ##
+ # Static
+ ##
+
+ def createSimilarFile(self, path):
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ def getChild(self, name):
+ self._provision()
+ return self.putChildren.get(name, None)
+
+ def listChildren(self):
+ return self.putChildren.keys()
+
+ ##
+ # ACL
+ ##
+
+ def principalCollections(self):
+ return (self,)
+
+class DirectoryPrincipalTypeResource (PermissionsMixIn, CalendarPrincipalCollectionResource, DAVFile):
+ """
+ Collection resource which provisions directory principals of a specific type as its children.
+ """
+ def __init__(self, path, parent, recordType):
+ """
+ @param path: the path to the file which will back the resource.
+ @param directory: an L{IDirectoryService} to provision calendars from.
+ @param recordType: the directory record type to provision.
+ """
+ CalendarPrincipalCollectionResource.__init__(self, joinURL(parent.principalCollectionURL(), recordType) + "/")
+ DAVFile.__init__(self, path)
+
+ self.directory = parent.directory
+ self.recordType = recordType
+ self._parent = parent
+
+ self._provision()
+
+ def _provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.makedirs()
+ self.fp.restat(False)
+
+ def principalForUser(self, user):
+ return self._parent.principalForUser(user)
+
+ def principalForRecord(self, record):
+ return self._parent.principalForRecord(record)
+
+ ##
+ # Static
+ ##
+
+ def createSimilarFile(self, path):
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ def getChild(self, name, record=None):
+ self._provision()
+
+ if name == "":
+ return self
+
+ if record is None:
+ record = self.directory.recordWithShortName(self.recordType, name)
+ if record is None:
+ return None
+ else:
+ assert name is None
+ name = record.shortName
+
+ return DirectoryPrincipalResource(self.fp.child(name).path, self, record)
+
+ def listChildren(self):
+ return (record.shortName for record in self.directory.listRecords(self.recordType))
+
+ ##
+ # ACL
+ ##
+
+ def principalCollections(self):
+ return self._parent.principalCollections()
+
+class DirectoryPrincipalResource (PermissionsMixIn, CalendarPrincipalResource, DAVFile):
+ """
+ Directory principal resource.
+ """
+ def __init__(self, path, parent, record):
+ """
+ @param path: them path to the file which will back this resource.
+ @param parent: the parent of this resource.
+ @param record: the L{IDirectoryRecord} that this resource represents.
+ """
+ super(DirectoryPrincipalResource, self).__init__(path, joinURL(parent.principalCollectionURL(), record.shortName))
+
+ self.record = record
+ self._parent = parent
+ self._url = joinURL(parent.principalCollectionURL(), record.shortName)
+
+ self._provision()
+
+ def _provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.open("w").close()
+ self.fp.restat(False)
+
+ ##
+ # HTTP
+ ##
+
+ def render(self, request):
+ def format_list(method, *args):
+ def genlist():
+ try:
+ item = None
+ for item in method(*args):
+ yield " -> %s\n" % (item,)
+ if item is None:
+ yield " '()\n"
+ except Exception, e:
+ log.err("Exception while rendering: %s" % (e,))
+ Failure().printTraceback()
+ yield " ** %s **: %s\n" % (e.__class__.__name__, e)
+ return "".join(genlist())
+
+ output = ("".join((
+ "Principal resource\n"
+ "------------------\n"
+ "\n"
+ #"Directory service: %s\n" % (self.record.service,),
+ "Record type: %s\n" % (self.record.recordType,),
+ "GUID: %s\n" % (self.record.guid,),
+ "Short name: %s\n" % (self.record.shortName,),
+ "Full name: %s\n" % (self.record.fullName,),
+ "Principal UID: %s\n" % (self.principalUID(),),
+ "Principal URL: %s\n" % (self.principalURL(),),
+ "\nAlternate URIs:\n" , format_list(self.alternateURIs),
+ "\nGroup members:\n" , format_list(self.groupMembers),
+ "\nGroup memberships:\n" , format_list(self.groupMemberships),
+ "\nCalendar homes:\n" , format_list(self.calendarHomeURLs),
+ "\nCalendar user addresses:\n" , format_list(self.calendarUserAddresses),
+ )))
+
+ if type(output) == unicode:
+ output = output.encode("utf-8")
+ mime_params = {"charset": "utf-8"}
+ else:
+ mime_params = {}
+
+ response = Response(code=responsecode.OK, stream=output)
+ response.headers.setHeader("content-type", MimeType("text", "plain", mime_params))
+
+ return response
+
+ ##
+ # DAV
+ ##
+
+ def displayName(self):
+ if self.record.fullName:
+ return self.record.fullName
+ else:
+ return self.record.shortName
+
+ ##
+ # ACL
+ ##
+
+ def alternateURIs(self):
+ # FIXME: Add API to IDirectoryRecord for getting a record URI?
+ return ()
+
+ def principalURL(self):
+ return self._url
+
+ def _getRelatives(self, method, record=None, relatives=None, records=None):
+ if record is None:
+ record = self.record
+ if relatives is None:
+ relatives = set()
+ if records is None:
+ records = set()
+
+ if record not in records:
+ records.add(record)
+ myRecordType = self.record.recordType
+ for relative in getattr(record, method)():
+ if relative not in records:
+ if relative.recordType == myRecordType:
+ relatives.add(self._parent.getChild(None, record=relative))
+ else:
+ relatives.add(self._parent._parent.getChild(relative.recordType).getChild(None, record=relative))
+ self._getRelatives(method, relative, relatives, records)
+
+ return relatives
+
+ def groupMembers(self):
+ return self._getRelatives("members")
+
+ def groupMemberships(self):
+ return self._getRelatives("groups")
+
+ def principalCollections(self):
+ return self._parent.principalCollections()
+
+ ##
+ # CalDAV
+ ##
+
+ def principalUID(self):
+ return self.record.shortName
+
+ def calendarUserAddresses(self):
+ # Add the principal URL to whatever calendar user addresses
+ # the directory record provides.
+ return (self.principalURL(),) + tuple(self.record.calendarUserAddresses)
+
+ def calendarHomeURLs(self):
+ # FIXME: self.record.service.calendarHomesCollection smells like a hack
+ # See CalendarHomeProvisioningFile.__init__()
+ service = self.record.service
+ if hasattr(service, "calendarHomesCollection"):
+ return (service.calendarHomesCollection.homeForDirectoryRecord(self.record).url(),)
+ else:
+ return ()
+
+ def scheduleInboxURL(self):
+ return self._homeChildURL("inbox/")
+
+ def scheduleOutboxURL(self):
+ return self._homeChildURL("outbox/")
+
+ def _homeChildURL(self, name):
+ homes = self.calendarHomeURLs()
+ if homes:
+ return joinURL(homes[0], name)
+ else:
+ return None
+
+authReadACL = davxml.ACL(
+ # Read access for authenticated users.
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(davxml.Read())),
+ davxml.Protected(),
+ ),
+)
Deleted: CalendarServer/trunk/twistedcaldav/directory/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/resource.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,985 +0,0 @@
-##
-# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Cyrus Daboo, cdaboo at apple.com
-##
-
-"""
-Implements a directory-backed principal hierarchy.
-"""
-
-__all__ = [
- "DirectoryPrincipalFile",
- "DirectoryUserPrincipalProvisioningResource",
- "DirectoryGroupPrincipalProvisioningResource",
- "DirectoryResourcePrincipalProvisioningResource",
- "DirectoryPrincipalProvisioningResource",
-]
-
-from twisted.python import log
-from twisted.internet import reactor
-from twisted.internet import task
-from twisted.internet.defer import succeed
-from twisted.cred import credentials
-from twisted.web2 import responsecode
-from twisted.web2.dav import davxml
-from twisted.web2.dav.static import DAVFile
-from twisted.web2.dav.util import joinURL
-from twisted.web2.http import HTTPError
-from twisted.web2.http import StatusResponse
-
-from twistedcaldav import caldavxml
-from twistedcaldav import customxml
-from twistedcaldav.principalindex import GroupIndex
-from twistedcaldav.principalindex import ResourceIndex
-from twistedcaldav.principalindex import UserIndex
-from twistedcaldav.resource import CalendarPrincipalCollectionResource
-from twistedcaldav.static import CalendarPrincipalFile
-
-import dsattributes
-import opendirectory
-import os
-import unicodedata
-
-class DirectoryPrincipalFile (CalendarPrincipalFile):
- """
- Directory principal resource.
- """
- def __init__(self, parent, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. This is the url which
- will be returned by L{principalURL}.
- """
- super(DirectoryPrincipalFile, self).__init__(path, url)
-
- self._parent = parent
-
- def checkCredentials(self, creds):
- """
- Check whether the provided credentials can be used to authenticate this prinicpal.
-
- @param creds: the L{ICredentials} for testing.
- @return: True if the credentials match, False otherwise
- """
-
- # If there is no calendar principal URI then the calendar user is disabled.
- if not self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
- return False
-
- if isinstance(creds, credentials.UsernamePassword):
- return opendirectory.authenticateUser(self._parent.directory, self.fp.basename(), creds.password)
- else:
- return False
-
- def directory(self):
- """
- Get the directory object used for directory operations.
-
- @return: C{object} for the directory instance
- """
-
- return self._parent.directory
-
- def groupMembers(self):
- """
- See L{IDAVPrincipalResource.groupMembers}.
- """
-
- # Check for the list of group member GUIDs
- if self.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
- # Get the list of GUIDs from the WebDAV private property
- memberguids = self.readDeadProperty(customxml.TwistedGroupMemberGUIDs())
- guids = [str(e) for e in memberguids.children]
-
- # Try to find each GUID in collections
- result = []
- for guid in guids:
- uri = DirectoryTypePrincipalProvisioningResource.findAnyGUID(guid)
- if uri is not None:
- result.append(uri)
-
- return result
-
- return ()
-
- def groupMemberships(self):
- """
- See L{IDAVPrincipalResource.groupMemberships}.
- """
-
- # Find any groups that match this user's GUID
- guid = self.getGUID()
- return DirectoryTypePrincipalProvisioningResource.findAnyGroupGUID(guid)
-
- def getPropertyValue(self, cls):
- """
- Get the requested proeprty value or return empty string.
- """
-
- if self.hasDeadProperty(cls()):
- prop = self.readDeadProperty(cls())
- return str(prop)
- else:
- return ""
-
- def setPropertyValue(self, str, cls):
- """
- Set the requested property value or remove it if the value is empty.
- """
-
- if str:
- self.writeDeadProperty(cls.fromString(str))
- else:
- self.removeDeadProperty(cls())
-
- def getGUID(self):
- return self.getPropertyValue(customxml.TwistedGUIDProperty)
-
- def readProperty(self, property, request):
- if type(property) is tuple:
- qname = property
- else:
- qname = property.qname()
-
- namespace, name = qname
-
- if namespace == caldavxml.caldav_namespace:
- if name == "calendar-user-address-set":
- return succeed(caldavxml.CalendarUserAddressSet(
- *[davxml.HRef().fromString(uri) for uri in self.calendarUserAddresses()]
- ))
-
- return super(DirectoryPrincipalFile, self).readProperty(qname, request)
-
- def writeProperty(self, property, request):
- # This resource is read-only.
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Protected property %s may not be set." % (property.sname(),)
- ))
-
- def calendarUserAddresses(self):
- # Must have a valid calendar principal uri
- if self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI):
- return (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI),)
- else:
- # If there is no calendar principal URI then the calendar user is disabled so do not provide
- # a valid calendar address.
- return ()
-
- def matchesCalendarUserAddress(self, request, address):
- # By default we will always allow either a relative or absolute URI to the principal to
- # be supplied as a valid calendar user address.
-
- # Try calendar principal URI
- return self.hasDeadProperty(customxml.TwistedCalendarPrincipalURI) and (self.getPropertyValue(customxml.TwistedCalendarPrincipalURI) == address)
-
- def enable(self, calhome, enable):
- """
- Enable or disable this principal and access to any calendars owned by it.
-
- @param calhome: L{DAVFile} for the container of the calendar home of this user.
- @param enable: C{True} to enable, C{False} to disable.
- """
- # Get home collection resource
- calrsrc = calhome.getChild(self.principalUID())
-
- # Handle access for the calendar home
- if enable:
- calrsrc.disable(False)
- else:
- calrsrc.disable(True)
-
- def remove(self, calhome):
- """
- Remove this principal by hiding (archiving) any calendars owned by it. This is done
- by turning on the disabling and renaming the calendar home to ensure a future user
- with the same id won't see the old calendars.
-
- @param calhome: L{DAVFile} for the container of the calendar home of this user.
- """
-
- # Get home collection resource
- calrsrc = calhome.getChild(self.principalUID())
-
- # Disable access for the calendar home
- calrsrc.disable(True)
-
- # Rename the calendar home to the original name with the GUID appended
- newname = self.principalUID() + "-" + self.getPropertyValue(customxml.TwistedGUIDProperty)
-
- try:
- # Make sure the new name is not already in use
- if os.path.exists(newname):
- count = 1
- tempname = newname + "-%d"
- while(os.path.exists(tempname % count)):
- count += 1
- newname = tempname % count
- os.rename(calrsrc.fp.path, calrsrc.fp.sibling(newname).path)
- except OSError:
- log.msg("Directory: Failed to rename %s to %s when deleting a principal" %
- (calrsrc.fp.path, calrsrc.fp.sibling(newname).path))
-
- # Remove the disabled property to prevent lock out in the future
- calrsrc.disable(False)
-
-class DirectoryTypePrincipalProvisioningResource (CalendarPrincipalCollectionResource, DAVFile):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
-
- This includes a periodic task for refreshing the cached data.
- """
- periodicSyncIntervalSeconds = 60.0
-
- typeUnknown = 0
- typeUser = 1
- typeGroup = 2
- typeResource = 3
-
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- CalendarPrincipalCollectionResource.__init__(self, url)
- DAVFile.__init__(self, path)
- self.directory = None
- self.calendarhomeroot = None
- self.index = None
- self.type = DirectoryTypePrincipalProvisioningResource.typeUnknown
-
- def setup(self, directory):
- self.directory = directory
-
- def initialize(self, homeuri, home):
- """
- May be called during repository account initialization.
-
- @param homeuri: C{str} uri of the calendar home root.
- @param home: L{DAVFile} of the calendar home root.
- """
-
- # Make sure index is valid and sync with directory
- self.index.check()
- self.calendarhomeroot = (homeuri, home)
-
- #
- # There is a problem with the interaction of Directory Services and the
- # fork/fork process the server goes through to daemonize itself. For some
- # resaon, if DS is used before the fork, then calls to it afterwards all
- # return eServerSendError (-14740) errors.
- #
- # To get around this we must not use opendirectory module calls here, as this
- # method gets run before the fork/fork. So instead, we schedule a sync
- # operation to occur one second after the reactor starts up - which is after
- # the fork/fork. The problem with this is that the http server is already up
- # and running at that point BEFORE any initially provisioning is done.
- #
-
- # Create a periodic sync operation to keep the cached user list
- # in sync with the directory server.
- def periodicSync(self):
- self.syncNames()
-
- # Add initial sync operation
- reactor.callLater(1.0, periodicSync, self) #@UndefinedVariable
-
- # Add periodic sync operations
- runner = task.LoopingCall(periodicSync, self)
- runner.start(DirectoryTypePrincipalProvisioningResource.periodicSyncIntervalSeconds, now=False)
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containing C{str}'s for each name found, or C{None} if failed.
- """
- raise NotImplementedError
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- raise NotImplementedError
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- raise NotImplementedError
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory and that the
- name corresponds to one that can use the calendar.
-
- @param name: C{str} of the name to check.
- @return: C{True} if the name exists, C{False} otherwise.
- """
- raise NotImplementedError
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- raise NotImplementedError
-
- def syncNames(self):
- """
- Synchronize the data in the directory with the local cache of resources in the file system.
- """
- #log.msg("Directory: Synchronizing cache for %s" % (self.getTitle(),))
-
- # Get index entries from directory and from cache
- remoteindex = self.listIndexAttributes()
- localindex = self.index.listIndex()
-
- # Create dicts indexed by GUID for each
- remotedict = {}
- for entry in remoteindex:
- remotedict[entry[dsattributes.indexGUID]] = entry
- localdict = {}
- for entry in localindex:
- localdict[entry[dsattributes.indexGUID]] = entry
-
- # Extract list of GUIDs in each
- remoteguids = [entry[dsattributes.indexGUID] for entry in remoteindex]
- localguids = [entry[dsattributes.indexGUID] for entry in localindex]
-
- remoteguidset = set(remoteguids)
- remoteguids = None
- localguidset = set(localguids)
- localguids = None
-
- new_remote = list(remoteguidset.difference(localguidset))
- removed_remote = list(localguidset.difference(remoteguidset))
-
- # Remove old principals
- old_names = [localdict[guid][dsattributes.indexUID] for guid in removed_remote]
- old_names.sort()
- for name in old_names:
- self.removePrincipal(name, True)
-
- # Get new ones (but only those with a CalendarPrincipalURI attribute)
- new_names = [remotedict[guid][dsattributes.indexUID] for guid in new_remote if remotedict[guid][dsattributes.indexCalendarPrincipalURI]]
-
- # Get all the directory entries for the new ones in one go for better performance
- if new_names:
- new_entries = self.listCommonAttributes(new_names)
- if new_entries is not None:
- new_names = [n for n in new_entries.iterkeys()]
- new_names.sort()
- for name in new_names:
- self.addPrincipal(name, attrs=new_entries[name], fast=True)
-
- # Look for changes in entries
- common_entries = list(remoteguidset.intersection(localguidset))
- for guid in common_entries:
- old = localdict[guid]
- new = remotedict[guid]
- if old != new:
- # Special case issue with unicode normalization of names
- if ((old[dsattributes.indexUID] != new [dsattributes.indexUID]) and
- (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
- unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8"))) and
- (old[1:] == new[1:])):
- continue
-
- self.changedPrincipal(old, new)
-
- # Commit index after all changes are done
- self.index.commit()
-
- def addPrincipal(self, name, attrs=None, fast=False):
- """
- Add a new principal resource to the server.
-
- @param name: C{str} containing the name of the resource to add.
- @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
- # This will create it
- child_fp = self.fp.child(name)
- #assert not child_fp.exists()
-
- assert self.exists()
- assert self.isCollection()
-
- child_fp.open("w").close()
-
- # Now update the principal's cached data
- self.updatePrincipal(name, attrs=attrs, fast=fast, new=True, nolog=True)
-
- log.msg("Directory: Add %s to %s" % (name, self.getTitle()))
-
- def changedPrincipal(self, old, new, fast=False):
- """
- Change a new principal resource to sync with directory.
-
- @param old: C{str} containing the name of the original resource.
- @param new: C{str} containing the name of the new resource.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
- # Look for change to calendar enabled state
-
- # See if the name changed because that is a real pain!
- if ((old[dsattributes.indexUID] != new[dsattributes.indexUID]) and
- (unicodedata.normalize("NFKC", old[dsattributes.indexUID].decode("UTF-8")) ==
- unicodedata.normalize("NFKC", new[dsattributes.indexUID].decode("UTF-8")))):
- self.renamePrincipal(old[dsattributes.indexUID], new[dsattributes.indexUID])
-
- # See if change in enable state
- enable_state = old[dsattributes.indexCalendarPrincipalURI] != new[dsattributes.indexCalendarPrincipalURI]
-
- # Do update (no log message if enable state is being changed as that will generate a log message itself)
- self.updatePrincipal(new[dsattributes.indexUID], nolog=enable_state)
-
- # Look for change in calendar enable state
- if enable_state:
- self.enablePrincipal(new[dsattributes.indexUID], len(new[dsattributes.indexCalendarPrincipalURI]) != 0)
-
- def renamePrincipal(self, old, new):
- """
- Change a principal resource name to sync with directory.
-
- @param old: C{str} containing the name of the original resource.
- @param new: C{str} containing the name of the new resource.
- """
- log.msg("Directory: Renamed Principal %s to %s in %s" % (old, new, self.getTitle()))
- raise NotImplementedError
-
- def updatePrincipal(self, name, attrs = None, fast=False, new=False, nolog=False):
- """
- Update details about the named principal in the principal's own property store
- and the principal collection index.
-
- @param name: C{str} containing the principal name to update.
- @param attrs: C{dict} directory attributes for this name, or C{None} if attributes need to be read in.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- @param new: C{True} when this update is the result of adding a new principal,
- C{False} otherwise.
- """
- # Get attributes from directory
- if attrs is None:
- attrs = self.directoryAttributes(name)
- realname = attrs.get(dsattributes.attrRealName, None)
- guid = attrs.get(dsattributes.attrGUID, None)
- lastModified = attrs.get(dsattributes.attrLastModified, None)
- principalurl = attrs.get(dsattributes.attrCalendarPrincipalURI, None)
-
- # Do provisioning (if the principal is a resource we will turn on the auto-provisioning option)
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to update missing principal: %s in %s" % (name, self.getTitle()))
- return
-
- if (new):
- principal.provisionCalendarAccount(name, None, True, None, self.calendarhomeroot,
- None, None, ["calendar"],
- self.type == DirectoryTypePrincipalProvisioningResource.typeResource,
- self.type == DirectoryTypePrincipalProvisioningResource.typeUser)
-
- # Add directory specific attributes to principal
- principal.setPropertyValue(realname, davxml.DisplayName)
- principal.setPropertyValue(guid, customxml.TwistedGUIDProperty)
- principal.setPropertyValue(lastModified, customxml.TwistedLastModifiedProperty)
- principal.setPropertyValue(principalurl, customxml.TwistedCalendarPrincipalURI)
-
- # Special for group
- if self.type == DirectoryTypePrincipalProvisioningResource.typeGroup:
- # Get comma separated list of members and split into a list
- groupmembers = attrs.get(dsattributes.attrGroupMembers, None)
- if isinstance(groupmembers, list):
- members = groupmembers
- elif isinstance(groupmembers, str):
- members = [groupmembers]
- else:
- members = []
-
- # Create and write the group property
- children = [customxml.TwistedGUIDProperty.fromString(s) for s in members]
- principal.writeDeadProperty(customxml.TwistedGroupMemberGUIDs(*children))
-
- # Do index
- self.index.addPrincipal(name, principal, fast)
-
- if not nolog:
- log.msg("Directory: Updated %s in %s" % (name, self.getTitle()))
-
- def enablePrincipal(self, name, enable):
- """
- Enable or disable calendar access for this principal.
-
- @param enable: C{True} to enable, C{False} to disable
- """
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to enable/disable missing principal: %s in %s" % (name, self.getTitle()))
- return
-
- if enable:
- principal.enable(self.calendarhomeroot[1], True)
- log.msg("Directory: Enabled %s in %s" % (name, self.getTitle()))
- else:
- principal.enable(self.calendarhomeroot[1], False)
- log.msg("Directory: Disabled %s in %s" % (name, self.getTitle()))
-
- def removePrincipal(self, name, fast=False):
- """
- Remove a principal from the cached resources.
-
- @param name: C{str} containing the name of the principal to remove.
- @param fast: if C{True} then final commit is not done, if C{False} commit is done.
- """
-
- # Get the principal to 'hide' its calendars
- principal = self.getChild(name)
- if principal is None:
- log.msg("Directory: Failed to remove missing principal: %s in %s" % (name, self.getTitle()))
- else:
- principal.remove(self.calendarhomeroot[1])
-
- # Now remove the principal resource itself
- child_fp = self.fp.child(name)
- if child_fp.exists():
- os.remove(child_fp.path)
-
- # Do index
- self.index.deleteName(name, fast)
-
- log.msg("Directory: Delete %s from %s" % (name, self.getTitle()))
-
- @staticmethod
- def findAnyGUID(guid):
- """
- Find the principal associated with the specified GUID.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{str} with matching principal URI, or C{None}
- """
- for url in CalendarPrincipalCollectionResource.principleCollectionSet.keys():
- try:
- pcollection = CalendarPrincipalCollectionResource.principleCollectionSet[url]
- if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
- principal = pcollection.findGUID(guid)
- if principal is not None:
- return principal
- except ReferenceError:
- pass
-
- return None
-
- def findGUID(self, guid):
- """
- See if a principal with the specified GUID exists and if so return its principal URI.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{str} with matching principal URI, or C{None}
- """
- name = self.index.nameFromGUID(guid)
- if name is not None:
- principal = self.getChild(name)
- return principal.principalURL()
- else:
- return None
-
- @staticmethod
- def findAnyGroupGUID(clazz, guid):
- """
- Find the principals containing the specified GUID as a group member.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{list} with matching principal URIs
- """
-
- result = []
- for url in CalendarPrincipalCollectionResource.principleCollectionSet.keys():
- try:
- pcollection = CalendarPrincipalCollectionResource.principleCollectionSet[url]
- if isinstance(pcollection, DirectoryTypePrincipalProvisioningResource):
- result.extend(pcollection.findGroupGUID(guid))
- except ReferenceError:
- pass
-
- return result
-
- def findGroupGUID(self, guid):
- """
- Find principals with the specified GUID as a group member.
-
- @param guid: the C{str} containing the GUID to match.
- @return: C{list} with matching principal URIs
- """
- # Only both for group collections
- if self.type != DirectoryTypePrincipalProvisioningResource.typeGroup:
- return []
-
- result = []
- for name in self.listChildren():
- principal = self.getChild(name)
- if principal.hasDeadProperty(customxml.TwistedGroupMemberGUIDs):
- guids = principal.readDeadProperty(customxml.TwistedGroupMemberGUIDs)
- for g in guids.children:
- if str(g) == guid:
- result.append(principal.principalURL())
- break
-
- return result
-
- def isCollection(self):
- """
- See L{IDAVResource.isCollection}.
- """
- return True
-
- def getChild(self, name):
- """
- Look up a child resource.
- @return: the child of this resource with the given name.
- """
- if name == "":
- return self
-
- child = self.putChildren.get(name, None)
- if child: return child
-
- child_fp = self.fp.child(name)
- if child_fp.exists():
- return DirectoryPrincipalFile(self, child_fp.path, joinURL(self._url, name))
- else:
- return None
-
- def principalSearchPropertySet(self):
- """
- See L{IDAVResource.principalSearchPropertySet}.
- """
- return davxml.PrincipalSearchPropertySet(
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- davxml.DisplayName()
- ),
- davxml.Description(
- davxml.PCDATAElement("Display Name"),
- **{"xml:lang":"en"}
- ),
- ),
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- caldavxml.CalendarUserAddressSet()
- ),
- davxml.Description(
- davxml.PCDATAElement("Calendar User Addresses"),
- **{"xml:lang":"en"}
- ),
- ),
- )
-
- def createSimilarFile(self, path):
- if path == self.fp.path:
- return self
- else:
- # TODO: Fix this - not sure how to get URI for second argument of __init__
- return CalendarPrincipalFile(path, "")
-
- def http_PUT (self, request): return responsecode.FORBIDDEN
- def http_MKCOL (self, request): return responsecode.FORBIDDEN
- def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
-
-class DirectoryUserPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = UserIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeUser
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listUsers(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listUsers(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listUsersWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check.
- @return: C{True} if the name exists, C{False} otherwise.
- """
- return opendirectory.checkUser(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listUsersWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "User Principals"
-
-class DirectoryGroupPrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = GroupIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeGroup
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listGroups(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listGroups(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listGroupsWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check
- @return: C{True} if the name exists, C{False} otherwise
- """
- return opendirectory.checkGroup(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listGroupsWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "Group Principals"
-
-class DirectoryResourcePrincipalProvisioningResource (DirectoryTypePrincipalProvisioningResource):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- @param directory: the reference to the directory to use
- """
- DirectoryTypePrincipalProvisioningResource.__init__(self, path, url)
- self.index = ResourceIndex(self)
- self.type = DirectoryTypePrincipalProvisioningResource.typeResource
-
- def listNames(self):
- """
- List all the names currently in the directory.
-
- @return: C{list} containg C{str}'s for each name found, or C{None} if failed.
- """
-
- # Lookup all users
- return [i[0] for i in opendirectory.listResources(self.directory)]
-
- def listIndexAttributes(self):
- """
- List all the names currently in the directory with specific attributes needed for indexing.
-
- @return: C{list} containing C{tuple}'s of C{str}'s for each entry found, or C{None} if failed.
- The C{tuple} elements are: uid, guid, last-modified.
- """
- return opendirectory.listResources(self.directory)
-
- def listCommonAttributes(self, names):
- """
- List specified names currently in the directory returning useful attributes.
-
- @param names: C{list} of record entries to list attributes for.
- @return: C{dict} with keys for each entry found, and a C{dict} value containg the attributes,
- or C{None} on failure.
- """
- return opendirectory.listResourcesWithAttributes(self.directory, names)
-
- def validName(self, name):
- """
- Verify that the supplied name exists as an entry in the directory.
-
- @param name: C{str} of the namer to check
- @return: C{True} if the name exists, C{False} otherwise
- """
- return opendirectory.checkResource(self.directory, name)
-
- def directoryAttributes(self, name):
- """
- Return the attributes relevant to the directory entry.
-
- @param name: C{str} containing the name for the directory entry.
- @return: C{dict} containing the attribute key/value map.
- """
- result = opendirectory.listResourcesWithAttributes(self.directory, [name])
- if result:
- return result[name]
- else:
- return None
-
- def getTitle(self):
- return "Resource Principals"
-
-class DirectoryPrincipalProvisioningResource (DAVFile):
- """
- L{DAVFile} resource which provisions calendar principal resources as needed.
- """
- def __init__(self, path, url, params={}):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- """
- super(DirectoryPrincipalProvisioningResource, self).__init__(path)
-
- assert self.exists(), "%s should exist" % (self,)
- assert self.isCollection(), "%s should be a collection" % (self,)
-
- # Extract parameters
- if (params.has_key("DirectoryNode")):
- self.directory = opendirectory.odInit(params["DirectoryNode"])
- if self.directory is None:
- raise ValueError("Failed to open Open Directory Node: %s" % (params["DirectoryNode"],))
- else:
- raise ValueError("DirectoryPrincipalProvisioningResource must be configured with an Open Directory Node")
-
- # Create children
- for name, clazz in (
- ("users" , DirectoryUserPrincipalProvisioningResource),
- ("groups" , DirectoryGroupPrincipalProvisioningResource),
- ("resources" , DirectoryResourcePrincipalProvisioningResource),
- ):
- child_fp = self.fp.child(name)
- if not child_fp.exists(): child_fp.makedirs()
- principalCollection = clazz(child_fp.path, joinURL(url, name) + "/")
- principalCollection.setup(self.directory)
- self.putChild(name, principalCollection)
-
- def isCollection(self):
- """
- See L{IDAVResource.isCollection}.
- """
- return True
-
- def initialize(self, homeuri, home):
- """
- May be called during repository account initialization.
- This implementation does nothing.
-
- @param homeuri: C{str} uri of the calendar home root.
- @param home: L{DAVFile} of the calendar home root.
- """
- for name in ("users", "groups", "resources",):
- self.getChild(name).initialize(joinURL(homeuri, name), home.getChild(name))
-
- def createSimilarFile(self, path):
- return DAVFile(path)
-
- def render(self, request):
- return StatusResponse(
- responsecode.OK,
- "This collection contains principal resources",
- title=self.displayName()
- )
Copied: CalendarServer/trunk/twistedcaldav/directory/sqldb.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/sqldb.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/sqldb.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/sqldb.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,331 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+
+"""
+SQL (sqlite) based user/group/resource directory service implementation.
+"""
+
+"""
+SCHEMA:
+
+User Database:
+
+ROW: TYPE, UID (unique), PSWD, NAME, CANPROXY
+
+Group Database:
+
+ROW: GRPUID, UID
+
+CUAddress database:
+
+ROW: CUADDR (unqiue), UID
+
+"""
+
+__all__ = [
+ "SQLDirectoryService",
+]
+
+from twisted.cred.credentials import UsernamePassword
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+from twistedcaldav.sql import AbstractSQLDatabase
+
+import os
+
+class SQLDirectoryManager(AbstractSQLDatabase):
+ """
+ House keeping operations on the SQL DB, including loading from XML file,
+ and record dumping. This can be used as a standalong DB management tool.
+ """
+
+ DBTYPE = "DIRECTORYSERVICE"
+ DBNAME = ".db.accounts"
+ DBVERSION = "1"
+ ACCOUNTDB = "ACCOUNTS"
+ GROUPSDB = "GROUPS"
+ CUADDRDB = "CUADDRS"
+
+ def __init__(self, path):
+ path = os.path.join(path, SQLDirectoryManager.DBNAME)
+ super(SQLDirectoryManager, self).__init__(path, SQLDirectoryManager.DBVERSION)
+
+ def loadFromXML(self, xmlFile):
+ xmlAccounts = XMLAccountsParser(xmlFile)
+
+ # Totally wipe existing DB and start from scratch
+ if os.path.exists(self.dbpath):
+ os.remove(self.dbpath)
+
+ # Now add records to db
+ for item in xmlAccounts.items.itervalues():
+ self._add_to_db(item)
+ self._db_commit()
+
+ def listRecords(self, recordType):
+ # Get each account record
+ rowiter = self._db_execute("select UID, PSWD, NAME from ACCOUNTS where TYPE = :1", recordType)
+ for row in rowiter:
+ uid = row[0]
+ password = row[1]
+ name = row[2]
+ members = []
+ groups = []
+ calendarUserAddresses = []
+
+ # See if we have a group
+ if recordType == "group":
+ rowiter = self._db_execute("select UID from GROUPS where GRPUID = :1", uid)
+ for row in rowiter:
+ members.append(row[0])
+
+ # See if we are a member of a group
+ rowiter = self._db_execute("select GRPUID from GROUPS where UID = :1", uid)
+ for row in rowiter:
+ groups.append(row[0])
+
+ # Get calendar user addresses
+ rowiter = self._db_execute("select CUADDR from CUADDRS where UID = :1", uid)
+ for row in rowiter:
+ calendarUserAddresses.append(row[0])
+
+ yield uid, password, name, members, groups, calendarUserAddresses
+
+ def getRecord(self, recordType, uid):
+ # Get individual account record
+ rowiter = self._db_execute("select UID, PSWD, NAME from ACCOUNTS where TYPE = :1 and UID = :2", recordType, uid)
+ result = None
+ for row in rowiter:
+ if result:
+ result = None
+ break
+ result = row
+
+ if result is None:
+ return None
+
+ uid = result[0]
+ password = result[1]
+ name = result[2]
+ members = []
+ groups = []
+ calendarUserAddresses = []
+
+ # See if we have a group
+ if recordType == "group":
+ rowiter = self._db_execute("select UID from GROUPS where GRPUID = :1", uid)
+ for row in rowiter:
+ members.append(row[0])
+
+ # See if we are a member of a group
+ rowiter = self._db_execute("select GRPUID from GROUPS where UID = :1", uid)
+ for row in rowiter:
+ groups.append(row[0])
+
+ # Get calendar user addresses
+ rowiter = self._db_execute("select CUADDR from CUADDRS where UID = :1", uid)
+ for row in rowiter:
+ calendarUserAddresses.append(row[0])
+
+ return uid, password, name, members, groups, calendarUserAddresses
+
+ def _add_to_db(self, record):
+ # Do regular account entry
+ type = record.recordType
+ uid = record.uid
+ password = record.password
+ name = record.name
+ canproxy = ('F', 'T')[record.canproxy]
+ self._db_execute(
+ """
+ insert into ACCOUNTS (TYPE, UID, PSWD, NAME, CANPROXY)
+ values (:1, :2, :3, :4, :5)
+ """, type, uid, password, name, canproxy
+ )
+
+ # Check for group
+ if type == "group":
+ for member in record.members:
+ self._db_execute(
+ """
+ insert into GROUPS (GRPUID, UID)
+ values (:1, :2)
+ """, uid, member
+ )
+
+ # CUAddress
+ for cuaddr in record.calendarUserAddresses:
+ self._db_execute(
+ """
+ insert into CUADDRS (CUADDR, UID)
+ values (:1, :2)
+ """, cuaddr, uid
+ )
+
+ def _delete_from_db(self, uid):
+ """
+ Deletes the specified entry from all dbs.
+ @param name: the name of the resource to delete.
+ @param uid: the uid of the resource to delete.
+ """
+ self._db_execute("delete from ACCOUNTS where UID = :1", uid)
+ self._db_execute("delete from GROUPS where GRPUID = :1", uid)
+ self._db_execute("delete from GROUPS where UID = :1", uid)
+ self._db_execute("delete from CUADDRS where UID = :1", uid)
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return SQLDirectoryManager.DBTYPE
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+ #
+ # ACCOUNTS table
+ #
+ q.execute(
+ """
+ create table ACCOUNTS (
+ TYPE text,
+ UID text unique,
+ PSWD text,
+ NAME text,
+ CANPROXY text(1)
+ )
+ """
+ )
+
+ #
+ # GROUPS table
+ #
+ q.execute(
+ """
+ create table GROUPS (
+ GRPUID text,
+ UID text
+ )
+ """
+ )
+
+ #
+ # CUADDRS table
+ #
+ q.execute(
+ """
+ create table CUADDRS (
+ CUADDR text unique,
+ UID text
+ )
+ """
+ )
+
+class SQLDirectoryService(DirectoryService):
+ """
+ XML based implementation of L{IDirectoryService}.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, dbParentPath, xmlFile = None):
+ super(SQLDirectoryService, self).__init__()
+
+ if type(dbParentPath) is str:
+ dbParentPath = FilePath(dbParentPath)
+
+ self.manager = SQLDirectoryManager(dbParentPath.path)
+ if xmlFile:
+ self.manager.loadFromXML(xmlFile)
+
+ def recordTypes(self):
+ recordTypes = ("user", "group", "resource")
+ return recordTypes
+
+ def listRecords(self, recordType):
+ for result in self.manager.listRecords(recordType):
+ yield SQLDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ shortName = result[0],
+ password = result[1],
+ name = result[2],
+ members = result[3],
+ groups = result[4],
+ calendarUserAddresses = result[5],
+ )
+
+ def recordWithShortName(self, recordType, shortName):
+ result = self.manager.getRecord(recordType, shortName)
+ if result:
+ return SQLDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ shortName = result[0],
+ password = result[1],
+ name = result[2],
+ members = result[3],
+ groups = result[4],
+ calendarUserAddresses = result[5],
+ )
+
+ return None
+
+ def recordWithGUID(self, guid):
+ raise NotImplementedError()
+
+class SQLDirectoryRecord(DirectoryRecord):
+ """
+ XML based implementation implementation of L{IDirectoryRecord}.
+ """
+ def __init__(self, service, recordType, shortName, password, name, members, groups, calendarUserAddresses):
+ super(SQLDirectoryRecord, self).__init__(
+ service = service,
+ recordType = recordType,
+ guid = None,
+ shortName = shortName,
+ fullName = name,
+ calendarUserAddresses = calendarUserAddresses,
+ )
+
+ self.password = password
+ self._members = members
+ self._groups = groups
+
+ def members(self):
+ for shortName in self._members:
+ yield self.service.recordWithShortName("user", shortName)
+
+ def groups(self):
+ for shortName in self._groups:
+ yield self.service.recordWithShortName("group", shortName)
+
+ def verifyCredentials(self, credentials):
+ if isinstance(credentials, UsernamePassword):
+ return credentials.password == self.password
+
+ return super(SQLDirectoryRecord, self).verifyCredentials(credentials)
+
+if __name__ == '__main__':
+ mgr = SQLDirectoryManager("./")
+ mgr.loadFromXML("test/accounts.xml")
Copied: CalendarServer/trunk/twistedcaldav/directory/test (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test)
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/__init__.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/__init__.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,17 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
Copied: CalendarServer/trunk/twistedcaldav/directory/test/__init__.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/__init__.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/__init__.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/__init__.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,17 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,39 +0,0 @@
-<!--
-Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-DRI: Cyrus Daboo, cdaboo at apple.com
- -->
-
-<!ELEMENT accounts (user*, group*, resource*) >
-
- <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
- <!ATTLIST user repeat CDATA "1">
-
- <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
- <!ATTLIST group repeat CDATA "1">
-
- <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
- <!ATTLIST resource repeat CDATA "1">
-
- <!ELEMENT uid (#PCDATA)>
- <!ELEMENT pswd (#PCDATA)>
- <!ELEMENT name (#PCDATA)>
- <!ELEMENT cuaddr (#PCDATA)>
- <!ELEMENT calendar (#PCDATA)>
- <!ELEMENT quota (#PCDATA)>
- <!ELEMENT autorespond EMPTY>
- <!ELEMENT canproxy EMPTY>
- <!ELEMENT members (uid*)>
-
\ No newline at end of file
Copied: CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.dtd)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.dtd 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,39 @@
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+DRI: Cyrus Daboo, cdaboo at apple.com
+ -->
+
+<!ELEMENT accounts (user*, group*, resource*) >
+
+ <!ELEMENT user (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST user repeat CDATA "1">
+
+ <!ELEMENT group (uid, pswd, name, members, cuaddr*, calendar*, quota?)>
+ <!ATTLIST group repeat CDATA "1">
+
+ <!ELEMENT resource (uid, pswd, name, cuaddr*, calendar*, quota?, autorespond?, canproxy?)>
+ <!ATTLIST resource repeat CDATA "1">
+
+ <!ELEMENT uid (#PCDATA)>
+ <!ELEMENT pswd (#PCDATA)>
+ <!ELEMENT name (#PCDATA)>
+ <!ELEMENT cuaddr (#PCDATA)>
+ <!ELEMENT calendar (#PCDATA)>
+ <!ELEMENT quota (#PCDATA)>
+ <!ELEMENT autorespond EMPTY>
+ <!ELEMENT canproxy EMPTY>
+ <!ELEMENT members (uid*)>
+
\ No newline at end of file
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-
-<accounts realm="Test Realm">
- <user>
- <uid>admin</uid>
- <pswd>nimda</pswd>
- <name>Super User</name>
- </user>
- <user>
- <uid>proxy</uid>
- <pswd>yxorp</pswd>
- <name>User who can authorize as someone else</name>
- <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
- </user>
- <user>
- <uid>wsanchez</uid>
- <pswd>zehcnasw</pswd>
- <name>Wilfredo Sanchez</name>
- <cuaddr>mailto:wsanchez at example.com</cuaddr>
- </user>
- <user>
- <uid>cdaboo</uid>
- <pswd>oobadc</pswd>
- <name>Cyrus Daboo</name>
- <cuaddr>mailto:cdaboo at example.com</cuaddr>
- </user>
- <user>
- <uid>lecroy</uid>
- <pswd>yorcel</pswd>
- <name>Chris Lecroy</name>
- <cuaddr>mailto:lecroy at example.com</cuaddr>
- </user>
- <user>
- <uid>dreid</uid>
- <pswd>dierd</pswd>
- <name>David Reid</name>
- <cuaddr>mailto:dreid at example.com</cuaddr>
- </user>
- <user repeat="2">
- <uid>user%02d</uid>
- <pswd>%02duser</pswd>
- <name>User %02d</name>
- </user>
- <group>
- <uid>managers</uid>
- <pswd>managers</pswd>
- <name>Managers</name>
- <members>
- <uid>lecroy</uid>
- </members>
- </group>
- <group>
- <uid>grunts</uid>
- <pswd>grunts</pswd>
- <name>We do all the work</name>
- <members>
- <uid>wsanchez</uid>
- <uid>cdaboo</uid>
- <uid>dreid</uid>
- </members>
- </group>
- <group>
- <uid>right_coast</uid>
- <pswd>right_coast</pswd>
- <name>East Coast</name>
- <members>
- <uid>cdaboo</uid>
- </members>
- </group>
- <group>
- <uid>left_coast</uid>
- <pswd>left_coast</pswd>
- <name>West Coast</name>
- <members>
- <uid>wsanchez</uid>
- <uid>lecroy</uid>
- <uid>dreid</uid>
- </members>
- </group>
- <resource>
- <uid>mercury</uid>
- <pswd>mercury</pswd>
- <name>Mecury Seven</name>
- <cuaddr>mailto:mercury at example.com</cuaddr>
- </resource>
- <resource>
- <uid>gemini</uid>
- <pswd>gemini</pswd>
- <name>Gemini Twelve</name>
- <cuaddr>mailto:gemini at example.com</cuaddr>
- </resource>
- <resource>
- <uid>apollo</uid>
- <pswd>apollo</pswd>
- <name>Apollo Eleven</name>
- <cuaddr>mailto:apollo at example.com</cuaddr>
- </resource>
-</accounts>
Copied: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/accounts.xml)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+ <user>
+ <uid>admin</uid>
+ <pswd>nimda</pswd>
+ <name>Super User</name>
+ </user>
+ <user>
+ <uid>proxy</uid>
+ <pswd>yxorp</pswd>
+ <name>User who can authorize as someone else</name>
+ <canproxy/> <!-- FIXME: Is the directory the right place to configure this bit? -->
+ </user>
+ <user>
+ <uid>wsanchez</uid>
+ <pswd>zehcnasw</pswd>
+ <name>Wilfredo Sanchez</name>
+ <cuaddr>mailto:wsanchez at example.com</cuaddr>
+ </user>
+ <user>
+ <uid>cdaboo</uid>
+ <pswd>oobadc</pswd>
+ <name>Cyrus Daboo</name>
+ <cuaddr>mailto:cdaboo at example.com</cuaddr>
+ </user>
+ <user>
+ <uid>lecroy</uid>
+ <pswd>yorcel</pswd>
+ <name>Chris Lecroy</name>
+ <cuaddr>mailto:lecroy at example.com</cuaddr>
+ </user>
+ <user>
+ <uid>dreid</uid>
+ <pswd>dierd</pswd>
+ <name>David Reid</name>
+ <cuaddr>mailto:dreid at example.com</cuaddr>
+ </user>
+ <user repeat="2">
+ <uid>user%02d</uid>
+ <pswd>%02duser</pswd>
+ <name>User %02d</name>
+ </user>
+ <group>
+ <uid>managers</uid>
+ <pswd>managers</pswd>
+ <name>Managers</name>
+ <members>
+ <uid>lecroy</uid>
+ </members>
+ </group>
+ <group>
+ <uid>grunts</uid>
+ <pswd>grunts</pswd>
+ <name>We do all the work</name>
+ <members>
+ <uid>wsanchez</uid>
+ <uid>cdaboo</uid>
+ <uid>dreid</uid>
+ </members>
+ </group>
+ <group>
+ <uid>right_coast</uid>
+ <pswd>right_coast</pswd>
+ <name>East Coast</name>
+ <members>
+ <uid>cdaboo</uid>
+ </members>
+ </group>
+ <group>
+ <uid>left_coast</uid>
+ <pswd>left_coast</pswd>
+ <name>West Coast</name>
+ <members>
+ <uid>wsanchez</uid>
+ <uid>lecroy</uid>
+ <uid>dreid</uid>
+ </members>
+ </group>
+ <resource>
+ <uid>mercury</uid>
+ <pswd>mercury</pswd>
+ <name>Mecury Seven</name>
+ <cuaddr>mailto:mercury at example.com</cuaddr>
+ </resource>
+ <resource>
+ <uid>gemini</uid>
+ <pswd>gemini</pswd>
+ <name>Gemini Twelve</name>
+ <cuaddr>mailto:gemini at example.com</cuaddr>
+ </resource>
+ <resource>
+ <uid>apollo</uid>
+ <pswd>apollo</pswd>
+ <name>Apollo Eleven</name>
+ <cuaddr>mailto:apollo at example.com</cuaddr>
+ </resource>
+</accounts>
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/basic
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/basic 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-wsanchez:Cytm0Bwm7CPJs
-cdaboo:I.Ef5FJl5GVh2
-dreid:LVhqAv4qSrYPs
-lecroy:/7/5VDrkrLxY.
Copied: CalendarServer/trunk/twistedcaldav/directory/test/basic (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/basic)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/basic (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/basic 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+wsanchez:Cytm0Bwm7CPJs
+cdaboo:I.Ef5FJl5GVh2
+dreid:LVhqAv4qSrYPs
+lecroy:/7/5VDrkrLxY.
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/digest
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/digest 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
-cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
-dreid:Test:8ee67801004b2752f72b84e7064889a6
-lecroy:Test:60d4feb424430953be045738041e51be
Copied: CalendarServer/trunk/twistedcaldav/directory/test/digest (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/digest)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/digest (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/digest 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c
+cdaboo:Test:61164bf3d607d072fe8a7ac420b24aac
+dreid:Test:8ee67801004b2752f72b84e7064889a6
+lecroy:Test:60d4feb424430953be045738041e51be
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/groups
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/groups 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,4 +0,0 @@
-managers: lecroy
-grunts: wsanchez, cdaboo, dreid
-right_coast: cdaboo
-left_coast: wsanchez, dreid, lecroy
Copied: CalendarServer/trunk/twistedcaldav/directory/test/groups (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/groups)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/groups (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/groups 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,4 @@
+managers: lecroy
+grunts: wsanchez, cdaboo, dreid
+right_coast: cdaboo
+left_coast: wsanchez, dreid, lecroy
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,106 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-
-digestRealm = "Test"
-
-basicUserFile = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
-digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
-groupFile = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class Apache (object):
- recordTypes = set(("user", "group"))
-
- users = {
- "wsanchez": "foo",
- "cdaboo" : "bar",
- "dreid" : "baz",
- "lecroy" : "quux",
- }
-
- groups = {
- "managers" : ("lecroy",),
- "grunts" : ("wsanchez", "cdaboo", "dreid"),
- "right_coast": ("cdaboo",),
- "left_coast" : ("wsanchez", "dreid", "lecroy"),
- }
-
- serviceClass = BasicDirectoryService
-
- def service(self):
- return self.serviceClass(self.userFile(), self.groupFile())
-
- userFileName = None
-
- def userFile(self):
- if not hasattr(self, "_userFile"):
- if self.userFileName is None:
- raise NotImplementedError("Test subclass needs to specify userFileName.")
- self._userFile = FilePath(self.mktemp())
- basicUserFile.copyTo(self._userFile)
- return self._userFile
-
- def groupFile(self):
- if not hasattr(self, "_groupFile"):
- self._groupFile = FilePath(self.mktemp())
- groupFile.copyTo(self._groupFile)
- return self._groupFile
-
- def test_changedGroupFile(self):
- self.groupFile().open("w").write("grunts: wsanchez\n")
- self.assertEquals(self.recordNames("group"), set(("grunts",)))
-
- def test_recordTypes_user(self):
- """
- IDirectoryService.recordTypes(userFile)
- """
- self.assertEquals(set(self.serviceClass(self.userFile()).recordTypes()), set(("user",)))
-
- userEntry = None
-
- def test_changedUserFile(self):
- if self.userEntry is None:
- raise NotImplementedError("Test subclass needs to specify userEntry.")
- self.userFile().open("w").write(self.userEntry[1])
- self.assertEquals(self.recordNames("user"), set((self.userEntry[0],)))
-
-class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase):
- """
- Test Apache-Compatible UserFile/GroupFile directory implementation.
- """
- userFileName = basicUserFile
- userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
-
-class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase):
- """
- Test Apache-Compatible DigestFile/GroupFile directory implementation.
- """
- userFileName = digestUserFile
- userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
-
- def test_verifyCredentials_digest(self):
- raise NotImplementedError() # Use super's implementation
- test_verifyCredentials_digest.todo = "unimplemented"
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_apache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_apache.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,106 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+
+digestRealm = "Test"
+
+basicUserFile = FilePath(os.path.join(os.path.dirname(__file__), "basic"))
+digestUserFile = FilePath(os.path.join(os.path.dirname(__file__), "digest"))
+groupFile = FilePath(os.path.join(os.path.dirname(__file__), "groups"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class Apache (object):
+ recordTypes = set(("user", "group"))
+
+ users = {
+ "wsanchez": "foo",
+ "cdaboo" : "bar",
+ "dreid" : "baz",
+ "lecroy" : "quux",
+ }
+
+ groups = {
+ "managers" : ("lecroy",),
+ "grunts" : ("wsanchez", "cdaboo", "dreid"),
+ "right_coast": ("cdaboo",),
+ "left_coast" : ("wsanchez", "dreid", "lecroy"),
+ }
+
+ serviceClass = BasicDirectoryService
+
+ def service(self):
+ return self.serviceClass(self.userFile(), self.groupFile())
+
+ userFileName = None
+
+ def userFile(self):
+ if not hasattr(self, "_userFile"):
+ if self.userFileName is None:
+ raise NotImplementedError("Test subclass needs to specify userFileName.")
+ self._userFile = FilePath(self.mktemp())
+ basicUserFile.copyTo(self._userFile)
+ return self._userFile
+
+ def groupFile(self):
+ if not hasattr(self, "_groupFile"):
+ self._groupFile = FilePath(self.mktemp())
+ groupFile.copyTo(self._groupFile)
+ return self._groupFile
+
+ def test_changedGroupFile(self):
+ self.groupFile().open("w").write("grunts: wsanchez\n")
+ self.assertEquals(self.recordNames("group"), set(("grunts",)))
+
+ def test_recordTypes_user(self):
+ """
+ IDirectoryService.recordTypes(userFile)
+ """
+ self.assertEquals(set(self.serviceClass(self.userFile()).recordTypes()), set(("user",)))
+
+ userEntry = None
+
+ def test_changedUserFile(self):
+ if self.userEntry is None:
+ raise NotImplementedError("Test subclass needs to specify userEntry.")
+ self.userFile().open("w").write(self.userEntry[1])
+ self.assertEquals(self.recordNames("user"), set((self.userEntry[0],)))
+
+class Basic (Apache, twistedcaldav.directory.test.util.BasicTestCase):
+ """
+ Test Apache-Compatible UserFile/GroupFile directory implementation.
+ """
+ userFileName = basicUserFile
+ userEntry = ("wsanchez", "wsanchez:Cytm0Bwm7CPJs\n")
+
+class Digest (Apache, twistedcaldav.directory.test.util.DigestTestCase):
+ """
+ Test Apache-Compatible DigestFile/GroupFile directory implementation.
+ """
+ userFileName = digestUserFile
+ userEntry = ("wsanchez", "wsanchez:Test:decbe233ab3d997cacc2fc058b19db8c\n")
+
+ def test_verifyCredentials_digest(self):
+ raise NotImplementedError() # Use super's implementation
+ test_verifyCredentials_digest.todo = "unimplemented"
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_opendirectory.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,44 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
-
-try:
- from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-except ImportError:
- pass
-else:
- import twistedcaldav.directory.test.util
-
- # Wonky hack to prevent unclean reactor shutdowns
- class DummyReactor(object):
- @staticmethod
- def callLater(*args):
- pass
- import twistedcaldav.directory.appleopendirectory
- twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
-
- class OpenDirectory (
- twistedcaldav.directory.test.util.BasicTestCase,
- twistedcaldav.directory.test.util.DigestTestCase
- ):
- """
- Test Open Directory directory implementation.
- """
- recordTypes = set(("user", "group", "resource"))
-
- def service(self):
- return OpenDirectoryService(node="/Local")
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_opendirectory.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+try:
+ from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
+except ImportError:
+ pass
+else:
+ import twistedcaldav.directory.test.util
+
+ # Wonky hack to prevent unclean reactor shutdowns
+ class DummyReactor(object):
+ @staticmethod
+ def callLater(*args):
+ pass
+ import twistedcaldav.directory.appleopendirectory
+ twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
+
+ class OpenDirectory (
+ twistedcaldav.directory.test.util.BasicTestCase,
+ twistedcaldav.directory.test.util.DigestTestCase
+ ):
+ """
+ Test Open Directory directory implementation.
+ """
+ recordTypes = set(("user", "group", "resource"))
+
+ def service(self):
+ return OpenDirectoryService(node="/Local")
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_principal.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,326 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
-
-#from twisted.web2 import responsecode
-#from twisted.web2.iweb import IResponse
-#from twisted.web2.dav import davxml
-#from twisted.web2.dav.util import davXMLFromStream
-#from twisted.web2.test.test_server import SimpleRequest
-#from twistedcaldav import caldavxml
-
-import os
-
-from twisted.internet.defer import deferredGenerator, waitForDeferred
-from twisted.web2.dav import davxml
-from twisted.web2.dav.fileop import rmdir
-from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.test.test_server import SimpleRequest
-from twisted.web2.dav.test.util import serialize
-
-from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
-from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.principal import DirectoryPrincipalTypeResource
-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-
-import twistedcaldav.test.util
-
-directoryServices = (
- BasicDirectoryService(basicUserFile, groupFile),
- DigestDirectoryService(digestUserFile, groupFile),
- XMLDirectoryService(xmlFile),
-)
-
-class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
- """
- Directory service provisioned principals.
- """
- def setUp(self):
- super(ProvisionedPrincipals, self).setUp()
-
- # Set up a principals hierarchy for each service we're testing with
- self.principalRootResources = {}
- for directory in directoryServices:
- name = directory.__class__.__name__
- url = "/" + name + "/"
- path = os.path.join(self.docroot, url[1:])
-
- if os.path.exists(path):
- rmdir(path)
- os.mkdir(path)
-
- provisioningResource = DirectoryPrincipalProvisioningResource(path, url, directory)
-
- self.site.resource.putChild(name, provisioningResource)
-
- self.principalRootResources[directory.__class__.__name__] = provisioningResource
-
- def test_hierarchy(self):
- """
- DirectoryPrincipalProvisioningResource.listChildren(),
- DirectoryPrincipalProvisioningResource.getChildren(),
- DirectoryPrincipalProvisioningResource.principalCollectionURL(),
- DirectoryPrincipalProvisioningResource.principalCollections()
-
- DirectoryPrincipalTypeResource.listChildren(),
- DirectoryPrincipalTypeResource.getChildren(),
- DirectoryPrincipalTypeResource.principalCollectionURL(),
- DirectoryPrincipalTypeResource.principalCollections()
-
- DirectoryPrincipalResource.principalURL(),
- """
- for directory in directoryServices:
- #print "\n -> %s" % (directory.__class__.__name__,)
- provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
- provisioningURL = "/" + directory.__class__.__name__ + "/"
- self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
-
- principalCollections = provisioningResource.principalCollections()
- self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
- recordTypes = set(provisioningResource.listChildren())
- self.assertEquals(recordTypes, set(directory.recordTypes()))
-
- for recordType in recordTypes:
- #print " -> %s" % (recordType,)
- typeResource = provisioningResource.getChild(recordType)
- self.failUnless(isinstance(typeResource, DirectoryPrincipalTypeResource))
-
- typeURL = provisioningURL + recordType + "/"
- self.assertEquals(typeURL, typeResource.principalCollectionURL())
-
- principalCollections = typeResource.principalCollections()
- self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
- shortNames = set(typeResource.listChildren())
- self.assertEquals(shortNames, set(r.shortName for r in directory.listRecords(recordType)))
-
- for shortName in shortNames:
- #print " -> %s" % (shortName,)
- recordResource = typeResource.getChild(shortName)
- self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
-
- recordURL = typeURL + shortName
- self.assertEquals(recordURL, recordResource.principalURL())
-
- principalCollections = recordResource.principalCollections()
- self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
-
- def test_principalForUser(self):
- """
- DirectoryPrincipalProvisioningResource.principalForUser()
- """
- for directory in directoryServices:
- provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
- for user in directory.listRecords("user"):
- userResource = provisioningResource.principalForUser(user.shortName)
- self.failIf(userResource is None)
- self.assertEquals(user, userResource.record)
-
- def test_principalForRecord(self):
- """
- DirectoryPrincipalProvisioningResource.principalForRecord()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.assertEquals(recordResource.record, record)
-
- def test_displayName(self):
- """
- DirectoryPrincipalResource.displayName()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.failUnless(recordResource.displayName())
-
- def test_groupMembers(self):
- """
- DirectoryPrincipalResource.groupMembers()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.failUnless(set(record.members()).issubset(set(r.record for r in recordResource.groupMembers())))
-
- def test_groupMemberships(self):
- """
- DirectoryPrincipalResource.groupMemberships()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.failUnless(set(record.groups()).issubset(set(r.record for r in recordResource.groupMemberships())))
-
- def test_principalUID(self):
- """
- DirectoryPrincipalResource.principalUID()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.assertEquals(record.shortName, recordResource.principalUID())
-
- def test_calendarUserAddresses(self):
- """
- DirectoryPrincipalResource.calendarUserAddresses()
- """
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.failUnless(
- (
- set((recordResource.principalURL(),)) |
- set(record.calendarUserAddresses)
- ).issubset(set(recordResource.calendarUserAddresses()))
- )
-
- def test_calendarHomeURLs(self):
- """
- DirectoryPrincipalResource.calendarHomeURLs(),
- DirectoryPrincipalResource.scheduleInboxURL(),
- DirectoryPrincipalResource.scheduleOutboxURL()
- """
- # No calendar home provisioner should result in no calendar homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- self.failIf(tuple(recordResource.calendarHomeURLs()))
- self.failIf(recordResource.scheduleInboxURL())
- self.failIf(recordResource.scheduleOutboxURL())
-
- # Need to create a calendar home provisioner for each service.
- calendarRootResources = {}
-
- for directory in directoryServices:
- url = "/homes_" + directory.__class__.__name__ + "/"
- path = os.path.join(self.docroot, url[1:])
-
- if os.path.exists(path):
- rmdir(path)
- os.mkdir(path)
-
- provisioningResource = CalendarHomeProvisioningFile(path, directory, url)
-
- calendarRootResources[directory.__class__.__name__] = provisioningResource
-
- # Calendar home provisioners should result in calendar homes.
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- homeURLs = tuple(recordResource.calendarHomeURLs())
- self.failUnless(homeURLs)
-
- calendarRootURL = calendarRootResources[record.service.__class__.__name__].url()
-
- inboxURL = recordResource.scheduleInboxURL()
- outboxURL = recordResource.scheduleOutboxURL()
-
- self.failUnless(inboxURL)
- self.failUnless(outboxURL)
-
- for homeURL in homeURLs:
- self.failUnless(homeURL.startswith(calendarRootURL))
-
- if inboxURL and inboxURL.startswith(homeURL):
- self.failUnless(len(inboxURL) > len(homeURL))
- self.failUnless(inboxURL.endswith("/"))
- inboxURL = None
-
- if outboxURL and outboxURL.startswith(homeURL):
- self.failUnless(len(outboxURL) > len(homeURL))
- self.failUnless(outboxURL.endswith("/"))
- outboxURL = None
-
- self.failIf(inboxURL)
- self.failIf(outboxURL)
-
- def test_defaultAccessControlList_principals(self):
- """
- Default access controls for principals.
- """
- def work():
- for provisioningResource, recordType, recordResource, record in self._allRecords():
- for args in _authReadOnlyPrivileges(recordResource, recordResource.principalURL()):
- yield args
-
- return serialize(self._checkPrivileges, work())
-
- def test_defaultAccessControlList_provisioners(self):
- """
- Default access controls for principal provisioning resources.
- """
- def work():
- for directory in directoryServices:
- #print "\n -> %s" % (directory.__class__.__name__,)
- provisioningResource = self.principalRootResources[directory.__class__.__name__]
-
- for args in _authReadOnlyPrivileges(provisioningResource, provisioningResource.principalCollectionURL()):
- yield args
-
- for recordType in provisioningResource.listChildren():
- #print " -> %s" % (recordType,)
- typeResource = provisioningResource.getChild(recordType)
-
- for args in _authReadOnlyPrivileges(typeResource, typeResource.principalCollectionURL()):
- yield args
-
- return serialize(self._checkPrivileges, work())
-
- def _allRecords(self):
- """
- @return: an iterable of tuples
- C{(provisioningResource, recordType, recordResource, record)}, where
- C{provisioningResource} is the root provisioning resource,
- C{recordType} is the record type,
- C{recordResource} is the principal resource and
- C{record} is the directory service record
- for each record in each directory in C{directoryServices}.
- """
- for directory in directoryServices:
- provisioningResource = self.principalRootResources[directory.__class__.__name__]
- for recordType in directory.recordTypes():
- for record in directory.listRecords(recordType):
- recordResource = provisioningResource.principalForRecord(record)
- yield provisioningResource, recordType, recordResource, record
-
- def _checkPrivileges(self, resource, url, principal, privilege, allowed):
- request = SimpleRequest(self.site, "GET", "/")
-
- def gotResource(resource):
- d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
- if allowed:
- def onError(f):
- f.trap(AccessDeniedError)
- #print resource.readDeadProperty(davxml.ACL)
- self.fail("%s should have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
- d.addErrback(onError)
- else:
- def onError(f):
- f.trap(AccessDeniedError)
- def onSuccess(_):
- #print resource.readDeadProperty(davxml.ACL)
- self.fail("%s should not have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
- d.addCallback(onSuccess)
- d.addErrback(onError)
- return d
-
- d = request.locateResource(url)
- d.addCallback(gotResource)
- return d
-
-def _authReadOnlyPrivileges(resource, url):
- for principal, privilege, allowed in (
- ( davxml.All() , davxml.Read() , False ),
- ( davxml.All() , davxml.Write() , False ),
- ( davxml.Unauthenticated() , davxml.Read() , False ),
- ( davxml.Unauthenticated() , davxml.Write() , False ),
- ( davxml.Authenticated() , davxml.Read() , True ),
- ( davxml.Authenticated() , davxml.Write() , False ),
- ):
- yield resource, url, principal, privilege, allowed
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_principal.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,326 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+#from twisted.web2 import responsecode
+#from twisted.web2.iweb import IResponse
+#from twisted.web2.dav import davxml
+#from twisted.web2.dav.util import davXMLFromStream
+#from twisted.web2.test.test_server import SimpleRequest
+#from twistedcaldav import caldavxml
+
+import os
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.web2.dav import davxml
+from twisted.web2.dav.fileop import rmdir
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.test.test_server import SimpleRequest
+from twisted.web2.dav.test.util import serialize
+
+from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.directory.apache import BasicDirectoryService, DigestDirectoryService
+from twistedcaldav.directory.test.test_apache import basicUserFile, digestUserFile, groupFile
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from twistedcaldav.directory.test.test_xmlfile import xmlFile
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.directory.principal import DirectoryPrincipalTypeResource
+from twistedcaldav.directory.principal import DirectoryPrincipalResource
+
+import twistedcaldav.test.util
+
+directoryServices = (
+ BasicDirectoryService(basicUserFile, groupFile),
+ DigestDirectoryService(digestUserFile, groupFile),
+ XMLDirectoryService(xmlFile),
+)
+
+class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
+ """
+ Directory service provisioned principals.
+ """
+ def setUp(self):
+ super(ProvisionedPrincipals, self).setUp()
+
+ # Set up a principals hierarchy for each service we're testing with
+ self.principalRootResources = {}
+ for directory in directoryServices:
+ name = directory.__class__.__name__
+ url = "/" + name + "/"
+ path = os.path.join(self.docroot, url[1:])
+
+ if os.path.exists(path):
+ rmdir(path)
+ os.mkdir(path)
+
+ provisioningResource = DirectoryPrincipalProvisioningResource(path, url, directory)
+
+ self.site.resource.putChild(name, provisioningResource)
+
+ self.principalRootResources[directory.__class__.__name__] = provisioningResource
+
+ def test_hierarchy(self):
+ """
+ DirectoryPrincipalProvisioningResource.listChildren(),
+ DirectoryPrincipalProvisioningResource.getChildren(),
+ DirectoryPrincipalProvisioningResource.principalCollectionURL(),
+ DirectoryPrincipalProvisioningResource.principalCollections()
+
+ DirectoryPrincipalTypeResource.listChildren(),
+ DirectoryPrincipalTypeResource.getChildren(),
+ DirectoryPrincipalTypeResource.principalCollectionURL(),
+ DirectoryPrincipalTypeResource.principalCollections()
+
+ DirectoryPrincipalResource.principalURL(),
+ """
+ for directory in directoryServices:
+ #print "\n -> %s" % (directory.__class__.__name__,)
+ provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+ provisioningURL = "/" + directory.__class__.__name__ + "/"
+ self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
+
+ principalCollections = provisioningResource.principalCollections()
+ self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+ recordTypes = set(provisioningResource.listChildren())
+ self.assertEquals(recordTypes, set(directory.recordTypes()))
+
+ for recordType in recordTypes:
+ #print " -> %s" % (recordType,)
+ typeResource = provisioningResource.getChild(recordType)
+ self.failUnless(isinstance(typeResource, DirectoryPrincipalTypeResource))
+
+ typeURL = provisioningURL + recordType + "/"
+ self.assertEquals(typeURL, typeResource.principalCollectionURL())
+
+ principalCollections = typeResource.principalCollections()
+ self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+ shortNames = set(typeResource.listChildren())
+ self.assertEquals(shortNames, set(r.shortName for r in directory.listRecords(recordType)))
+
+ for shortName in shortNames:
+ #print " -> %s" % (shortName,)
+ recordResource = typeResource.getChild(shortName)
+ self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
+
+ recordURL = typeURL + shortName
+ self.assertEquals(recordURL, recordResource.principalURL())
+
+ principalCollections = recordResource.principalCollections()
+ self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
+
+ def test_principalForUser(self):
+ """
+ DirectoryPrincipalProvisioningResource.principalForUser()
+ """
+ for directory in directoryServices:
+ provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+ for user in directory.listRecords("user"):
+ userResource = provisioningResource.principalForUser(user.shortName)
+ self.failIf(userResource is None)
+ self.assertEquals(user, userResource.record)
+
+ def test_principalForRecord(self):
+ """
+ DirectoryPrincipalProvisioningResource.principalForRecord()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.assertEquals(recordResource.record, record)
+
+ def test_displayName(self):
+ """
+ DirectoryPrincipalResource.displayName()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.failUnless(recordResource.displayName())
+
+ def test_groupMembers(self):
+ """
+ DirectoryPrincipalResource.groupMembers()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.failUnless(set(record.members()).issubset(set(r.record for r in recordResource.groupMembers())))
+
+ def test_groupMemberships(self):
+ """
+ DirectoryPrincipalResource.groupMemberships()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.failUnless(set(record.groups()).issubset(set(r.record for r in recordResource.groupMemberships())))
+
+ def test_principalUID(self):
+ """
+ DirectoryPrincipalResource.principalUID()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.assertEquals(record.shortName, recordResource.principalUID())
+
+ def test_calendarUserAddresses(self):
+ """
+ DirectoryPrincipalResource.calendarUserAddresses()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.failUnless(
+ (
+ set((recordResource.principalURL(),)) |
+ set(record.calendarUserAddresses)
+ ).issubset(set(recordResource.calendarUserAddresses()))
+ )
+
+ def test_calendarHomeURLs(self):
+ """
+ DirectoryPrincipalResource.calendarHomeURLs(),
+ DirectoryPrincipalResource.scheduleInboxURL(),
+ DirectoryPrincipalResource.scheduleOutboxURL()
+ """
+ # No calendar home provisioner should result in no calendar homes.
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ self.failIf(tuple(recordResource.calendarHomeURLs()))
+ self.failIf(recordResource.scheduleInboxURL())
+ self.failIf(recordResource.scheduleOutboxURL())
+
+ # Need to create a calendar home provisioner for each service.
+ calendarRootResources = {}
+
+ for directory in directoryServices:
+ url = "/homes_" + directory.__class__.__name__ + "/"
+ path = os.path.join(self.docroot, url[1:])
+
+ if os.path.exists(path):
+ rmdir(path)
+ os.mkdir(path)
+
+ provisioningResource = CalendarHomeProvisioningFile(path, directory, url)
+
+ calendarRootResources[directory.__class__.__name__] = provisioningResource
+
+ # Calendar home provisioners should result in calendar homes.
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ homeURLs = tuple(recordResource.calendarHomeURLs())
+ self.failUnless(homeURLs)
+
+ calendarRootURL = calendarRootResources[record.service.__class__.__name__].url()
+
+ inboxURL = recordResource.scheduleInboxURL()
+ outboxURL = recordResource.scheduleOutboxURL()
+
+ self.failUnless(inboxURL)
+ self.failUnless(outboxURL)
+
+ for homeURL in homeURLs:
+ self.failUnless(homeURL.startswith(calendarRootURL))
+
+ if inboxURL and inboxURL.startswith(homeURL):
+ self.failUnless(len(inboxURL) > len(homeURL))
+ self.failUnless(inboxURL.endswith("/"))
+ inboxURL = None
+
+ if outboxURL and outboxURL.startswith(homeURL):
+ self.failUnless(len(outboxURL) > len(homeURL))
+ self.failUnless(outboxURL.endswith("/"))
+ outboxURL = None
+
+ self.failIf(inboxURL)
+ self.failIf(outboxURL)
+
+ def test_defaultAccessControlList_principals(self):
+ """
+ Default access controls for principals.
+ """
+ def work():
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ for args in _authReadOnlyPrivileges(recordResource, recordResource.principalURL()):
+ yield args
+
+ return serialize(self._checkPrivileges, work())
+
+ def test_defaultAccessControlList_provisioners(self):
+ """
+ Default access controls for principal provisioning resources.
+ """
+ def work():
+ for directory in directoryServices:
+ #print "\n -> %s" % (directory.__class__.__name__,)
+ provisioningResource = self.principalRootResources[directory.__class__.__name__]
+
+ for args in _authReadOnlyPrivileges(provisioningResource, provisioningResource.principalCollectionURL()):
+ yield args
+
+ for recordType in provisioningResource.listChildren():
+ #print " -> %s" % (recordType,)
+ typeResource = provisioningResource.getChild(recordType)
+
+ for args in _authReadOnlyPrivileges(typeResource, typeResource.principalCollectionURL()):
+ yield args
+
+ return serialize(self._checkPrivileges, work())
+
+ def _allRecords(self):
+ """
+ @return: an iterable of tuples
+ C{(provisioningResource, recordType, recordResource, record)}, where
+ C{provisioningResource} is the root provisioning resource,
+ C{recordType} is the record type,
+ C{recordResource} is the principal resource and
+ C{record} is the directory service record
+ for each record in each directory in C{directoryServices}.
+ """
+ for directory in directoryServices:
+ provisioningResource = self.principalRootResources[directory.__class__.__name__]
+ for recordType in directory.recordTypes():
+ for record in directory.listRecords(recordType):
+ recordResource = provisioningResource.principalForRecord(record)
+ yield provisioningResource, recordType, recordResource, record
+
+ def _checkPrivileges(self, resource, url, principal, privilege, allowed):
+ request = SimpleRequest(self.site, "GET", "/")
+
+ def gotResource(resource):
+ d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
+ if allowed:
+ def onError(f):
+ f.trap(AccessDeniedError)
+ #print resource.readDeadProperty(davxml.ACL)
+ self.fail("%s should have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
+ d.addErrback(onError)
+ else:
+ def onError(f):
+ f.trap(AccessDeniedError)
+ def onSuccess(_):
+ #print resource.readDeadProperty(davxml.ACL)
+ self.fail("%s should not have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
+ d.addCallback(onSuccess)
+ d.addErrback(onError)
+ return d
+
+ d = request.locateResource(url)
+ d.addCallback(gotResource)
+ return d
+
+def _authReadOnlyPrivileges(resource, url):
+ for principal, privilege, allowed in (
+ ( davxml.All() , davxml.Read() , False ),
+ ( davxml.All() , davxml.Write() , False ),
+ ( davxml.Unauthenticated() , davxml.Read() , False ),
+ ( davxml.Unauthenticated() , davxml.Write() , False ),
+ ( davxml.Authenticated() , davxml.Read() , True ),
+ ( davxml.Authenticated() , davxml.Write() , False ),
+ ):
+ yield resource, url, principal, privilege, allowed
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,44 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Cyrus Daboo, cdaboo at apple.com
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-import twistedcaldav.directory.test.test_xmlfile
-from twistedcaldav.directory.sqldb import SQLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class SQLDB (
- twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
- twistedcaldav.directory.test.util.BasicTestCase,
- twistedcaldav.directory.test.util.DigestTestCase
-):
- """
- Test SQL directory implementation.
- """
- def service(self):
- return SQLDirectoryService(os.getcwd(), self.xmlFile())
-
- def test_verifyCredentials_digest(self):
- raise NotImplementedError("Use super's implementation")
- test_verifyCredentials_digest.todo = "FIXME: SQLDirectoryService.realmName is None"
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_sqldb.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_sqldb.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+import twistedcaldav.directory.test.test_xmlfile
+from twistedcaldav.directory.sqldb import SQLDirectoryService
+
+xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class SQLDB (
+ twistedcaldav.directory.test.test_xmlfile.XMLFileBase,
+ twistedcaldav.directory.test.util.BasicTestCase,
+ twistedcaldav.directory.test.util.DigestTestCase
+):
+ """
+ Test SQL directory implementation.
+ """
+ def service(self):
+ return SQLDirectoryService(os.getcwd(), self.xmlFile())
+
+ def test_verifyCredentials_digest(self):
+ raise NotImplementedError("Use super's implementation")
+ test_verifyCredentials_digest.todo = "FIXME: SQLDirectoryService.realmName is None"
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,95 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
-
-import os
-
-from twisted.python.filepath import FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-
-xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class XMLFileBase(object):
- recordTypes = set(("user", "group", "resource"))
-
- users = {
- "admin" : "nimda",
- "proxy" : "yxorp",
- "wsanchez": "zehcnasw",
- "cdaboo" : "oobadc",
- "lecroy" : "yorcel",
- "dreid" : "dierd",
- "user01" : "01user",
- "user02" : "02user",
- }
-
- groups = {
- "managers" : ("lecroy",),
- "grunts" : ("wsanchez", "cdaboo", "dreid"),
- "right_coast": ("cdaboo",),
- "left_coast" : ("wsanchez", "dreid", "lecroy"),
- }
-
- resources = set((
- "mercury",
- "gemini",
- "apollo",
- ))
-
- def xmlFile(self):
- if not hasattr(self, "_xmlFile"):
- self._xmlFile = FilePath(self.mktemp())
- xmlFile.copyTo(self._xmlFile)
- return self._xmlFile
-
-class XMLFile (
- XMLFileBase,
- twistedcaldav.directory.test.util.BasicTestCase,
- twistedcaldav.directory.test.util.DigestTestCase
-):
- """
- Test XML file based directory implementation.
- """
- def service(self):
- return XMLDirectoryService(self.xmlFile())
-
- def test_changedXML(self):
- self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts>
- <user>
- <uid>admin</uid>
- <pswd>nimda</pswd>
- <name>Super User</name>
- </user>
-</accounts>
-"""
- )
- for recordType, expectedRecords in (
- ( "user" , ("admin",) ),
- ( "group" , () ),
- ( "resource" , () ),
- ):
- self.assertEquals(
- set(r.shortName for r in self.service().listRecords(recordType)),
- set(expectedRecords)
- )
Copied: CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/test_xmlfile.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_xmlfile.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,95 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+import os
+
+from twisted.python.filepath import FilePath
+
+import twistedcaldav.directory.test.util
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
+
+xmlFile = FilePath(os.path.join(os.path.dirname(__file__), "accounts.xml"))
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class XMLFileBase(object):
+ recordTypes = set(("user", "group", "resource"))
+
+ users = {
+ "admin" : "nimda",
+ "proxy" : "yxorp",
+ "wsanchez": "zehcnasw",
+ "cdaboo" : "oobadc",
+ "lecroy" : "yorcel",
+ "dreid" : "dierd",
+ "user01" : "01user",
+ "user02" : "02user",
+ }
+
+ groups = {
+ "managers" : ("lecroy",),
+ "grunts" : ("wsanchez", "cdaboo", "dreid"),
+ "right_coast": ("cdaboo",),
+ "left_coast" : ("wsanchez", "dreid", "lecroy"),
+ }
+
+ resources = set((
+ "mercury",
+ "gemini",
+ "apollo",
+ ))
+
+ def xmlFile(self):
+ if not hasattr(self, "_xmlFile"):
+ self._xmlFile = FilePath(self.mktemp())
+ xmlFile.copyTo(self._xmlFile)
+ return self._xmlFile
+
+class XMLFile (
+ XMLFileBase,
+ twistedcaldav.directory.test.util.BasicTestCase,
+ twistedcaldav.directory.test.util.DigestTestCase
+):
+ """
+ Test XML file based directory implementation.
+ """
+ def service(self):
+ return XMLDirectoryService(self.xmlFile())
+
+ def test_changedXML(self):
+ self.xmlFile().open("w").write(
+"""<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+<accounts>
+ <user>
+ <uid>admin</uid>
+ <pswd>nimda</pswd>
+ <name>Super User</name>
+ </user>
+</accounts>
+"""
+ )
+ for recordType, expectedRecords in (
+ ( "user" , ("admin",) ),
+ ( "group" , () ),
+ ( "resource" , () ),
+ ):
+ self.assertEquals(
+ set(r.shortName for r in self.service().listRecords(recordType)),
+ set(expectedRecords)
+ )
Deleted: CalendarServer/trunk/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -1,234 +0,0 @@
-##
-# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
-##
-
-import twisted.trial.unittest
-from twisted.trial.unittest import SkipTest
-from twisted.cred.credentials import UsernamePassword
-from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class DirectoryTestCase (twisted.trial.unittest.TestCase):
- """
- Tests a directory implementation.
- """
- # Subclass should init this to a set of recordtypes.
- recordTypes = set()
-
- # Subclass should init this to a dict of username keys and password values.
- users = {}
-
- # Subclass should init this to a dict of groupname keys and
- # sequence-of-members values.
- groups = {}
-
- # Subclass should init this to a set of resourcenames.
- resources = set()
-
- # Subclass should init this to an IDirectoryService implementation class.
- def service(self):
- """
- Returns an IDirectoryService.
- """
- raise NotImplementedError("Subclass needs to implement service()")
-
- def test_recordTypes(self):
- """
- IDirectoryService.recordTypes()
- """
- if not self.recordTypes:
- raise SkipTest("No record types")
-
- self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
-
- def test_listRecords_user(self):
- """
- IDirectoryService.listRecords("user")
- """
- if not self.users:
- raise SkipTest("No users")
-
- self.assertEquals(self.recordNames("user"), set(self.users.keys()))
-
- def test_listRecords_group(self):
- """
- IDirectoryService.listRecords("group")
- """
- if not self.groups:
- raise SkipTest("No groups")
-
- self.assertEquals(self.recordNames("group"), set(self.groups.keys()))
-
- def test_listRecords_resources(self):
- """
- IDirectoryService.listRecords("resources")
- """
- if not self.resources:
- raise SkipTest("No resources")
-
- self.assertEquals(self.recordNames("resource"), self.resources)
-
- def test_recordWithShortName_user(self):
- """
- IDirectoryService.recordWithShortName("user")
- """
- if not self.users:
- raise SkipTest("No users")
-
- service = self.service()
- for user in self.users:
- record = service.recordWithShortName("user", user)
- self.assertEquals(record.shortName, user)
- self.assertEquals(service.recordWithShortName("user", "IDunnoWhoThisIsIReallyDont"), None)
-
- def test_recordWithShortName_group(self):
- """
- IDirectoryService.recordWithShortName("group")
- """
- if not self.groups:
- raise SkipTest("No groups")
-
- service = self.service()
- for group in self.groups:
- groupRecord = service.recordWithShortName("group", group)
- self.assertEquals(groupRecord.shortName, group)
- self.assertEquals(service.recordWithShortName("group", "IDunnoWhoThisIsIReallyDont"), None)
-
- def test_recordWithShortName_resource(self):
- """
- XMLDirectoryService.recordWithShortName("resource")
- """
- if not self.resources:
- raise SkipTest("No resources")
-
- service = self.service()
- for resource in self.resources:
- resourceRecord = service.recordWithShortName("resource", resource)
- self.assertEquals(resourceRecord.shortName, resource)
-
- def test_groupMembers(self):
- """
- IDirectoryRecord.members()
- """
- if not self.groups:
- raise SkipTest("No groups")
-
- service = self.service()
- for group in self.groups:
- groupRecord = service.recordWithShortName("group", group)
- self.assertEquals(
- set(m.shortName for m in groupRecord.members()),
- set(self.groups[group])
- )
-
- def test_groupMemberships(self):
- """
- IDirectoryRecord.groups()
- """
- if not self.users:
- raise SkipTest("No users")
- if not self.groups:
- raise SkipTest("No groups")
-
- service = self.service()
- for user in self.users:
- userRecord = service.recordWithShortName("user", user)
- self.assertEquals(
- set(g.shortName for g in userRecord.groups()),
- set(g for g in self.groups if user in self.groups[g])
- )
-
- def recordNames(self, recordType):
- return set(r.shortName for r in self.service().listRecords(recordType))
-
-class BasicTestCase (DirectoryTestCase):
- """
- Tests a directory implementation with basic auth.
- """
- def test_verifyCredentials_basic(self):
- """
- IDirectoryRecord.verifyCredentials() with basic
- """
- if not self.users:
- raise SkipTest("No users")
-
- service = self.service()
- for user in self.users:
- userRecord = service.recordWithShortName("user", user)
- self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, self.users[user])))
-
-# authRequest = {
-# username="username",
-# realm="test realm",
-# nonce="178288758716122392881254770685",
-# uri="/write/",
-# response="62f388be1cf678fbdfce87910871bcc5",
-# opaque="1041524039",
-# algorithm="md5",
-# cnonce="29fc54aa1641c6fa0e151419361c8f23",
-# nc=00000001,
-# qop="auth",
-# }
-
-class DigestTestCase (DirectoryTestCase):
- """
- Tests a directory implementation with digest auth.
- """
- def test_verifyCredentials_digest(self):
- """
- IDirectoryRecord.verifyCredentials() with digest
- """
- if not self.users:
- raise SkipTest("No users")
-
- service = self.service()
- for user in self.users:
- userRecord = service.recordWithShortName("user", user)
-
- # I'm glad this is so simple...
- response = calcResponse(
- calcHA1(
- "md5",
- user,
- service.realmName,
- userRecord.password,
- "booger",
- "phlegm",
- ),
- "md5",
- "booger",
- None,
- "phlegm",
- "auth",
- "GET",
- "/",
- None,
- )
-
- self.failUnless(userRecord.verifyCredentials(DigestedCredentials(
- user,
- "GET",
- service.realmName,
- {
- "response": response,
- "uri": "/",
- "nonce": "booger",
- "cnonce": "phlegm",
- "nc": None,
- },
- )))
Copied: CalendarServer/trunk/twistedcaldav/directory/test/util.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/test/util.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/util.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,234 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+
+import twisted.trial.unittest
+from twisted.trial.unittest import SkipTest
+from twisted.cred.credentials import UsernamePassword
+from twisted.web2.auth.digest import DigestedCredentials, calcResponse, calcHA1
+
+# FIXME: Add tests for GUID hooey, once we figure out what that means here
+
+class DirectoryTestCase (twisted.trial.unittest.TestCase):
+ """
+ Tests a directory implementation.
+ """
+ # Subclass should init this to a set of recordtypes.
+ recordTypes = set()
+
+ # Subclass should init this to a dict of username keys and password values.
+ users = {}
+
+ # Subclass should init this to a dict of groupname keys and
+ # sequence-of-members values.
+ groups = {}
+
+ # Subclass should init this to a set of resourcenames.
+ resources = set()
+
+ # Subclass should init this to an IDirectoryService implementation class.
+ def service(self):
+ """
+ Returns an IDirectoryService.
+ """
+ raise NotImplementedError("Subclass needs to implement service()")
+
+ def test_recordTypes(self):
+ """
+ IDirectoryService.recordTypes()
+ """
+ if not self.recordTypes:
+ raise SkipTest("No record types")
+
+ self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
+
+ def test_listRecords_user(self):
+ """
+ IDirectoryService.listRecords("user")
+ """
+ if not self.users:
+ raise SkipTest("No users")
+
+ self.assertEquals(self.recordNames("user"), set(self.users.keys()))
+
+ def test_listRecords_group(self):
+ """
+ IDirectoryService.listRecords("group")
+ """
+ if not self.groups:
+ raise SkipTest("No groups")
+
+ self.assertEquals(self.recordNames("group"), set(self.groups.keys()))
+
+ def test_listRecords_resources(self):
+ """
+ IDirectoryService.listRecords("resources")
+ """
+ if not self.resources:
+ raise SkipTest("No resources")
+
+ self.assertEquals(self.recordNames("resource"), self.resources)
+
+ def test_recordWithShortName_user(self):
+ """
+ IDirectoryService.recordWithShortName("user")
+ """
+ if not self.users:
+ raise SkipTest("No users")
+
+ service = self.service()
+ for user in self.users:
+ record = service.recordWithShortName("user", user)
+ self.assertEquals(record.shortName, user)
+ self.assertEquals(service.recordWithShortName("user", "IDunnoWhoThisIsIReallyDont"), None)
+
+ def test_recordWithShortName_group(self):
+ """
+ IDirectoryService.recordWithShortName("group")
+ """
+ if not self.groups:
+ raise SkipTest("No groups")
+
+ service = self.service()
+ for group in self.groups:
+ groupRecord = service.recordWithShortName("group", group)
+ self.assertEquals(groupRecord.shortName, group)
+ self.assertEquals(service.recordWithShortName("group", "IDunnoWhoThisIsIReallyDont"), None)
+
+ def test_recordWithShortName_resource(self):
+ """
+ XMLDirectoryService.recordWithShortName("resource")
+ """
+ if not self.resources:
+ raise SkipTest("No resources")
+
+ service = self.service()
+ for resource in self.resources:
+ resourceRecord = service.recordWithShortName("resource", resource)
+ self.assertEquals(resourceRecord.shortName, resource)
+
+ def test_groupMembers(self):
+ """
+ IDirectoryRecord.members()
+ """
+ if not self.groups:
+ raise SkipTest("No groups")
+
+ service = self.service()
+ for group in self.groups:
+ groupRecord = service.recordWithShortName("group", group)
+ self.assertEquals(
+ set(m.shortName for m in groupRecord.members()),
+ set(self.groups[group])
+ )
+
+ def test_groupMemberships(self):
+ """
+ IDirectoryRecord.groups()
+ """
+ if not self.users:
+ raise SkipTest("No users")
+ if not self.groups:
+ raise SkipTest("No groups")
+
+ service = self.service()
+ for user in self.users:
+ userRecord = service.recordWithShortName("user", user)
+ self.assertEquals(
+ set(g.shortName for g in userRecord.groups()),
+ set(g for g in self.groups if user in self.groups[g])
+ )
+
+ def recordNames(self, recordType):
+ return set(r.shortName for r in self.service().listRecords(recordType))
+
+class BasicTestCase (DirectoryTestCase):
+ """
+ Tests a directory implementation with basic auth.
+ """
+ def test_verifyCredentials_basic(self):
+ """
+ IDirectoryRecord.verifyCredentials() with basic
+ """
+ if not self.users:
+ raise SkipTest("No users")
+
+ service = self.service()
+ for user in self.users:
+ userRecord = service.recordWithShortName("user", user)
+ self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, self.users[user])))
+
+# authRequest = {
+# username="username",
+# realm="test realm",
+# nonce="178288758716122392881254770685",
+# uri="/write/",
+# response="62f388be1cf678fbdfce87910871bcc5",
+# opaque="1041524039",
+# algorithm="md5",
+# cnonce="29fc54aa1641c6fa0e151419361c8f23",
+# nc=00000001,
+# qop="auth",
+# }
+
+class DigestTestCase (DirectoryTestCase):
+ """
+ Tests a directory implementation with digest auth.
+ """
+ def test_verifyCredentials_digest(self):
+ """
+ IDirectoryRecord.verifyCredentials() with digest
+ """
+ if not self.users:
+ raise SkipTest("No users")
+
+ service = self.service()
+ for user in self.users:
+ userRecord = service.recordWithShortName("user", user)
+
+ # I'm glad this is so simple...
+ response = calcResponse(
+ calcHA1(
+ "md5",
+ user,
+ service.realmName,
+ userRecord.password,
+ "booger",
+ "phlegm",
+ ),
+ "md5",
+ "booger",
+ None,
+ "phlegm",
+ "auth",
+ "GET",
+ "/",
+ None,
+ )
+
+ self.failUnless(userRecord.verifyCredentials(DigestedCredentials(
+ user,
+ "GET",
+ service.realmName,
+ {
+ "response": response,
+ "uri": "/",
+ "nonce": "booger",
+ "cnonce": "phlegm",
+ "nc": None,
+ },
+ )))
Copied: CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlaccountsparser.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaccountsparser.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,194 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+
+"""
+XML based user/group/resource configuration file handling.
+"""
+
+__all__ = [
+ "XMLAccountsParser",
+]
+
+import xml.dom.minidom
+
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.resource import CalDAVResource
+
+ELEMENT_ACCOUNTS = "accounts"
+ELEMENT_USER = "user"
+ELEMENT_GROUP = "group"
+ELEMENT_RESOURCE = "resource"
+
+ELEMENT_USERID = "uid"
+ELEMENT_PASSWORD = "pswd"
+ELEMENT_NAME = "name"
+ELEMENT_MEMBERS = "members"
+ELEMENT_CUADDR = "cuaddr"
+ELEMENT_CANPROXY = "canproxy"
+
+ATTRIBUTE_REALM = "realm"
+ATTRIBUTE_REPEAT = "repeat"
+
+class XMLAccountsParser(object):
+ """
+ XML account configuration file parser.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, xmlFile):
+ if type(xmlFile) is str:
+ xmlFile = FilePath(xmlFile)
+
+ self.xmlFile = xmlFile
+ self.realm = None
+ self.items = {}
+
+ # Read in XML
+ fd = open(self.xmlFile.path, "r")
+ doc = xml.dom.minidom.parse( fd )
+ fd.close()
+
+ # Verify that top-level element is correct
+ accounts_node = doc._get_documentElement()
+ if accounts_node._get_localName() != ELEMENT_ACCOUNTS:
+ self.log("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
+ return
+ self._parseXML(accounts_node)
+
+ def _parseXML(self, node):
+ """
+ Parse the XML root node from the accounts configuration document.
+ @param node: the L{Node} to parse.
+ """
+ if node.hasAttribute(ATTRIBUTE_REALM):
+ self.realm = node.getAttribute(ATTRIBUTE_REALM)
+
+ for child in node._get_childNodes():
+ if child._get_localName() in (ELEMENT_USER, ELEMENT_GROUP, ELEMENT_RESOURCE):
+ if child.hasAttribute(ATTRIBUTE_REPEAT):
+ repeat = int(child.getAttribute(ATTRIBUTE_REPEAT))
+ else:
+ repeat = 1
+
+ recordType = {
+ ELEMENT_USER: "user",
+ ELEMENT_GROUP: "group",
+ ELEMENT_RESOURCE:"resource",}[child._get_localName()]
+
+ principal = XMLAccountRecord(recordType)
+ principal.parseXML( child )
+ if repeat > 1:
+ for ctr in range(repeat):
+ newprincipal = principal.repeat(ctr + 1)
+ self.items[newprincipal.uid] = newprincipal
+ if recordType == "group":
+ self._updateMembership(newprincipal)
+ else:
+ self.items[principal.uid] = principal
+ if recordType == "group":
+ self._updateMembership(principal)
+
+ def _updateMembership(self, group):
+ # Update group membership
+ for member in group.members:
+ if self.items.has_key(member):
+ self.items[member].groups.append(group.uid)
+
+class XMLAccountRecord (object):
+ """
+ Contains provision information for one user.
+ """
+ def __init__(self, recordType):
+ """
+ @param recordType: record type for directory entry.
+ """
+
+ self.recordType = recordType
+ self.uid = None
+ self.password = None
+ self.name = None
+ self.members = []
+ self.groups = []
+ self.calendarUserAddresses = []
+ self.canproxy = False
+
+ def repeat(self, ctr):
+ """
+ Create another object like this but with all text items having % substitution
+ done on them with the numeric value provided.
+ @param ctr: an integer to substitute into text.
+ """
+
+ if self.uid.find("%") != -1:
+ uid = self.uid % ctr
+ else:
+ uid = self.uid
+ if self.password.find("%") != -1:
+ password = self.password % ctr
+ else:
+ password = self.password
+ if self.name.find("%") != -1:
+ name = self.name % ctr
+ else:
+ name = self.name
+ calendarUserAddresses = []
+ for cuaddr in self.calendarUserAddresses:
+ if cuaddr.find("%") != -1:
+ calendarUserAddresses.append(cuaddr % ctr)
+ else:
+ calendarUserAddresses.append(cuaddr)
+
+ result = XMLAccountRecord(self.recordType)
+ result.uid = uid
+ result.password = password
+ result.name = name
+ result.members = self.members
+ result.calendarUserAddresses = calendarUserAddresses
+ result.canproxy = self.canproxy
+ return result
+
+ def parseXML( self, node ):
+
+ for child in node._get_childNodes():
+ if child._get_localName() == ELEMENT_USERID:
+ if child.firstChild is not None:
+ self.uid = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_PASSWORD:
+ if child.firstChild is not None:
+ self.password = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_NAME:
+ if child.firstChild is not None:
+ self.name = child.firstChild.data.encode("utf-8")
+ elif child._get_localName() == ELEMENT_MEMBERS:
+ self._parseMembers(child)
+ elif child._get_localName() == ELEMENT_CUADDR:
+ if child.firstChild is not None:
+ self.calendarUserAddresses.append(child.firstChild.data.encode("utf-8"))
+ elif child._get_localName() == ELEMENT_CANPROXY:
+ CalDAVResource.proxyUsers.add(self.uid)
+ self.canproxy = True
+
+ def _parseMembers( self, node ):
+
+ for child in node._get_childNodes():
+ if child._get_localName() == ELEMENT_USERID:
+ if child.firstChild is not None:
+ self.members.append(child.firstChild.data.encode("utf-8"))
Copied: CalendarServer/trunk/twistedcaldav/directory/xmlfile.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/directory/xmlfile.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlfile.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlfile.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,124 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+XML based user/group/resource directory service implementation.
+"""
+
+__all__ = [
+ "XMLDirectoryService",
+]
+
+from twisted.cred.credentials import UsernamePassword
+from twisted.web2.auth.digest import DigestedCredentials
+from twisted.python.filepath import FilePath
+
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser
+
+class XMLDirectoryService(DirectoryService):
+ """
+ XML based implementation of L{IDirectoryService}.
+ """
+ def __repr__(self):
+ return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
+ def __init__(self, xmlFile):
+ super(XMLDirectoryService, self).__init__()
+
+ if type(xmlFile) is str:
+ xmlFile = FilePath(xmlFile)
+
+ self.xmlFile = xmlFile
+ self._fileInfo = None
+
+ def recordTypes(self):
+ recordTypes = ("user", "group", "resource")
+ return recordTypes
+
+ def listRecords(self, recordType):
+ for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+ yield XMLDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ xmlPrincipal = xmlprincipal,
+ )
+
+ def recordWithShortName(self, recordType, shortName):
+ for entryShortName, xmlprincipal in self._entriesForRecordType(recordType):
+ if entryShortName == shortName:
+ return XMLDirectoryRecord(
+ service = self,
+ recordType = recordType,
+ shortName = entryShortName,
+ xmlPrincipal = xmlprincipal,
+ )
+
+ return None
+
+ def recordWithGUID(self, guid):
+ raise NotImplementedError()
+
+ def _entriesForRecordType(self, recordType):
+ for entry in self._accounts().itervalues():
+ if entry.recordType == recordType:
+ yield entry.uid, entry
+
+ def _accounts(self):
+ fileInfo = (self.xmlFile.getmtime(), self.xmlFile.getsize())
+ if fileInfo != self._fileInfo:
+ parser = XMLAccountsParser(self.xmlFile)
+ self._parsedAccounts = parser.items
+ self.realmName = parser.realm
+ self._fileInfo = fileInfo
+ return self._parsedAccounts
+
+class XMLDirectoryRecord(DirectoryRecord):
+ """
+ XML based implementation implementation of L{IDirectoryRecord}.
+ """
+ def __init__(self, service, recordType, shortName, xmlPrincipal):
+ super(XMLDirectoryRecord, self).__init__(
+ service = service,
+ recordType = recordType,
+ guid = None,
+ shortName = shortName,
+ fullName = xmlPrincipal.name,
+ calendarUserAddresses = xmlPrincipal.calendarUserAddresses
+ )
+
+ self.password = xmlPrincipal.password
+ self._members = xmlPrincipal.members
+ self._groups = xmlPrincipal.groups
+
+ def members(self):
+ for shortName in self._members:
+ yield self.service.recordWithShortName("user", shortName)
+
+ def groups(self):
+ for shortName in self._groups:
+ yield self.service.recordWithShortName("group", shortName)
+
+ def verifyCredentials(self, credentials):
+ if isinstance(credentials, UsernamePassword):
+ return credentials.password == self.password
+ if isinstance(credentials, DigestedCredentials):
+ return credentials.checkPassword(self.password)
+
+ return super(XMLDirectoryRecord, self).verifyCredentials(credentials)
Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -76,13 +76,13 @@
)
@classmethod
- def provision(clzz, principal, cuhome):
+ def provision(clzz, cuhome):
"""
Provision user account with appropriate collections for drop box
and notifications.
@param principal: the L{CalendarPrincipalResource} for the principal to provision
- @param cuhome: C{tuple} of (C{str} - URI of user calendar home, L{DAVResource} - resource of user calendar home)
+ @param cuhome: L{DAVResource} - resource of user calendar home
"""
# Only if enabled
@@ -92,7 +92,7 @@
# Create drop box collection in calendar-home collection resource if not already present.
from twistedcaldav.static import CalDAVFile
- child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.dropboxName))
+ child = CalDAVFile(os.path.join(cuhome.fp.path, DropBox.dropboxName))
child_exists = child.exists()
if not child_exists:
c = child.createSpecialCollection(davxml.ResourceType.dropboxhome)
@@ -102,7 +102,7 @@
if not DropBox.notifications:
return
- child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.notifcationName))
+ child = CalDAVFile(os.path.join(cuhome.fp.path, DropBox.notifcationName))
child_exists = child.exists()
if not child_exists:
c = child.createSpecialCollection(davxml.ResourceType.notifications)
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -27,6 +27,7 @@
]
from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError
from twisted.web2.dav.http import StatusResponse
import twisted.web2.dav.resource
@@ -52,7 +53,7 @@
)
def _forbidden(self, request):
- return readOnlyResponse
+ return self.readOnlyResponse
http_DELETE = _forbidden
http_MOVE = _forbidden
@@ -60,4 +61,4 @@
http_PUT = _forbidden
def writeProperty(self, property, request):
- raise HTTPError(readOnlyResponse)
+ raise HTTPError(self.readOnlyResponse)
Modified: CalendarServer/trunk/twistedcaldav/method/mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcalendar.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/method/mkcalendar.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -22,8 +22,7 @@
__all__ = ["http_MKCALENDAR"]
-from twisted.internet.defer import deferredGenerator
-from twisted.internet.defer import waitForDeferred
+from twisted.internet.defer import deferredGenerator, waitForDeferred
from twisted.python import log
from twisted.python.failure import Failure
from twisted.web2 import responsecode
Modified: CalendarServer/trunk/twistedcaldav/repository.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/repository.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/repository.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -24,6 +24,14 @@
__all__ = ["RepositoryBuilder"]
+import os
+
+from xml.dom.minidom import Element
+from xml.dom.minidom import Text
+import xml.dom.minidom
+
+from zope.interface import implements
+
from twisted.application.internet import SSLServer, TCPServer
from twisted.application.service import Application, IServiceCollection, MultiService
from twisted.cred.portal import Portal
@@ -45,15 +53,9 @@
from twistedcaldav import authkerb
from twistedcaldav.logging import RotatingFileAccessLoggingObserver
from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.static import CalendarHomeFile, CalendarPrincipalFile
-from twistedcaldav.directory.cred import DirectoryCredentialsChecker
+from twistedcaldav.static import CalendarHomeFile
+from twistedcaldav.directory.idirectory import IDirectoryService
-import os
-
-from xml.dom.minidom import Element
-from xml.dom.minidom import Text
-import xml.dom.minidom
-
ELEMENT_REPOSITORY = "repository"
ELEMENT_DOCROOT = "docroot"
@@ -127,6 +129,7 @@
def startServer(docroot, repo, doacct, doacl, dossl,
keyfile, certfile, onlyssl, port, sslport, maxsize,
quota, serverlogfile,
+ directoryservice,
dropbox, dropboxName, dropboxACLs,
notifications, notifcationName,
manhole):
@@ -177,13 +180,26 @@
# Turn on drop box support before building the repository
DropBox.enable(dropbox, dropboxName, dropboxACLs, notifications, notifcationName)
+ dirname = directoryservice["type"]
+ dirparams = directoryservice["params"]
+ try:
+ resource_class = namedObject(dirname)
+ except:
+ log.err("Unable to locate Python class %r" % (dirname,))
+ raise
+ try:
+ directory = resource_class(**dirparams)
+ except Exception:
+ log.err("Unable to instantiate Python class %r with arguments %r" % (resource_class, dirparams))
+ raise
+
# Build the server
builder = RepositoryBuilder(docroot,
doAccounts=doacct,
resetACLs=doacl,
maxsize=maxsize,
quota=quota)
- builder.buildFromFile(repo)
+ builder.buildFromFile(repo, directory)
rootresource = builder.docRoot.collection.resource
application = Application("CalDAVServer")
@@ -200,7 +216,7 @@
portal.registerChecker(auth.TwistedPropertyChecker())
print "Using property-based password checker."
elif authenticator.credentials == ATTRIBUTE_VALUE_DIRECTORY:
- portal.registerChecker(DirectoryCredentialsChecker())
+ portal.registerChecker(directory)
print "Using directory-based password checker."
elif authenticator.credentials == ATTRIBUTE_VALUE_KERBEROS:
if authenticator.type == "basic":
@@ -285,24 +301,24 @@
if self.quota <= 0:
self.quota = None
- def buildFromFile(self, file):
+ def buildFromFile(self, filename, directory):
"""
Parse the required information from an XML file.
@param file: the path of the XML file to parse.
"""
# Read in XML
- fd = open(file, "r")
+ fd = open(filename, "r")
doc = xml.dom.minidom.parse( fd )
fd.close()
# Verify that top-level element is correct
repository_node = doc._get_documentElement()
if repository_node._get_localName() != ELEMENT_REPOSITORY:
- self.log("Ignoring file \"%s\" because it is not a repository builder file" % (file,))
+ self.log("Ignoring file %r because it is not a repository builder file" % (filename,))
return
self.parseXML(repository_node)
- self.docRoot.build()
+ self.docRoot.build(directory)
if self.doAccounts:
self.accounts.provision(
self.docRoot.principalCollections,
@@ -358,11 +374,11 @@
self.collection.parseXML(child, self)
break
- def build(self):
+ def build(self, directory):
"""
Build the entire repository starting at the root resource.
"""
- self.collection.build(self.path, "/")
+ self.collection.build(self.path, "/", directory)
# Setup the principal-collection-set property if required
if self.autoPrincipalCollectionSet:
@@ -384,7 +400,7 @@
"""
def __init__(self):
self.name = None
- self.pytype = "twistedcaldav.static.CalDAVFile" # FIXME: Why not None?
+ self.pytype = None
self.params = {}
self.properties = []
self.acl = None
@@ -466,7 +482,7 @@
self.properties.append(Prop())
self.properties[-1].parseXML(child)
- def build(self, docroot, urlroot):
+ def build(self, docroot, urlroot, directory):
"""
Create this collection, initialising any properties and then create any child
collections.
@@ -490,14 +506,19 @@
kwargs = {}
argnames = resource_class.__init__.func_code.co_varnames
for name, value in (
- ("path", mypath),
- ("url" , myurl ),
+ ("path" , mypath ),
+ ("url" , myurl ),
+ ("directory", directory),
):
if name in argnames:
kwargs[name] = value
if self.params:
kwargs["params"] = self.params
- self.resource = resource_class(**kwargs)
+ try:
+ self.resource = resource_class(**kwargs)
+ except Exception:
+ log.err("Unable to instantiate Python class %r with arguments %r" % (resource_class, kwargs))
+ raise
self.uri = myurl
@@ -510,7 +531,7 @@
self.resource.setAccessControlList(self.acl.acl)
for member in self.members:
- child = member.build(mypath, myurl)
+ child = member.build(mypath, myurl, directory)
# Only putChild if one does not already exists
if self.resource.putChildren.get(member.name, None) is None:
self.resource.putChild(member.name, child)
@@ -704,10 +725,10 @@
self.calendarHome.resource,
)
- # Check for proper account home
- if not self.accountCollection:
- log.err("Accounts cannot be created: no principal collection was marked with an account attribute.")
- raise ValueError, "Accounts cannot be created."
+# # Check for proper account home
+# if not self.accountCollection:
+# log.err("Accounts cannot be created: no principal collection was marked with an account attribute.")
+# raise ValueError, "Accounts cannot be created."
# Provision each user
for repeat, principal in self.items:
@@ -717,44 +738,6 @@
for ctr in range(1, repeat+1):
self.provisionOne(principal.repeat(ctr), resetACLs)
- def provisionOne(self, item, resetACLs):
- """
- Provision one user account/
- @param item: The account to provision.
- @param resetACLs: if True, ACL privileges on all resources related to the
- accounts being created are reset, if False no ACL privileges are changed.
- """
- principalURL = joinURL(self.accountCollection.uri, item.uid)
-
- # Create principal resource
- principal = FilePath(os.path.join(self.accountCollection.resource.fp.path, item.uid))
- principal_exists = principal.exists()
- if not principal_exists:
- principal.open("w").close()
- log.msg("Created principal: %s" % principalURL)
- principal = CalendarPrincipalFile(principal.path, principalURL)
-
- # Special case: if we have an explicit cuhome URL, we will use that,
- # otherwise we fall back to the inferred home and resource
- if item.cuhome:
- cuhome = (item.cuhome, None)
- else:
- cuhome = (self.calendarHome.uri, self.calendarHome.resource)
-
- # Principal knows how to provision itself in the appropriate manner
- principal.provisionCalendarAccount(
- item.name,
- item.pswd,
- resetACLs or not principal_exists,
- item.cuaddrs,
- cuhome,
- item.acl,
- item.quota,
- item.calendars,
- item.autorespond,
- True
- )
-
class ProvisionPrincipal (object):
"""
Contains provision information for one user.
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -33,8 +33,6 @@
"isScheduleOutboxResource",
]
-from weakref import WeakValueDictionary
-
from zope.interface import implements
from twisted.internet import reactor
@@ -43,7 +41,8 @@
from twisted.python import log
from twisted.web2 import responsecode
from twisted.web2.dav import davxml
-from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource
+from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
+from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource, DAVPrincipalCollectionResource
from twisted.web2.dav.davxml import dav_namespace
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.resource import TwistedACLInheritable
@@ -233,7 +232,7 @@
return super(CalDAVResource, self).accessControlList(*args, **kwargs)
- def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
+ def authorizationPrincipal(self, request, authid, authnPrincipal):
"""
Determine the authorization principal for the given request and authentication principal.
This implementation looks for an X-Authorize-As header value to use as the authoization principal.
@@ -242,10 +241,10 @@
@param authid: a string containing the uthentication/authorization identifier
for the principal to lookup.
@param authnPrincipal: the L{IDAVPrincipal} for the authenticated principal
- @param authnURI: a C{str} containing the URI of the authenticated principal
@return: a deferred result C{tuple} of (L{IDAVPrincipal}, C{str}) containing the authorization principal
resource and URI respectively.
"""
+ # FIXME: Unroll defgen
# Look for X-Authorize-As Header
authz = request.headers.getRawHeaders("x-authorize-as")
@@ -260,15 +259,13 @@
log.msg("Cannot proxy as another proxy: user '%s' as user '%s'" % (authid, authz))
raise HTTPError(responsecode.UNAUTHORIZED)
else:
- d = waitForDeferred(self.findPrincipalForAuthID(request, authz))
- yield d
- result = d.getResult()
+ authzPrincipal = waitForDeferred(self.findPrincipalForAuthID(request, authz))
+ yield authzPrincipal
+ authzPrincipal = authzPrincipal.getResult()
- if result is not None:
+ if authzPrincipal is not None:
log.msg("Allow proxy: user '%s' as '%s'" % (authid, authz,))
- authzPrincipal = result[0]
- authzURI = result[1]
- yield authzPrincipal, authzURI
+ yield authzPrincipal
return
else:
log.msg("Could not find proxy user id: '%s'" % authid)
@@ -281,7 +278,7 @@
raise HTTPError(responsecode.UNAUTHORIZED)
else:
# No proxy - do default behavior
- d = waitForDeferred(super(CalDAVResource, self).authorizationPrincipal(request, authid, authnPrincipal, authnURI))
+ d = waitForDeferred(super(CalDAVResource, self).authorizationPrincipal(request, authid, authnPrincipal))
yield d
yield d.getResult()
return
@@ -491,13 +488,11 @@
"""
return request.locateResource(parentForURL(uri))
-class CalendarPrincipalCollectionResource (CalDAVResource):
+class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
"""
CalDAV principal collection.
"""
- # Use a WeakKeyDictionary to keep track of all instances.
- # A WeakKeySet would be more appropriate, but there is no such class yet.
- principleCollectionSet = WeakValueDictionary()
+ implements(IDAVPrincipalCollectionResource)
@classmethod
def outboxForCalendarUser(clazz, request, address):
@@ -539,13 +534,6 @@
d.addCallback(_defer)
return d
- def __init__(self, url):
- self._url = url
-
- # Register self with class
- if url not in CalendarPrincipalCollectionResource.principleCollectionSet:
- CalendarPrincipalCollectionResource.principleCollectionSet[url] = self
-
def isCollection(self):
return True
@@ -580,9 +568,6 @@
findCalendarUser = deferredGenerator(findCalendarUser)
- def principalCollectionURL(self):
- return self._url
-
def supportedReports(self):
"""
Principal collections are the only resources supporting the
@@ -592,6 +577,29 @@
result.append(davxml.Report(davxml.PrincipalSearchPropertySet(),))
return result
+ def principalSearchPropertySet(self):
+ return davxml.PrincipalSearchPropertySet(
+ davxml.PrincipalSearchProperty(
+ davxml.PropertyContainer(
+ davxml.DisplayName()
+ ),
+ davxml.Description(
+ davxml.PCDATAElement("Display Name"),
+ **{"xml:lang":"en"}
+ ),
+ ),
+ davxml.PrincipalSearchProperty(
+ davxml.PropertyContainer(
+ caldavxml.CalendarUserAddressSet()
+ ),
+ davxml.Description(
+ davxml.PCDATAElement("Calendar User Addresses"),
+ **{"xml:lang":"en"}
+ ),
+ ),
+ )
+
+# FIXME: Replace this
def findAnyCalendarUser(request, address):
"""
Find the calendar user principal associated with the specified calendar
@@ -601,25 +609,18 @@
@return: the L{CalendarPrincipalResource} for the specified calendar
user, or C{None} if the user is not found.
"""
- for url in CalendarPrincipalCollectionResource.principleCollectionSet.keys():
- try:
- # Explicitly locate the prinicpal collection resource to force URL caching in request
- pcollection = waitForDeferred(request.locateResource(url))
- yield pcollection
- pcollection = pcollection.getResult()
+ for collection in self.principalCollections():
+ if isinstance(collection, CalendarPrincipalCollectionResource):
+ principal = waitForDeferred(collection.findCalendarUser(request, address))
+ yield principal
+ principal = principal.getResult()
- if isinstance(pcollection, CalendarPrincipalCollectionResource):
- principal = waitForDeferred(pcollection.findCalendarUser(request, address))
+ if principal is not None:
yield principal
- principal = principal.getResult()
- if principal is not None:
- yield principal
- return
- except ReferenceError:
- pass
+ return
+ else:
+ yield None
- yield None
-
findAnyCalendarUser = deferredGenerator(findAnyCalendarUser)
class CalendarPrincipalResource (DAVPrincipalResource):
@@ -655,9 +656,9 @@
)
if name == "calendar-user-address-set":
- return caldavxml.CalendarUserAddressSet(
- *[davxml.HRef(url) for url in self.calendarHomeURLs()]
- )
+ return succeed(caldavxml.CalendarUserAddressSet(
+ *[davxml.HRef(uri) for uri in self.calendarUserAddresses()]
+ ))
if name == "schedule-inbox-URL":
url = self.scheduleInboxURL()
Copied: CalendarServer/trunk/twistedcaldav/sql.py (from rev 636, CalendarServer/branches/users/wsanchez/provisioning-2/twistedcaldav/sql.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sql.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sql.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -0,0 +1,213 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Generic SQL database access object.
+"""
+
+__all__ = [
+ "AbstractSQLDatabase",
+]
+
+import os
+
+from pysqlite2 import dbapi2 as sqlite
+
+from twisted.python import log
+
+class AbstractSQLDatabase(object):
+ """
+ A generic SQL database.
+ """
+
+ def __init__(self, dbpath, version):
+ """
+ @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+ index. C{resource} must be a calendar collection (ie.
+ C{resource.isPseudoCalendarCollection()} returns C{True}.)
+ """
+ self.dbpath = dbpath
+ self.version = version
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ raise NotImplementedError
+
+ def _db(self):
+ """
+ Access the underlying database.
+ @return: a db2 connection object for this index's underlying data store.
+ """
+ if not hasattr(self, "_db_connection"):
+ db_filename = self.dbpath
+ self._db_connection = sqlite.connect(db_filename)
+
+ #
+ # Set up the schema
+ #
+ q = self._db_connection.cursor()
+ try:
+ # Create CALDAV table if needed
+ q.execute(
+ """
+ select (1) from SQLITE_MASTER
+ where TYPE = 'table' and NAME = 'CALDAV'
+ """)
+ caldav = q.fetchone()
+
+ if caldav:
+ q.execute(
+ """
+ select VALUE from CALDAV
+ where KEY = 'SCHEMA_VERSION'
+ """)
+ version = q.fetchone()
+
+ if version is not None: version = version[0]
+
+ q.execute(
+ """
+ select VALUE from CALDAV
+ where KEY = 'TYPE'
+ """)
+ type = q.fetchone()
+
+ if type is not None: type = type[0]
+
+ if (version != self.version) or (type != self._db_type()):
+ if version != self.version:
+ log.err("Database %s has different schema (v.%s vs. v.%s)"
+ % (db_filename, version, self.version))
+ if type != self._db_type():
+ log.err("Database %s has different type (%s vs. %s)"
+ % (db_filename, type, self._db_type()))
+
+ # Delete this index and start over
+ q.close()
+ q = None
+ self._db_connection.close()
+ del(self._db_connection)
+ os.remove(db_filename)
+ return self._db()
+
+ else:
+ self._db_init(db_filename, q)
+
+ self._db_connection.commit()
+ finally:
+ if q is not None: q.close()
+ return self._db_connection
+
+ def _db_init(self, db_filename, q):
+ """
+ Initialise the underlying database tables.
+ @param db_filename: the file name of the index database.
+ @param q: a database cursor to use.
+ """
+ log.msg("Initializing database %s" % (db_filename,))
+
+ self._db_init_schema_table(q)
+ self._db_init_data_tables(q)
+
+ def _db_init_schema_table(self, q):
+ """
+ Initialise the underlying database tables.
+ @param db_filename: the file name of the index database.
+ @param q: a database cursor to use.
+ """
+
+ #
+ # CALDAV table keeps track of our schema version and type
+ #
+ q.execute(
+ """
+ create table CALDAV (
+ KEY text unique, VALUE text unique
+ )
+ """
+ )
+ q.execute(
+ """
+ insert into CALDAV (KEY, VALUE)
+ values ('SCHEMA_VERSION', :1)
+ """, [self.version]
+ )
+ q.execute(
+ """
+ insert into CALDAV (KEY, VALUE)
+ values ('TYPE', :1)
+ """, [self._db_type()]
+ )
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param db_filename: the file name of the index database.
+ @param q: a database cursor to use.
+ """
+ raise NotImplementedError
+
+ def _db_values_for_sql(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain the resulting values.
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: an interable of values in the first column of each row
+ resulting from executing C{sql} with C{query_params}.
+ @raise AssertionError: if the query yields multiple columns.
+ """
+ return (row[0] for row in self._db_execute(sql, *query_params))
+
+ def _db_value_for_sql(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain a single value.
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: the value resulting from the executing C{sql} with
+ C{query_params}.
+ @raise AssertionError: if the query yields multiple rows or columns.
+ """
+ value = None
+ for row in self._db_values_for_sql(sql, *query_params):
+ assert value is None, "Multiple values in DB for %s %s" % (sql, query_params)
+ value = row
+ return value
+
+ def _db_execute(self, sql, *query_params):
+ """
+ Execute an SQL query and obtain the resulting values.
+ @param sql: the SQL query to execute.
+ @param query_params: parameters to C{sql}.
+ @return: an interable of tuples for each row resulting from executing
+ C{sql} with C{query_params}.
+ """
+ q = self._db().cursor()
+ try:
+ try:
+ q.execute(sql, query_params)
+ except:
+ log.err("Exception while executing SQL: %r %r" % (sql, query_params))
+ raise
+ return q.fetchall()
+ finally:
+ q.close()
+
+ def _db_commit (self): self._db_connection.commit()
+ def _db_rollback(self): self._db_connection.rollback()
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/static.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -26,13 +26,11 @@
"ScheduleOutboxFile",
"CalendarHomeFile",
"CalendarHomeProvisioningFile",
- "CalendarPrincipalFile",
"CalendarPrincipalCollectionFile",
]
import os
import errno
-from posixpath import basename
from urlparse import urlsplit
from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
@@ -45,25 +43,25 @@
from twisted.web2.dav.fileop import mkcollection, rmdir
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.idav import IDAVResource
-from twisted.web2.dav.resource import TwistedACLInheritable
-from twisted.web2.dav.resource import TwistedQuotaRootProperty
+from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty, davPrivilegeSet
from twisted.web2.dav.util import parentForURL, joinURL, bindMethods
from twistedcaldav import caldavxml
from twistedcaldav import customxml
+from twistedcaldav.extensions import ReadOnlyResourceMixIn
from twistedcaldav.ical import Component as iComponent
from twistedcaldav.ical import Property as iProperty
from twistedcaldav.index import Index, IndexSchedule, db_basename
from twistedcaldav.resource import CalDAVResource, isNonCalendarCollectionParentResource, CalendarPrincipalResource
-from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource, CalendarPrincipalCollectionResource
+from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource
from twistedcaldav.resource import isCalendarCollectionResource
from twistedcaldav.extensions import DAVFile
+from twistedcaldav.directory.idirectory import IDirectoryService
class CalDAVFile (CalDAVResource, DAVFile):
"""
CalDAV-accessible L{DAVFile} resource.
"""
-
def __repr__(self):
if self.isCalendarCollection():
return "<%s (calendar collection): %s>" % (self.__class__.__name__, self.fp.path)
@@ -212,65 +210,13 @@
return caldavxml.CalendarData.fromCalendarData(self.iCalendarText(name))
def supportedPrivileges(self, request):
- if not hasattr(CalDAVFile, "_supportedCalendarPrivilegeSet"):
- CalDAVFile._supportedCalendarPrivilegeSet = davxml.SupportedPrivilegeSet(
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.All()),
- davxml.Description("all privileges", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Read()),
- davxml.Description("read resource", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(caldavxml.ReadFreeBusy()),
- davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
- ),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Write()),
- davxml.Description("write resource", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteProperties()),
- davxml.Description("write resource properties", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteContent()),
- davxml.Description("write resource content", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Bind()),
- davxml.Description("add child resource", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unbind()),
- davxml.Description("remove child resource", **{"xml:lang": "en"}),
- ),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unlock()),
- davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadACL()),
- davxml.Description("read resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteACL()),
- davxml.Description("write resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
- ),
- ),
- )
-
# read-free-busy support on calendar collection and calendar object resources
if self.isCollection():
- return succeed(CalDAVFile._supportedCalendarPrivilegeSet)
+ return succeed(calendarPrivilegeSet)
else:
def _callback(parent):
if parent and isCalendarCollectionResource(parent):
- return succeed(CalDAVFile._supportedCalendarPrivilegeSet)
+ return succeed(calendarPrivilegeSet)
else:
return super(CalDAVFile, self).supportedPrivileges(request)
@@ -280,7 +226,6 @@
return super(CalDAVFile, self).supportedPrivileges(request)
-
##
# Public additions
##
@@ -413,13 +358,21 @@
_checkParents = deferredGenerator(_checkParents)
-class ScheduleInboxFile (ScheduleInboxResource, CalDAVFile):
- """
- L{CalDAVFile} calendar inbox collection resource.
- """
- def __repr__(self):
- return "<%s (calendar inbox collection): %s>" % (self.__class__.__name__, self.fp.path)
+class ScheduleFile (CalDAVFile):
+ def __init__(self, path, parent):
+ super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
+ self._parent = parent
+
+ self.provision()
+ def provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.makedirs()
+ self.fp.restat(False)
+
def index(self):
"""
Obtains the index for an schedule collection resource.
@@ -430,6 +383,7 @@
return IndexSchedule(self)
def createSimilarFile(self, path):
+ self.provision()
if path == self.fp.path:
return self
else:
@@ -441,499 +395,311 @@
def http_MKCOL (self, request): return responsecode.FORBIDDEN
def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+ ##
+ # ACL
+ ##
+
def supportedPrivileges(self, request):
- if not hasattr(ScheduleInboxFile, "_supportedSchedulePrivilegeSet"):
- ScheduleInboxFile._supportedSchedulePrivilegeSet = davxml.SupportedPrivilegeSet(
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.All()),
- davxml.Description("all privileges", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Read()),
- davxml.Description("read resource", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Write()),
- davxml.Description("write resource", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteProperties()),
- davxml.Description("write resource properties", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteContent()),
- davxml.Description("write resource content", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Bind()),
- davxml.Description("add child resource", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unbind()),
- davxml.Description("remove child resource", **{"xml:lang": "en"}),
- ),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unlock()),
- davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadACL()),
- davxml.Description("read resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteACL()),
- davxml.Description("write resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(caldavxml.Schedule()),
- davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
- ),
- ),
- )
-
- return succeed(ScheduleInboxFile._supportedSchedulePrivilegeSet)
+ return succeed(schedulePrivilegeSet)
-class ScheduleOutboxFile (ScheduleOutboxResource, CalDAVFile):
+class ScheduleInboxFile (ScheduleInboxResource, ScheduleFile):
"""
- L{CalDAVFile} calendar outbox collection resource.
+ L{CalDAVFile} calendar inbox collection resource.
"""
def __repr__(self):
- return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
+ return "<%s (calendar inbox collection): %s>" % (self.__class__.__name__, self.fp.path)
- def index(self):
- """
- Obtains the index for an iTIP collection resource.
- @return: the index object for this resource.
- @raise AssertionError: if this resource is not a calendar collection
- resource.
- """
- return IndexSchedule(self)
+ def provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.makedirs()
+ self.fp.restat(False)
- def createSimilarFile(self, path):
- if path == self.fp.path:
- return self
- else:
- return CalDAVFile(path)
+ # FIXME: This should probably be a directory record option that
+ # maps to the property value directly without the need to store one.
+ if self._parent.record.recordType == "resource":
+ # Resources should have autorespond turned on by default,
+ # since they typically don't have someone responding for them.
+ self.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
- def http_COPY (self, request): return responsecode.FORBIDDEN
- def http_MOVE (self, request): return responsecode.FORBIDDEN
- def http_DELETE (self, request): return responsecode.FORBIDDEN
- def http_MKCOL (self, request): return responsecode.FORBIDDEN
- def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+ ##
+ # ACL
+ ##
- def supportedPrivileges(self, request):
- if not hasattr(ScheduleOutboxFile, "_supportedSchedulePrivilegeSet"):
- ScheduleOutboxFile._supportedSchedulePrivilegeSet = davxml.SupportedPrivilegeSet(
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.All()),
- davxml.Description("all privileges", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Read()),
- davxml.Description("read resource", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Write()),
- davxml.Description("write resource", **{"xml:lang": "en"}),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteProperties()),
- davxml.Description("write resource properties", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteContent()),
- davxml.Description("write resource content", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Bind()),
- davxml.Description("add child resource", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unbind()),
- davxml.Description("remove child resource", **{"xml:lang": "en"}),
- ),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.Unlock()),
- davxml.Description("unlock resource without ownership", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadACL()),
- davxml.Description("read resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.WriteACL()),
- davxml.Description("write resource access control list", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- davxml.Description("read privileges for current principal", **{"xml:lang": "en"}),
- ),
- davxml.SupportedPrivilege(
- davxml.Privilege(caldavxml.Schedule()),
- davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
- ),
+ def defaultAccessControlList(self):
+ return davxml.ACL(
+ # CalDAV:schedule for any authenticated user
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(caldavxml.Schedule()),
),
- )
-
- return succeed(ScheduleOutboxFile._supportedSchedulePrivilegeSet)
+ ),
+ )
-class CalendarHomeFile (CalDAVFile):
+class ScheduleOutboxFile (ScheduleOutboxResource, ScheduleFile):
"""
- L{CalDAVFile} calendar home collection resource.
+ L{CalDAVFile} calendar outbox collection resource.
"""
-
- # A global quota limit for all calendar homes. Either a C{int} (size in bytes) to limit
- # quota to that size, or C{None} for no limit.
- quotaLimit = None
+ def __repr__(self):
+ return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
- def __init__(self, path):
+class CalendarHomeProvisioningFile (ReadOnlyResourceMixIn, DAVFile):
+ """
+ L{CalDAVFile} resource which provisions calendar home collections as needed.
+ """
+ def __init__(self, path, directory, url):
"""
@param path: the path to the file which will back the resource.
+ @param directory: an L{IDirectoryService} to provision calendars from.
"""
- super(CalendarHomeFile, self).__init__(path)
+ assert url.endswith("/"), "Collection URL must end in '/'"
- assert self.exists(), "%s should exist" % (self,)
- assert self.isCollection(), "%s should be a collection" % (self,)
+ super(CalendarHomeProvisioningFile, self).__init__(path)
+ self.directory = IDirectoryService(directory)
+ self._url = url
+
+ # FIXME: Smells like a hack
+ directory.calendarHomesCollection = self
+
+ # self.provision()
+
# Create children
- for name, clazz in (
- ("inbox" , ScheduleInboxFile),
- ("outbox", ScheduleOutboxFile),
- ):
- child_fp = self.fp.child(name)
- if not child_fp.exists(): child_fp.makedirs()
- self.putChild(name, clazz(child_fp.path))
+ for recordType in self.directory.recordTypes():
+ self.putChild(recordType, CalendarHomeTypeProvisioningFile(self.fp.child(recordType).path, self, recordType))
+ # def provision(self):
+ # self.fp.restat(False)
+ # if not self.fp.exists():
+ # self.fp.makedirs()
+ # self.fp.restat(False)
+
+ def url(self):
+ return self._url
+
def createSimilarFile(self, path):
- if path == self.fp.path:
- return self
- else:
- return CalDAVFile(path)
+ raise HTTPError(responsecode.NOT_FOUND)
def getChild(self, name):
- # This avoids finding case variants of put children on case-insensitive filesystems.
- if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
+ # self.provision()
+
+ children = self.putChildren
+ if name not in children and name.lower() in (x.lower() for x in children):
+ # This avoids finding case variants of put children on case-insensitive filesystems.
return None
+ else:
+ return children.get(name, None)
- return super(CalendarHomeFile, self).getChild(name)
+ def listChildren(self):
+ return self.directory.recordTypes()
+ def principalCollections(self):
+ # FIXME: directory.principalCollection smells like a hack
+ # See DirectoryPrincipalProvisioningResource.__init__()
+ return self.directory.principalCollection.principalCollections()
+
+ def homeForDirectoryRecord(self, record):
+ return self.getChild(record.recordType).getChild(record.shortName)
+
##
- # Quota
+ # ACL
##
- def hasQuotaRoot(self, request):
- """
- @return: a C{True} if this resource has quota root, C{False} otherwise.
- """
- return self.hasDeadProperty(TwistedQuotaRootProperty) or CalendarHomeFile.quotaLimit is not None
-
- def quotaRoot(self, request):
- """
- @return: a C{int} containing the maximum allowed bytes if this collection
- is quota-controlled, or C{None} if not quota controlled.
- """
- if self.hasDeadProperty(TwistedQuotaRootProperty):
- return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
- else:
- return CalendarHomeFile.quotaLimit
+ def defaultAccessControlList(self):
+ return readOnlyACL
-class CalendarHomeProvisioningFile (CalDAVFile):
+class CalendarHomeTypeProvisioningFile (ReadOnlyResourceMixIn, DAVFile):
"""
- L{CalDAVFile} resource which provisions calendar home collections as needed.
+ L{CalDAVFile} resource which provisions calendar home collections of a specific
+ record type as needed.
"""
- calendarHomeClass = CalendarHomeFile
-
- def __init__(self, path):
+ def __init__(self, path, parent, recordType):
"""
@param path: the path to the file which will back the resource.
+ @param directory: an L{IDirectoryService} to provision calendars from.
+ @param recordType: the directory record type to provision.
"""
- super(CalendarHomeProvisioningFile, self).__init__(path)
+ super(CalendarHomeTypeProvisioningFile, self).__init__(path)
- def hasChild(self, name):
- """
- @return: C{True} if this resource has a child with the given name,
- C{False} otherwise.
- """
- return name in self.listChildren()
+ self.directory = parent.directory
+ self.recordType = recordType
+ self._parent = parent
- def locateChild(self, request, segments):
- return locateExistingChild(self, request, segments)
+ self.provision()
- def getChild(self, name):
- if name == "": return self
+ def provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.makedirs()
+ self.fp.restat(False)
- # Avoid case variants when allocating resources
- if not self.hasChild(name):
- return None
+ def url(self):
+ return joinURL(self._parent.url(), self.recordType)
- child_fp = self.fp.child(name)
- if child_fp.exists():
- assert child_fp.isdir()
+ def createSimilarFile(self, path):
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ def getChild(self, name, record=None):
+ self.provision()
+
+ if name == "":
+ return self
+
+ if record is None:
+ record = self.directory.recordWithShortName(self.recordType, name)
+ if record is None:
+ return None
else:
- assert self.exists()
- assert self.isCollection()
+ assert name is None
+ name = record.shortName
- child_fp.makedirs()
+ return CalendarHomeFile(self.fp.child(name).path, self, record)
- return self.calendarHomeClass(child_fp.path)
+ def listChildren(self):
+ return (record.shortName for record in self.directory.listRecords(self.recordType))
- def createSimilarFile(self, path):
- raise NotImplementedError("Not allowed")
+ ##
+ # ACL
+ ##
- def http_PUT (self, request): return responsecode.FORBIDDEN
- def http_MKCOL (self, request): return responsecode.FORBIDDEN
- def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
+ def defaultAccessControlList(self):
+ return readOnlyACL
-class CalendarPrincipalFile (CalendarPrincipalResource, CalDAVFile):
+ def principalCollections(self):
+ return self._parent.principalCollections()
+
+class CalendarHomeFile (CalDAVFile):
"""
- Calendar principal resource.
+ L{CalDAVFile} calendar home collection resource.
"""
- def __init__(self, path, url):
+ # A global quota limit for all calendar homes. Either a C{int} (size in bytes) to limit
+ # quota to that size, or C{None} for no limit.
+ quotaLimit = None
+
+ def __init__(self, path, parent, record):
"""
@param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. This is the url which
- will be returned by L{principalURL}.
"""
- super(CalendarPrincipalFile, self).__init__(path)
+ super(CalendarHomeFile, self).__init__(path)
- self._url = url
+ self.record = record
+ self._parent = parent
- def createSimilarFile(self, path):
- return self.__class__(path, self._url)
+ self.provision()
- ##
- # ACL
- ##
+ # Cache children which must be of a specific type
+ for name, cls in (
+ ("inbox" , ScheduleInboxFile),
+ ("outbox", ScheduleOutboxFile),
+ ):
+ self.putChild(name, cls(self.fp.child(name).path, self))
- def alternateURIs(self):
- return ()
+ def provision(self):
+ self.fp.restat(False)
+ if not self.fp.exists():
+ assert self._parent.exists()
+ assert self._parent.isCollection()
+ self.fp.makedirs()
+ self.fp.restat(False)
- def principalURL(self):
- return self._url
+ # # Create a calendar collection
+ # calendarURLs = []
+ # for calendar in ("calendar",):
+ # childURL = joinURL(self.url(), calendar)
+ # child = CalDAVFile(os.path.join(self.fp.path, calendar))
+ # c = child.createCalendarCollection()
+ # assert c.called # FIXME: (This is not valid!)
+ # c = c.result
+ # calendarURLs.append(childURL)
+ # child.setAccessControlList(
+ # davxml.ACL(
+ # davxml.ACE(
+ # davxml.Principal(davxml.Authenticated()),
+ # davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+ # TwistedACLInheritable(),
+ # ),
+ # )
+ # )
+ #
+ # # Set calendar-free-busy-set on inbox
+ # inbox = self.getChild("inbox")
+ # inbox.writeDeadProperty(caldavxml.CalendarFreeBusySet(*[davxml.HRef(url) for url in calendarURLs]))
- def groupMembers(self):
- return ()
+ # Do drop box
+ if self.record.recordType == "user":
+ from twistedcaldav.dropbox import DropBox
+ DropBox.provision(self)
+
+ def url(self):
+ return joinURL(self._parent.url(), self.record.shortName)
- def groupMemberships(self):
- return ()
+ def createSimilarFile(self, path):
+ if path == self.fp.path:
+ return self
+ else:
+ return CalDAVFile(path, principalCollections=self.principalCollections())
- ##
- # CalDAV
- ##
+ def getChild(self, name):
+ self.provision()
- def principalUID(self):
- """
- @return: the user id for this principal.
- """
- return self.fp.basename()
+ # This avoids finding case variants of put children on case-insensitive filesystems.
+ if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
+ return None
- def provisionCalendarAccount(self, name, pswd, resetacl, cuaddrs, cuhome, cuhomeacls, quota, cals, autorespond, allowdropbox):
- """
- Provision the principal and a calendar account for it.
-
- @param name: C{str} name (uid) of principal.
- @param pswd: C{str} password for BASIC authentication, or C{None}.
- @param resetacl: C{True} if ACLs on the principal resource should be reset.
- @param cuaddrs: C{list} list of calendar user addresses, or C{None}
- @param cuhome: C{tuple} of (C{str} - URI of calendar home root, L{DAVResource} - resource of home root)
- @param cuhomeacls: L{ACL} acls to use on calendar home when resetting ACLs, or C{None} to use default set.
- @param cals: C{list} list of calendar names to create in the calendar home for this prinicpal.
- @param autorespond: C{True} if iTIP auto-response is required, C{False} otherwise.
- @param allowdropbox: C{True} if drop box should be enabled for this user is drop box is supproted, C{False} otherwise.
- """
-
- if pswd:
- self.writeDeadProperty(TwistedPasswordProperty.fromString(pswd))
- else:
- self.removeDeadProperty(TwistedPasswordProperty())
- if name:
- self.writeDeadProperty(davxml.DisplayName.fromString(name))
- else:
- self.removeDeadProperty(davxml.DisplayName())
- if cuaddrs:
- self.writeDeadProperty(caldavxml.CalendarUserAddressSet(*[davxml.HRef(addr) for addr in cuaddrs]))
- else:
- self.removeDeadProperty(caldavxml.CalendarUserAddressSet())
+ return super(CalendarHomeFile, self).getChild(name)
- if resetacl:
- self.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.HRef.fromString(self._url)),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- ),
- ),
- )
- )
+ ##
+ # ACL
+ ##
- # If the user does not have any calendar user addresses we do not create a calendar home for them
- if not cuaddrs and not cals:
- return
+ def defaultAccessControlList(self):
+ # FIXME: directory.principalCollection smells like a hack
+ # See DirectoryPrincipalProvisioningResource.__init__()
+ myPrincipal = self._parent._parent.directory.principalCollection.principalForRecord(self.record)
- # Create calendar home if we already have the resource, otherwise simply record
- # the URL as the calendar-home-set
- if cuhome[1] is None:
- self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(cuhome[0])))
- else:
- homeURL = joinURL(cuhome[0], self.principalUID())
- home = FilePath(os.path.join(cuhome[1].fp.path, self.principalUID()))
- home_exists = home.exists()
- if not home_exists:
- home.createDirectory()
- home = CalendarHomeFile(home.path)
-
- if resetacl or not home_exists:
- if cuhomeacls:
- home.setAccessControlList(cuhomeacls.acl)
- else:
- home.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- ),
- ),
- davxml.ACE(
- davxml.Principal(davxml.HRef.fromString(self._url)),
- davxml.Grant(
- davxml.Privilege(davxml.All()),
- ),
- TwistedACLInheritable(),
- ),
- )
- )
-
- # Handle quota on calendar home
- home.setQuotaRoot(None, quota)
-
- # Save the calendar-home-set, schedule-inbox and schedule-outbox properties
- self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(homeURL + "/")))
- self.writeDeadProperty(caldavxml.ScheduleInboxURL(davxml.HRef.fromString(joinURL(homeURL, "inbox/"))))
- self.writeDeadProperty(caldavxml.ScheduleOutboxURL(davxml.HRef.fromString(joinURL(homeURL, "outbox/"))))
-
- # Set ACLs on inbox and outbox
- if resetacl or not home_exists:
- inbox = home.getChild("inbox")
- inbox.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(caldavxml.Schedule()),
- ),
- ),
- )
- )
- if autorespond:
- inbox.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
-
- outbox = home.getChild("outbox")
- if outbox.hasDeadProperty(davxml.ACL()):
- outbox.removeDeadProperty(davxml.ACL())
-
- calendars = []
- for calendar in cals:
- childURL = joinURL(homeURL, calendar)
- child = CalDAVFile(os.path.join(home.fp.path, calendar))
- child_exists = child.exists()
- if not child_exists:
- c = child.createCalendarCollection()
- assert c.called
- c = c.result
- calendars.append(childURL)
- if (resetacl or not child_exists):
- child.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(caldavxml.ReadFreeBusy()),
- ),
- TwistedACLInheritable(),
- ),
- )
- )
-
- # Set calendar-free-busy-set on Inbox if not already present
- inbox = home.getChild("inbox")
- if not inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet()):
- fbset = caldavxml.CalendarFreeBusySet(*[davxml.HRef.fromString(uri) for uri in calendars])
- inbox.writeDeadProperty(fbset)
-
- # Do drop box if requested
- if allowdropbox:
- from twistedcaldav.dropbox import DropBox
- DropBox.provision(self, (homeURL, home))
+ return davxml.ACL(
+ # DAV:read access for authenticated users.
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(davxml.Read())),
+ ),
+ # Inheritable DAV:all access for the resource's associated principal.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
+ davxml.Grant(davxml.Privilege(davxml.All())),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+ def principalCollections(self):
+ return self._parent.principalCollections()
-class CalendarPrincipalCollectionFile (CalendarPrincipalCollectionResource, DAVFile):
- """
- L{DAVFile} resource which provisions user L{CalendarPrincipalFile} resources
- as needed.
- """
- def __init__(self, path, url):
- """
- @param path: the path to the file which will back the resource.
- @param url: the primary URL for the resource. Provisioned child
- resources will use a URL based on C{url} as their primary URLs.
- """
- CalendarPrincipalCollectionResource.__init__(self, url)
- DAVFile.__init__(self, path)
+ ##
+ # Quota
+ ##
- def initialize(self, homeuri, home):
+ def hasQuotaRoot(self, request):
"""
- May be called during repository account initialization.
- This implementation does nothing.
-
- @param homeuri: C{str} uri of the calendar home root.
- @param home: L{DAVFile} of the calendar home root.
+ @return: a C{True} if this resource has quota root, C{False} otherwise.
"""
- pass
+ return self.hasDeadProperty(TwistedQuotaRootProperty) or CalendarHomeFile.quotaLimit is not None
- def createSimilarFile(self, path):
- if path == self.fp.path:
- return self
- else:
- # TODO: Fix this - not sure how to get URI for second argument of __init__
- return CalendarPrincipalFile(path, joinURL(self.principalCollectionURL(), basename(path)))
-
- def principalSearchPropertySet(self):
+ def quotaRoot(self, request):
"""
- See L{IDAVResource.principalSearchPropertySet}.
-
- This implementation returns None. Principal collection resources MUST override
- and return their own suitable response.
-
+ @return: a C{int} containing the maximum allowed bytes if this collection
+ is quota-controlled, or C{None} if not quota controlled.
"""
- return davxml.PrincipalSearchPropertySet(
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- davxml.DisplayName()
- ),
- davxml.Description(
- davxml.PCDATAElement("Display Name"),
- **{"xml:lang":"en"}
- ),
- ),
- davxml.PrincipalSearchProperty(
- davxml.PropertyContainer(
- caldavxml.CalendarUserAddressSet()
- ),
- davxml.Description(
- davxml.PCDATAElement("Calendar User Addresses"),
- **{"xml:lang":"en"}
- ),
- ),
- )
+ if self.hasDeadProperty(TwistedQuotaRootProperty):
+ return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
+ else:
+ return CalendarHomeFile.quotaLimit
- def http_PUT (self, request): return responsecode.FORBIDDEN
- def http_MKCOL (self, request): return responsecode.FORBIDDEN
- def http_MKCALENDAR (self, request): return responsecode.FORBIDDEN
-
##
# Utilities
##
@@ -951,6 +717,83 @@
# Otherwise, there is no child
return (None, ())
+# DAV:read access for authenticated users.
+readOnlyACL = davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(davxml.Read())),
+ davxml.Protected(),
+ ),
+)
+
+def _schedulePrivilegeSet():
+ edited = False
+
+ top_supported_privileges = []
+
+ for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+ all_privilege = supported_privilege.childOfType(davxml.Privilege)
+ if isinstance(all_privilege.children[0], davxml.All):
+ all_description = supported_privilege.childOfType(davxml.Description)
+ all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+ all_supported_privileges.append(
+ davxml.SupportedPrivilege(
+ davxml.Privilege(caldavxml.Schedule()),
+ davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
+ ),
+ )
+ top_supported_privileges.append(
+ davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+ )
+ edited = True
+ else:
+ top_supported_privileges.append(supported_privilege)
+
+ assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
+
+ return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+schedulePrivilegeSet = _schedulePrivilegeSet()
+
+def _calendarPrivilegeSet ():
+ edited = False
+
+ top_supported_privileges = []
+
+ for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+ all_privilege = supported_privilege.childOfType(davxml.Privilege)
+ if isinstance(all_privilege.children[0], davxml.All):
+ all_description = supported_privilege.childOfType(davxml.Description)
+ all_supported_privileges = []
+ for all_supported_privilege in supported_privilege.childrenOfType(davxml.SupportedPrivilege):
+ read_privilege = all_supported_privilege.childOfType(davxml.Privilege)
+ if isinstance(read_privilege.children[0], davxml.Read):
+ read_description = all_supported_privilege.childOfType(davxml.Description)
+ read_supported_privileges = list(all_supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+ read_supported_privileges.append(
+ davxml.SupportedPrivilege(
+ davxml.Privilege(caldavxml.ReadFreeBusy()),
+ davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
+ ),
+ )
+ all_supported_privileges.append(
+ davxml.SupportedPrivilege(read_privilege, read_description, *read_supported_privileges)
+ )
+ edited = True
+ else:
+ all_supported_privileges.append(all_supported_privilege)
+ top_supported_privileges.append(
+ davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+ )
+ else:
+ top_supported_privileges.append(supported_privilege)
+
+ assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for calendarPrivilegeSet"
+
+ return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+calendarPrivilegeSet = _calendarPrivilegeSet()
+
##
# Attach methods
##
Modified: CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py 2006-12-01 18:36:35 UTC (rev 636)
+++ CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py 2006-12-01 19:18:19 UTC (rev 637)
@@ -46,7 +46,8 @@
uri = "/calendar_make"
path = os.path.join(self.docroot, uri[1:])
- if os.path.exists(path): rmdir(path)
+ if os.path.exists(path):
+ rmdir(path)
def do_test(response):
response = IResponse(response)
@@ -72,7 +73,8 @@
uri = "/calendar_prop"
path = os.path.join(self.docroot, uri[1:])
- if os.path.exists(path): rmdir(path)
+ if os.path.exists(path):
+ rmdir(path)
def do_test(response):
response = IResponse(response)
@@ -120,10 +122,10 @@
mk = caldavxml.MakeCalendar(
davxml.Set(
davxml.PropertyContainer(
- davxml.DisplayName.fromString("Lisa's Events"),
- caldavxml.CalendarDescription.fromString("Calendar restricted to events."), # FIXME: lang=en
+ davxml.DisplayName("Lisa's Events"),
+ caldavxml.CalendarDescription("Calendar restricted to events."), # FIXME: lang=en
caldavxml.SupportedCalendarComponentSet(caldavxml.CalendarComponent(name="VEVENT")),
- caldavxml.CalendarTimeZone.fromString(
+ caldavxml.CalendarTimeZone(
"""BEGIN:VCALENDAR
PRODID:-//Example Corp.//CalDAV Client//EN
VERSION:2.0
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061201/fd7a3730/attachment.html
More information about the calendarserver-changes
mailing list