[CalendarServer-changes] [958] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Jan 8 08:48:39 PST 2007
Revision: 958
http://trac.macosforge.org/projects/calendarserver/changeset/958
Author: cdaboo at apple.com
Date: 2007-01-08 08:48:38 -0800 (Mon, 08 Jan 2007)
Log Message:
-----------
Merge of cuproxy-857 branch. This completes cuproxy work.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/directory/calendar.py
CalendarServer/trunk/twistedcaldav/directory/principal.py
CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
CalendarServer/trunk/twistedcaldav/extensions.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/root.py
CalendarServer/trunk/twistedcaldav/schedule.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/test/data/makelargecalendars.py
CalendarServer/trunk/twistedcaldav/test/test_root.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/trunk/twistedcaldav/test/data/makelargefbset.py
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2007-01-08 16:48:38 UTC (rev 958)
@@ -150,6 +150,9 @@
<key>NotificationsEnabled</key>
<true/>
+ <key>CalendarUserProxyEnabled</key>
+ <true/>
+
<key>twistdLocation</key>
<string>../Twisted/bin/twistd</string>
@@ -181,7 +184,7 @@
<key>AdminPrincipals</key>
<array>
- <string>/principals/user/admin</string>
+ <string>/principals/user/admin/</string>
</array>
</dict>
</plist>
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/conf/caldavd.plist 2007-01-08 16:48:38 UTC (rev 958)
@@ -99,6 +99,9 @@
<key>NotificationsEnabled</key>
<true/>
+ <key>CalendarUserProxyEnabled</key>
+ <true/>
+
<key>SACLEnable</key>
<true/>
@@ -127,7 +130,7 @@
<key>AdminPrincipals</key>
<array>
- <string>/principals/user/admin</string>
+ <string>/principals/user/admin/</string>
</array>
</dict>
</plist>
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2007-01-08 16:48:38 UTC (rev 958)
@@ -823,7 +823,7 @@
# HTTP
##
-@@ -1558,7 +1913,7 @@
+@@ -1558,10 +1913,10 @@
"""
DAV resource with no children.
"""
@@ -831,8 +831,31 @@
+ def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
return succeed(None)
- class DAVPrincipalResource (DAVLeafResource):
-@@ -1673,6 +2028,24 @@
+-class DAVPrincipalResource (DAVLeafResource):
++class DAVPrincipalResource (DAVResource):
+ """
+ Resource representing a WebDAV principal. (RFC 3744, section 2)
+ """
+@@ -1571,7 +1926,7 @@
+ # WebDAV
+ ##
+
+- liveProperties = DAVLeafResource.liveProperties + (
++ liveProperties = DAVResource.liveProperties + (
+ (dav_namespace, "alternate-URI-set"),
+ (dav_namespace, "principal-URL" ),
+ (dav_namespace, "group-member-set" ),
+@@ -1584,9 +1939,6 @@
+ def isCollection(self):
+ return False
+
+- def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
+- return succeed(None)
+-
+ def readProperty(self, property, request):
+ def defer():
+ if type(property) is tuple:
+@@ -1673,6 +2025,24 @@
else:
return uri in self.groupMembers()
@@ -857,7 +880,7 @@
class AccessDeniedError(Exception):
def __init__(self, errors):
"""
-@@ -1712,6 +2085,37 @@
+@@ -1712,6 +2082,37 @@
davxml.registerElement(TwistedACLInheritable)
davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/config.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -23,6 +23,7 @@
defaultConfigFile = '/etc/caldavd/caldavd.plist'
defaultConfig = {
+ 'CalendarUserProxyEnabled': True,
'DirectoryService': {
'params': {'node': '/Search'},
'type': 'twistedcaldav.directory.appleopendirectory.OpenDirectoryService'
@@ -60,7 +61,7 @@
'ServicePrincipal': '',
},
},
- 'AdminPrincipals': ['/principals/user/admin']
+ 'AdminPrincipals': ['/principals/user/admin/']
}
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -52,6 +52,22 @@
def getValue(self):
return str(self)
+class CalendarProxyRead (davxml.WebDAVEmptyElement):
+ """
+ A read-only calendar user proxy principal resource.
+ (Apple Extension to CalDAV)
+ """
+ namespace = calendarserver_namespace
+ name = "calendar-proxy-read"
+
+class CalendarProxyWrite (davxml.WebDAVEmptyElement):
+ """
+ A read-write calendar user proxy principal resource.
+ (Apple Extension to CalDAV)
+ """
+ namespace = calendarserver_namespace
+ name = "calendar-proxy-write"
+
class TwistedCalendarPrincipalURI(davxml.WebDAVTextElement):
"""
Contains the calendarPrincipalURI value for a directory record corresponding to a principal.
@@ -199,3 +215,5 @@
davxml.ResourceType.dropboxhome = davxml.ResourceType(davxml.Collection(), DropBoxHome())
davxml.ResourceType.dropbox = davxml.ResourceType(davxml.Collection(), DropBox())
davxml.ResourceType.notifications = davxml.ResourceType(davxml.Collection(), Notifications())
+davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
+davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -81,6 +81,11 @@
# See DirectoryPrincipalProvisioningResource.__init__()
return self.directory.principalCollection.principalCollections()
+ def principalForRecord(self, record):
+ # FIXME: directory.principalCollection smells like a hack
+ # See DirectoryPrincipalProvisioningResource.__init__()
+ return self.directory.principalCollection.principalForRecord(record)
+
def homeForDirectoryRecord(self, record):
typeResource = self.getChild(record.recordType)
if typeResource is None:
@@ -161,6 +166,10 @@
def principalCollections(self):
return self._parent.principalCollections()
+ def principalForRecord(self, record):
+ return self._parent.principalForRecord(record)
+
+
class DirectoryCalendarHomeResource (AutoProvisioningResourceMixIn, CalDAVResource):
"""
Calendar home collection resource.
@@ -254,9 +263,9 @@
def defaultAccessControlList(self):
# FIXME: directory.principalCollection smells like a hack
# See DirectoryPrincipalProvisioningResource.__init__()
- myPrincipal = self._parent._parent.directory.principalCollection.principalForRecord(self.record)
+ myPrincipal = self.principalForRecord()
- return davxml.ACL(
+ aces = (
# DAV:read access for authenticated users.
davxml.ACE(
davxml.Principal(davxml.Authenticated()),
@@ -270,10 +279,33 @@
TwistedACLInheritable(),
),
)
+
+ if config.CalendarUserProxyEnabled:
+ aces += (
+ # DAV:read access for this principal's calendar-proxy-read users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-read/"))),
+ davxml.Grant(davxml.Privilege(davxml.Read())),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ # DAV:read/DAV:write access for this principal's calendar-proxy-write users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write/"))),
+ davxml.Grant(davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.Write())),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+ return davxml.ACL(*aces)
+
def principalCollections(self):
return self._parent.principalCollections()
+ def principalForRecord(self):
+ return self._parent.principalForRecord(self.record)
+
##
# Quota
##
Copied: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py (from rev 954, CalendarServer/branches/users/cdaboo/cuproxy-857/twistedcaldav/directory/calendaruserproxy.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -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 calendar user proxy principal.
+"""
+
+__all__ = [
+ "CalendarUserProxyPrincipalResource",
+]
+
+from urllib import unquote
+
+from twisted.internet.defer import succeed
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.element.base import dav_namespace
+from twisted.web2.dav.util import joinURL
+from twisted.web2.http import Response
+from twisted.web2.http_headers import MimeType
+
+from twistedcaldav.extensions import DAVFile
+from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
+from twistedcaldav.resource import CalendarPrincipalResource
+from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.static import AutoProvisioningFileMixIn
+
+import os
+
+class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
+ def defaultAccessControlList(self):
+ aces = (
+ # 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(self.parent.principalURL())),
+ davxml.Grant(davxml.Privilege(davxml.WriteProperties())),
+ davxml.Protected(),
+ ),
+ )
+
+ return davxml.ACL(*aces)
+
+ 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 CalendarUserProxyPrincipalResource (AutoProvisioningFileMixIn, PermissionsMixIn, CalendarPrincipalResource, DAVFile):
+ """
+ Calendar user proxy principal resource.
+ """
+ def __init__(self, path, parent, proxyType):
+ """
+ @param path: the path to the file which will back this resource.
+ @param parent: the parent of this resource.
+ @param proxyType: a C{str} containing the name of the resource.
+ """
+ super(CalendarUserProxyPrincipalResource, self).__init__(path, joinURL(parent.principalURL(), proxyType))
+
+ self.parent = parent
+ self.proxyType = proxyType
+ self._url = joinURL(parent.principalURL(), proxyType)
+ if self.isCollection():
+ self._url += "/"
+
+ # Provision in __init__() because principals are used prior to request
+ # lookups.
+ self.provision()
+
+ def _index(self):
+ """
+ Return the SQL database for this group principal.
+
+ @return: the L{CalendarUserProxyDatabase} for the principal collection.
+ """
+
+ # Get the principal collection we are contained in
+ pcollection = self.parent.parent.parent
+
+ # The db is located in the principal collection root
+ if not hasattr(pcollection, "calendar_user_proxy_db"):
+ setattr(pcollection, "calendar_user_proxy_db", CalendarUserProxyDatabase(pcollection.fp.path))
+ return pcollection.calendar_user_proxy_db
+
+ def resourceType(self):
+ if self.proxyType == "calendar-proxy-read":
+ return davxml.ResourceType.calendarproxyread
+ elif self.proxyType == "calendar-proxy-write":
+ return davxml.ResourceType.calendarproxywrite
+ else:
+ return super(CalendarUserProxyPrincipalResource, self).resourceType()
+
+ def writeProperty(self, property, request):
+ assert isinstance(property, davxml.WebDAVElement)
+
+ if property.qname() == (dav_namespace, "group-member-set"):
+ return self.setGroupMemberSet(property, request)
+
+ return super(CalendarUserProxyPrincipalResource, self).writeProperty(property, request)
+
+ def setGroupMemberSet(self, new_members, request):
+
+ # Break out the list into a set of URIs.
+ members = [str(h) for h in new_members.children]
+ self._index().setGroupMembers(self._url, members)
+ return succeed(True)
+
+ ##
+ # 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 = [
+ """<html>"""
+ """<head>"""
+ """<title>%(title)s</title>"""
+ """<style>%(style)s</style>"""
+ """</head>"""
+ """<body>"""
+ """<div class="directory-listing">"""
+ """<h1>Proxy Principal Details</h1>"""
+ """<pre><blockquote>"""
+ % {
+ "title": unquote(request.uri),
+ "style": self.directoryStyleSheet(),
+ }
+ ]
+
+ output.append("".join((
+ "Directory Information\n"
+ "---------------------\n"
+ "Parent Directory GUID: %s\n" % (self.parent.record.service.guid,),
+ "Realm: %s\n" % (self.parent.record.service.realmName,),
+ "\n"
+ "Parent Principal Information\n"
+ "---------------------\n"
+ "GUID: %s\n" % (self.parent.record.guid,),
+ "Record type: %s\n" % (self.parent.record.recordType,),
+ "Short name: %s\n" % (self.parent.record.shortName,),
+ "Full name: %s\n" % (self.parent.record.fullName,),
+ "\n"
+ "Proxy Principal Information\n"
+ "---------------------\n"
+ "Principal URL: %s\n" % (self.principalURL(),),
+ "\nAlternate URIs:\n" , format_list(self.alternateURIs),
+ "\nGroup members:\n" , format_list(self.groupMembers),
+ )))
+
+ output.append(
+ """</pre></blockquote></div>"""
+ )
+
+ output.append(self.getDirectoryTable("Collection Listing"))
+
+ output.append("</body></html>")
+
+ output = "".join(output)
+ 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", "html", mime_params))
+
+ return response
+
+ ##
+ # DAV
+ ##
+
+ def displayName(self):
+ return self.proxyType
+
+ ##
+ # ACL
+ ##
+
+ def alternateURIs(self):
+ # FIXME: Add API to IDirectoryRecord for getting a record URI?
+ return ()
+
+ def principalURL(self):
+ return self._url
+
+ def principalCollections(self):
+ return self.parent.principalCollections()
+
+ def groupMembers(self):
+ return self._index().getMembers(self._url)
+
+ def groupMemberships(self):
+ return self._index().getMemberships(self._url)
+
+
+class CalendarUserProxyDatabase(AbstractSQLDatabase):
+ """
+ A database to maintain calendar user proxy group memberships.
+
+ SCHEMA:
+
+ Group Database:
+
+ ROW: GROUPNAME, MEMBER
+
+ """
+
+ dbType = "CALENDARUSERPROXY"
+ dbFilename = ".db.calendaruserproxy"
+ dbFormatVersion = "1"
+
+ def __init__(self, path):
+ path = os.path.join(path, CalendarUserProxyDatabase.dbFilename)
+ super(CalendarUserProxyDatabase, self).__init__(path, CalendarUserProxyDatabase.dbFormatVersion)
+
+ def setGroupMembers(self, principalURI, members):
+ """
+ Add a group membership record.
+
+ @param principalURI: the principalURI of the group principal to add.
+ @param members: the list of principalURIs that are members of this group.
+ """
+
+ # Remove what is there, then add it back.
+ self._delete_from_db(principalURI)
+ self._add_to_db(principalURI, members)
+ self._db_commit()
+
+ def removeGroup(self, principalURI):
+ """
+ Remove a group membership record.
+
+ @param principalURI: the principalURI of the group principal to add.
+ """
+ self._delete_from_db(principalURI)
+ self._db_commit()
+
+ def getMembers(self, principalURI):
+ """
+ Return the list of group members for the specified principal.
+ """
+ members = set()
+ for row in self._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", principalURI):
+ members.add(row[0])
+ return members
+
+ def getMemberships(self, principalURI):
+ """
+ Return the list of groups the specified principal is a member of.
+ """
+ members = set()
+ for row in self._db_execute("select GROUPNAME from GROUPS where MEMBER = :1", principalURI):
+ members.add(row[0])
+ return members
+
+ def _add_to_db(self, principalURI, members):
+ """
+ Insert the specified entry into the database.
+
+ @param principalURI: the principalURI of the group principal to remove.
+ @param members: the list of principalURIs that are members of this group.
+ """
+ for member in members:
+ self._db_execute(
+ """
+ insert into GROUPS (GROUPNAME, MEMBER)
+ values (:1, :2)
+ """, principalURI, member
+ )
+
+ def _delete_from_db(self, principalURI):
+ """
+ Deletes the specified entry from the database.
+
+ @param principalURI: the principalURI of the group principal to remove.
+ """
+ self._db_execute("delete from GROUPS where GROUPNAME = :1", principalURI)
+
+ def _db_type(self):
+ """
+ @return: the collection type assigned to this index.
+ """
+ return CalendarUserProxyDatabase.dbType
+
+ def _db_init_data_tables(self, q):
+ """
+ Initialise the underlying database tables.
+ @param q: a database cursor to use.
+ """
+
+ #
+ # GROUPS table
+ #
+ q.execute(
+ """
+ create table GROUPS (
+ GROUPNAME text,
+ MEMBER text
+ )
+ """
+ )
+
+##
+# Utilities
+##
+
+authReadACL = davxml.ACL(
+ # Read access for authenticated users.
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(davxml.Read())),
+ davxml.Protected(),
+ ),
+)
Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -37,6 +37,9 @@
from twisted.web2.dav import davxml
from twisted.web2.dav.util import joinURL
+from twistedcaldav.config import config
+from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
+from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVFile
from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
from twistedcaldav.static import AutoProvisioningFileMixIn
@@ -105,7 +108,7 @@
path = None
if path:
- segments = [unquote(s) for s in path.split("/")]
+ segments = [unquote(s) for s in path.rstrip("/").split("/")]
if segments[0] == "" and len(segments) == 3:
typeResource = self.getChild(segments[1])
if typeResource is not None:
@@ -228,6 +231,8 @@
self.record = record
self.parent = parent
self._url = joinURL(parent.principalCollectionURL(), record.shortName)
+ if self.isCollection():
+ self._url += "/"
# Provision in __init__() because principals are used prior to request
# lookups.
@@ -252,7 +257,23 @@
yield " ** %s **: %s\n" % (e.__class__.__name__, e)
return "".join(genlist())
- output = ("".join((
+ output = [
+ """<html>"""
+ """<head>"""
+ """<title>%(title)s</title>"""
+ """<style>%(style)s</style>"""
+ """</head>"""
+ """<body>"""
+ """<div class="directory-listing">"""
+ """<h1>Principal Details</h1>"""
+ """<pre><blockquote>"""
+ % {
+ "title": unquote(request.uri),
+ "style": self.directoryStyleSheet(),
+ }
+ ]
+
+ output.append("".join((
"Directory Information\n"
"---------------------\n"
"Directory GUID: %s\n" % (self.record.service.guid,),
@@ -273,6 +294,15 @@
"\nCalendar user addresses:\n" , format_list(self.calendarUserAddresses),
)))
+ output.append(
+ """</pre></blockquote></div>"""
+ )
+
+ output.append(self.getDirectoryTable("Collection Listing"))
+
+ output.append("</body></html>")
+
+ output = "".join(output)
if type(output) == unicode:
output = output.encode("utf-8")
mime_params = {"charset": "utf-8"}
@@ -280,7 +310,7 @@
mime_params = {}
response = Response(code=responsecode.OK, stream=output)
- response.headers.setHeader("content-type", MimeType("text", "plain", mime_params))
+ response.headers.setHeader("content-type", MimeType("text", "html", mime_params))
return response
@@ -294,9 +324,6 @@
else:
return self.record.shortName
- def isCollection(self):
- return False
-
##
# ACL
##
@@ -329,11 +356,29 @@
return relatives
+ def _calendar_user_proxy_index(self):
+ """
+ Return the SQL database for calendar user proxies.
+
+ @return: the L{CalendarUserProxyDatabase} for the principal collection.
+ """
+
+ # Get the principal collection we are contained in
+ pcollection = self.parent.parent
+
+ # The db is located in the principal collection root
+ if not hasattr(pcollection, "calendar_user_proxy_db"):
+ setattr(pcollection, "calendar_user_proxy_db", CalendarUserProxyDatabase(pcollection.fp.path))
+ return pcollection.calendar_user_proxy_db
+
def groupMembers(self):
return self._getRelatives("members")
def groupMemberships(self):
- return self._getRelatives("groups")
+ groups = self._getRelatives("groups")
+ if config.CalendarUserProxyEnabled:
+ groups.update(self._calendar_user_proxy_index().getMemberships(self._url))
+ return groups
def principalCollections(self):
return self.parent.principalCollections()
@@ -396,6 +441,29 @@
else:
return None
+ ##
+ # Static
+ ##
+
+ def createSimilarFile(self, path):
+ log.err("Attempt to create clone %r of resource %r" % (path, self))
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ def getChild(self, name, record=None):
+ if name == "":
+ return self
+
+ if config.CalendarUserProxyEnabled and name in ("calendar-proxy-read", "calendar-proxy-write"):
+ return CalendarUserProxyPrincipalResource(self.fp.child(name).path, self, name)
+ else:
+ return None
+
+ def listChildren(self):
+ if config.CalendarUserProxyEnabled:
+ return ("calendar-proxy-read", "calendar-proxy-write")
+ else:
+ return ()
+
##
# Utilities
##
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -118,7 +118,7 @@
recordResource = typeResource.getChild(shortName)
self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
- recordURL = typeURL + shortName
+ recordURL = typeURL + shortName + "/"
self.assertEquals(recordURL, recordResource.principalURL())
principalCollections = recordResource.principalCollections()
Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/extensions.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -40,12 +40,37 @@
from twisted.web2.dav.http import StatusResponse
from twisted.web2.dav.static import DAVFile as SuperDAVFile
from twisted.web2.dav.resource import DAVResource as SuperDAVResource
+from twisted.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
class DAVResource (SuperDAVResource):
"""
Extended L{twisted.web2.dav.resource.DAVResource} implementation.
"""
+class DAVPrincipalResource (SuperDAVPrincipalResource):
+ """
+ Extended L{twisted.web2.dav.static.DAVFile} implementation.
+ """
+ def readProperty(self, property, request):
+ if type(property) is tuple:
+ qname = property
+ else:
+ qname = property.qname()
+
+ if qname == (dav_namespace, "resourcetype"):
+ return succeed(self.resourceType())
+
+ return super(DAVPrincipalResource, self).readProperty(property, request)
+
+ def resourceType(self):
+ # Allow live property to be overriden by dead property
+ if self.deadProperties().contains((dav_namespace, "resourcetype")):
+ return self.deadProperties().get((dav_namespace, "resourcetype"))
+ if self.isCollection():
+ return davxml.ResourceType(davxml.Collection(), davxml.Principal())
+ else:
+ return davxml.ResourceType(davxml.Principal())
+
class DAVFile (SuperDAVFile):
"""
Extended L{twisted.web2.dav.static.DAVFile} implementation.
@@ -70,7 +95,6 @@
return davxml.ResourceType.empty
def render(self, req):
- """You know what you doing."""
if not self.fp.exists():
return responsecode.NOT_FOUND
@@ -131,7 +155,7 @@
"""
Render a directory listing.
"""
- title = "Directory listing for %s" % urllib.unquote(request.path)
+ pagetitle = "Directory listing for %s" % urllib.unquote(request.path)
output = [
"""<html>"""
@@ -140,51 +164,41 @@
"""<style>%(style)s</style>"""
"""</head>"""
"""<body>"""
+ % {
+ "title": pagetitle,
+ "style": self.directoryStyleSheet(),
+ }
+ ]
+
+ output.append(self.getDirectoryTable(urllib.unquote(request.uri)))
+
+ output.append("</body></html>")
+
+ response = Response(200, {}, "".join(output))
+ response.headers.setHeader("content-type", MimeType("text", "html"))
+ return response
+
+ def getDirectoryTable(self, title):
+ """
+ Generate a directory listing table in HTML.
+ """
+
+ output = [
"""<div class="directory-listing">"""
"""<h1>%(title)s</h1>"""
"""<table>"""
"""<tr><th>Name</th> <th>Size</th> <th>Last Modified</th> <th>MIME Type</th></tr>"""
% {
- "title": urllib.unquote(request.uri),
- "style": self.directoryStyleSheet(),
+ "title": title,
}
]
- def orNone(value, default="?", f=None):
- if value is None:
- return default
- elif f is not None:
- return f(value)
- else:
- return value
-
even = False
for name in sorted(self.listChildren()):
child = self.getChild(name)
- url = urllib.quote(name, '/')
- if isinstance(child, SuperDAVFile) and child.isCollection():
- url += "/"
- name += "/"
+ url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name)
- if isinstance(child, MetaDataMixin):
- size = child.contentLength()
- lastModified = child.lastModified()
- contentType = child.contentType()
- else:
- size = None
- lastModified = None
- contentType = None
-
- if self.fp.isdir():
- contentType = "(collection)"
- else:
- contentType = orNone(
- contentType,
- default="-",
- f=lambda m: "%s/%s %s" % (m.mediaType, m.mediaSubtype, m.params)
- )
-
# FIXME: gray out resources that are not readable
output.append(
"""<tr class="%(even)s">"""
@@ -197,25 +211,64 @@
"even": even and "even" or "odd",
"url": url,
"name": name,
- "size": orNone(size),
- "lastModified": orNone(
- lastModified,
- default="",
- f=lambda t: time.strftime("%Y-%b-%d %H:%M", time.localtime(t))
- ),
+ "size": size,
+ "lastModified": lastModified,
"type": contentType,
}
)
even = not even
- output.append("</table></div></body></html>")
+ output.append("</table></div>")
- response = Response(200, {}, "".join(output))
- response.headers.setHeader("content-type", MimeType("text", "html"))
- return response
+ return "".join(output)
-class ReadOnlyResourceMixIn (object):
+ def getChildDirectoryEntry(self, child, name):
+
+ def orNone(value, default="?", f=None):
+ if value is None:
+ return default
+ elif f is not None:
+ return f(value)
+ else:
+ return value
+
+ url = urllib.quote(name, '/')
+ if isinstance(child, SuperDAVFile) and child.isCollection():
+ url += "/"
+ name += "/"
+
+ if isinstance(child, MetaDataMixin):
+ size = child.contentLength()
+ lastModified = child.lastModified()
+ contentType = child.contentType()
+ else:
+ size = None
+ lastModified = None
+ contentType = None
+
+ if self.fp.isdir():
+ contentType = "(collection)"
+ else:
+ contentType = self._orNone(
+ contentType,
+ default="-",
+ f=lambda m: "%s/%s %s" % (m.mediaType, m.mediaSubtype, m.params)
+ )
+
+ return (
+ url,
+ name,
+ orNone(size),
+ orNone(
+ lastModified,
+ default="",
+ f=lambda t: time.strftime("%Y-%b-%d %H:%M", time.localtime(t))
+ ),
+ contentType,
+ )
+
+class ReadOnlyWritePropertiesResourceMixIn (object):
"""
- Read only resource.
+ Read only that will allow writing of properties resource.
"""
readOnlyResponse = StatusResponse(
responsecode.FORBIDDEN,
@@ -225,10 +278,15 @@
def _forbidden(self, request):
return self.readOnlyResponse
- http_DELETE = _forbidden
- http_MOVE = _forbidden
- http_PROPPATCH = _forbidden
- http_PUT = _forbidden
+ http_DELETE = _forbidden
+ http_MOVE = _forbidden
+ http_PUT = _forbidden
+class ReadOnlyResourceMixIn (ReadOnlyWritePropertiesResourceMixIn):
+ """
+ Read only resource.
+ """
+ http_PROPPATCH = ReadOnlyWritePropertiesResourceMixIn._forbidden
+
def writeProperty(self, property, request):
raise HTTPError(self.readOnlyResponse)
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -24,7 +24,6 @@
"CalDAVResource",
"CalendarPrincipalCollectionResource",
"CalendarPrincipalResource",
- "CalendarSchedulingCollectionResource",
"isCalendarCollectionResource",
"isPseudoCalendarCollectionResource",
]
@@ -38,7 +37,7 @@
from twisted.web2 import responsecode
from twisted.web2.dav import davxml
from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
-from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource, DAVPrincipalCollectionResource
+from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource
from twisted.web2.dav.davxml import dav_namespace
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.resource import TwistedACLInheritable
@@ -51,7 +50,7 @@
import twistedcaldav
from twistedcaldav import caldavxml, customxml
-from twistedcaldav.extensions import DAVResource
+from twistedcaldav.extensions import DAVResource, DAVPrincipalResource
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.customxml import calendarserver_namespace
@@ -551,14 +550,15 @@
(calendarserver_namespace, "notifications-URL"),
)
+ def isCollection(self):
+ return True
+
def readProperty(self, property, request):
def defer():
if type(property) is tuple:
qname = property
- sname = "{%s}%s" % property
else:
qname = property.qname()
- sname = property.sname()
namespace, name = qname
@@ -607,6 +607,12 @@
return maybeDeferred(defer)
+ def groupMembers(self):
+ return ()
+
+ def groupMemberships(self):
+ return ()
+
def calendarHomeURLs(self):
if self.hasDeadProperty((caldav_namespace, "calendar-home-set")):
home_set = self.readDeadProperty((caldav_namespace, "calendar-home-set"))
Modified: CalendarServer/trunk/twistedcaldav/root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/root.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/root.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -69,7 +69,7 @@
def _checkSACLCb((authnUser, authzUser)):
# Figure out the "username" from the davxml.Principal object
username = authzUser.children[0].children[0].data
- username = username.split('/')[-1]
+ username = username.rstrip('/').split('/')[-1]
if RootResource.CheckSACL(username, self.saclService) != 0:
return Failure(HTTPError(403))
Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/schedule.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -41,6 +41,7 @@
from twistedcaldav import itip
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.config import config
from twistedcaldav.ical import Component
from twistedcaldav.method import report_common
from twistedcaldav.method.put_common import storeCalendarObjectResource
@@ -56,6 +57,16 @@
Extends L{DAVResource} to provide CalDAV scheduling collection
functionality.
"""
+ def __init__(self, parent):
+ """
+ @param parent: the parent resource of this one.
+ """
+ assert parent is not None
+
+ CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+ self.parent = parent
+
def isCollection(self):
return True
@@ -98,6 +109,23 @@
Extends L{DAVResource} to provide CalDAV functionality.
"""
+
+ def defaultAccessControlList(self):
+ if config.CalendarUserProxyEnabled:
+ myPrincipal = self.parent.principalForRecord()
+
+ return davxml.ACL(
+ # CalDAV:schedule for associated write proxies
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
+ davxml.Grant(
+ davxml.Privilege(caldavxml.Schedule()),
+ ),
+ ),
+ )
+ else:
+ return super(ScheduleOutboxResource, self).defaultAccessControlList()
+
def resourceType(self):
return davxml.ResourceType.scheduleOutbox
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/static.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -496,7 +496,6 @@
class ScheduleFile (AutoProvisioningFileMixIn, CalDAVFile):
def __init__(self, path, parent):
super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
- self.parent = parent
def isCollection(self):
return True
@@ -533,6 +532,10 @@
"""
Calendar scheduling inbox collection resource.
"""
+ def __init__(self, path, parent):
+ ScheduleFile.__init__(self, path, parent)
+ ScheduleInboxResource.__init__(self, parent)
+
def provision(self):
if self.provisionFile():
# FIXME: This should probably be a directory record option that
@@ -551,6 +554,10 @@
"""
Calendar scheduling outbox collection resource.
"""
+ def __init__(self, path, parent):
+ ScheduleFile.__init__(self, path, parent)
+ ScheduleOutboxResource.__init__(self, parent)
+
def __repr__(self):
return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
Modified: CalendarServer/trunk/twistedcaldav/test/data/makelargecalendars.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/makelargecalendars.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/test/data/makelargecalendars.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -20,7 +20,7 @@
import os
-user_max = 20
+user_max = 99
calendars = ("calendar.10", "calendar.100", "calendar.1000",)
for ctr in xrange(1, user_max + 1):
Copied: CalendarServer/trunk/twistedcaldav/test/data/makelargefbset.py (from rev 954, CalendarServer/branches/users/cdaboo/cuproxy-857/twistedcaldav/test/data/makelargefbset.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/makelargefbset.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/makelargefbset.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+##
+# 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
+import xattr
+
+user_max = 99
+calendars = ("calendar.1000",)
+
+for ctr in xrange(1, user_max + 1):
+ path = "calendars/user/user%02d" % (ctr,)
+
+ try: os.makedirs(path)
+ except OSError: pass
+
+ try: os.makedirs(os.path.join(path, "calendar"))
+ except OSError: pass
+
+ for calendar in calendars:
+ inboxname = os.path.join(path, "inbox")
+ attrs = xattr.xattr(inboxname)
+ attrs["WebDAV:{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set"] = """<?xml version='1.0' encoding='UTF-8'?>
+<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>
+ <href xmlns='DAV:'>/calendars/user/user%02d/calendar/</href>
+ <href xmlns='DAV:'>/calendars/user/user%02d/calendar.1000/</href>
+</calendar-free-busy-set>""" % (ctr, ctr,)
Property changes on: CalendarServer/trunk/twistedcaldav/test/data/makelargefbset.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: CalendarServer/trunk/twistedcaldav/test/test_root.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_root.py 2007-01-08 16:43:05 UTC (rev 957)
+++ CalendarServer/trunk/twistedcaldav/test/test_root.py 2007-01-08 16:48:38 UTC (rev 958)
@@ -142,7 +142,7 @@
self.assertEquals(request.authzUser,
davxml.Principal(
- davxml.HRef('/principals/user/dreid')))
+ davxml.HRef('/principals/user/dreid/')))
d = defer.maybeDeferred(resrc.locateChild, request, ['principals'])
d.addCallback(_Cb)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070108/1c02b388/attachment.html
More information about the calendarserver-changes
mailing list