[CalendarServer-changes] [5482] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Apr 15 14:03:22 PDT 2010


Revision: 5482
          http://trac.macosforge.org/projects/calendarserver/changeset/5482
Author:   cdaboo at apple.com
Date:     2010-04-15 14:03:20 -0700 (Thu, 15 Apr 2010)
Log Message:
-----------
CardDAV global addressbook support. Some addition CardDAV specific clean-up.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/twext/web2/responsecode.py
    CalendarServer/trunk/twistedcaldav/carddavxml.py
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/directory/addressbook.py
    CalendarServer/trunk/twistedcaldav/directory/calendar.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/linkresource.py

Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -344,7 +344,7 @@
 
 
 
-class SocketGroupOwnership(BaseTestCase):
+class SocketGroupOwnership(TestCase):
     """
     Tests for L{GroupOwnedUNIXServer}.
     """

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -25,7 +25,7 @@
 from time import sleep
 
 from twisted.python.reflect import namedClass
-from twisted.internet import reactor
+from twisted.internet.reactor import addSystemEventTrigger
 from twisted.cred.portal import Portal
 from twext.web2.http_headers import Headers
 from twext.web2.dav import auth
@@ -47,7 +47,8 @@
 from twistedcaldav.notify import installNotificationClient
 from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
 from twistedcaldav.simpleresource import SimpleResource
-from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.static import CalendarHomeProvisioningFile,\
+    GlobalAddressBookFile
 from twistedcaldav.static import IScheduleInboxFile
 from twistedcaldav.static import TimezoneServiceFile
 from twistedcaldav.static import AddressBookHomeProvisioningFile, DirectoryBackedAddressBookFile
@@ -91,6 +92,7 @@
     webAdminResourceClass        = WebAdminResource
     addressBookResourceClass     = AddressBookHomeProvisioningFile
     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookFile
+    globalAddressBookResourceClass          = GlobalAddressBookFile
 
     #
     # Setup the Directory
@@ -290,10 +292,7 @@
                 directoryPath,
                 principalCollections=(principalCollection,)
             )
-            # do this after process is owned by carddav user, not root.  XXX
-            # this should be fixed to execute at a different stage of service
-            # startup entirely.
-            reactor.callLater(1.0, directoryBackedAddressBookCollection.provisionDirectory)
+            addSystemEventTrigger("after", "startup", directoryBackedAddressBookCollection.provisionDirectory)
         else:
             # remove /directory from previous runs that may have created it
             try:
@@ -303,6 +302,16 @@
                 if e.errno != errno.ENOENT:
                     log.error("Could not delete: %s : %r" %  (directoryPath, e,))
 
+        if config.GlobalAddressBook.Enabled:
+            log.info("Setting up global address book collection: %r" % (globalAddressBookResourceClass,))
+
+            globalAddressBookCollection = globalAddressBookResourceClass(
+                os.path.join(config.DocumentRoot, config.GlobalAddressBook.Name),
+                principalCollections=(principalCollection,)
+            )
+            if not globalAddressBookCollection.exists():
+                addSystemEventTrigger("after", "startup", globalAddressBookCollection.createAddressBookCollection)
+
     log.info("Setting up root resource: %r" % (rootResourceClass,))
 
     root = rootResourceClass(
@@ -322,6 +331,8 @@
         root.putChild('addressbooks', addressBookCollection)
         if config.DirectoryAddressBook.Enabled:
             root.putChild(config.DirectoryAddressBook.name, directoryBackedAddressBookCollection)
+        if config.GlobalAddressBook.Enabled:
+            root.putChild(config.GlobalAddressBook.Name, globalAddressBookCollection)            
 
     # /.well-known
     if config.EnableWellKnown:

Modified: CalendarServer/trunk/twext/web2/responsecode.py
===================================================================
--- CalendarServer/trunk/twext/web2/responsecode.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twext/web2/responsecode.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -71,6 +71,7 @@
 SERVICE_UNAVAILABLE             = 503
 GATEWAY_TIMEOUT                 = 504
 HTTP_VERSION_NOT_SUPPORTED      = 505
+LOOP_DETECTED                   = 506
 INSUFFICIENT_STORAGE_SPACE      = 507
 NOT_EXTENDED                    = 510
 
@@ -116,7 +117,7 @@
     REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large",
     REQUEST_URI_TOO_LONG: "Request-URI Too Long",
     UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
-    REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable",
+    REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range Not Satisfiable",
     EXPECTATION_FAILED: "Expectation Failed",
     UNPROCESSABLE_ENTITY: "Unprocessable Entity",
     LOCKED: "Locked",
@@ -128,7 +129,8 @@
     BAD_GATEWAY: "Bad Gateway",
     SERVICE_UNAVAILABLE: "Service Unavailable",
     GATEWAY_TIMEOUT: "Gateway Time-out",
-    HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported",
+    HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported",
+    LOOP_DETECTED: "Loop In Linked or Bound Resource",
     INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space",
     NOT_EXTENDED: "Not Extended"
     }

Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/carddavxml.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -151,7 +151,7 @@
     hidden = True
     protected = True
 
-    allowed_children = { (carddav_namespace, "addressbook-data"): (0, None) }
+    allowed_children = { (carddav_namespace, "address-data-type"): (0, None) }
 
 class MaxResourceSize (CardDAVTextElement):
     """
@@ -221,6 +221,19 @@
         self.filter = filter
         self.limit = limit
 
+class AddressDataType (CardDAVEmptyElement):
+    """
+    Defines which parts of a address component object should be returned by a
+    report.
+    (CardDAV, section 6.2.2)
+    """
+    name = "address-data-type"
+
+    allowed_attributes = {
+        "content-type": False,
+        "version"     : False,
+    }
+
 class AddressData (CardDAVElement):
     """
     Defines which parts of a address component object should be returned by a

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -28,7 +28,7 @@
 from twext.web2.dav.element.base import twisted_private_namespace
 from twext.web2.dav import davxml
 
-from twistedcaldav import caldavxml
+from twistedcaldav import caldavxml, carddavxml
 from twistedcaldav.ical import Component as iComponent
 
 from vobject.icalendar import utc
@@ -891,4 +891,6 @@
 davxml.ResourceType.ischeduleinbox = davxml.ResourceType(IScheduleInbox())
 davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())
 davxml.ResourceType.notification = davxml.ResourceType(davxml.Collection(), Notification())
-davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
+davxml.ResourceType.sharedownercalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
+davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), Shared())
+davxml.ResourceType.sharedaddressbook = davxml.ResourceType(davxml.Collection(), carddavxml.AddressBook(), Shared())

Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -25,6 +25,7 @@
     "DirectoryAddressBookHomeTypeProvisioningResource",
     "DirectoryAddressBookHomeUIDProvisioningResource",
     "DirectoryAddressBookHomeResource",
+    "GlobalAddressBookResource",
 ]
 
 from twext.python.log import Logger
@@ -377,3 +378,49 @@
             is quota-controlled, or C{None} if not quota controlled.
         """
         return config.UserQuota if config.UserQuota != 0 else None
+
+class GlobalAddressBookResource (CalDAVResource):
+    """
+    Global address book. All we care about is making sure permissions are setup.
+    """
+
+    def resourceType(self, request):
+        return succeed(davxml.ResourceType.sharedaddressbook)
+
+    def url(self):
+        return joinURL("/", config.GlobalAddressBook.Name, "/")
+
+    def canonicalURL(self, request):
+        return succeed(self.url())
+
+    def defaultAccessControlList(self):
+
+        aces = (
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    davxml.Privilege(davxml.Write()),
+                ),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+           ),
+        )
+        
+        if config.GlobalAddressBook.EnableAnonymousReadAccess:
+            aces += (
+                davxml.ACE(
+                    davxml.Principal(davxml.Unauthenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+               ),
+            )
+        return davxml.ACL(*aces)
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
+        return succeed(self.defaultAccessControlList())

Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -41,7 +41,7 @@
 from twistedcaldav.dropbox import DropBoxHomeResource
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
 from twistedcaldav.freebusyurl import FreeBusyURLResource
-from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.resource import CalDAVResource, CalDAVComplianceMixIn
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
 from twistedcaldav.directory.idirectory import IDirectoryService
 from twistedcaldav.directory.wiki import getWikiACL
@@ -57,6 +57,7 @@
 class DirectoryCalendarProvisioningResource (
     AutoProvisioningResourceMixIn,
     ReadOnlyResourceMixIn,
+    CalDAVComplianceMixIn,
     DAVResource,
 ):
     def defaultAccessControlList(self):

Added: CalendarServer/trunk/twistedcaldav/linkresource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/linkresource.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/linkresource.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -0,0 +1,114 @@
+##
+# Copyright (c) 2010 Apple 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.
+##
+
+from twext.python.log import LoggingMixIn
+
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
+
+from twistedcaldav.resource import CalDAVComplianceMixIn
+from twext.web2.http import HTTPError
+from twext.web2 import responsecode
+from twext.web2.resource import WrapperResource
+
+__all__ = [
+    "LinkResource",
+]
+
+"""
+A resource that is a soft-link to another.
+"""
+
+class LinkResource(CalDAVComplianceMixIn, WrapperResource, LoggingMixIn):
+    """
+    This is similar to a WrapperResource except that we locate our resource dynamically. 
+    """
+    
+    def __init__(self, parent, link_url):
+        self.parent = parent
+        self.linkURL = link_url
+        super(LinkResource, self).__init__(self.parent.principalCollections())
+
+    @inlineCallbacks
+    def linkedResource(self, request):
+        
+        if not hasattr(self, "_linkedResource"):
+            self._linkedResource = (yield request.locateResource(self.linkURL))
+
+        if self._linkedResource is None:
+            raise HTTPError(responsecode.NOT_FOUND)
+            
+        returnValue(self._linkedResource)
+
+    def isCollection(self):
+        return True
+
+    @inlineCallbacks
+    def resourceType(self, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.resourceType(request))
+        returnValue(result)
+        
+    def locateChild(self, request, segments):
+        
+        def _defer(result):
+            return (result, segments)
+        d = self.linkedResource(request)
+        d.addCallback(_defer)
+        return d
+
+    def renderHTTP(self, request):
+        return self.linkedResource(request)
+
+    def getChild(self, name):
+        return self._hostedResource.getChild(name)
+
+    @inlineCallbacks
+    def hasProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.hasProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def readProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.readProperty(property, request))
+        returnValue(result)
+
+    @inlineCallbacks
+    def writeProperty(self, property, request):
+        hosted = (yield self.linkedResource(request))
+        result = (yield hosted.writeProperty(property, request))
+        returnValue(result)
+
+class LinkFollowerMixIn(object):
+
+    @inlineCallbacks
+    def locateChild(self, req, segments):
+
+        resource, path = (yield maybeDeferred(super(LinkFollowerMixIn, self).locateChild, req, segments))
+        MAX_LINK_DEPTH = 10
+        ctr = 0
+        seenResource = set()
+        while isinstance(resource, LinkResource):
+            seenResource.add(resource)
+            ctr += 1
+            resource = (yield resource.linkedResource(req))
+            
+            if ctr > MAX_LINK_DEPTH or resource in seenResource:
+                raise HTTPError(responsecode.LOOP_DETECTED)
+        
+        returnValue((resource, path))
+        

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -160,10 +160,22 @@
     def liveProperties(self):
         baseProperties = (
             davxml.Owner.qname(),               # Private Events needs this but it is also OK to return empty
-            caldavxml.SupportedCalendarComponentSet.qname(),
-            caldavxml.SupportedCalendarData.qname(),
         )
         
+        if self.isPseudoCalendarCollection():
+            baseProperties += (
+                caldavxml.SupportedCalendarComponentSet.qname(),
+                caldavxml.SupportedCalendarData.qname(),
+            )
+
+        if self.isAddressBookCollection():
+            baseProperties += (
+                carddavxml.SupportedAddressData.qname(),
+            )
+
+        if config.EnableSyncReport and (self.isPseudoCalendarCollection() or self.isAddressBookCollection()):
+            baseProperties += (davxml.SyncToken.qname(),)
+            
         if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
             baseProperties += (davxml.AddMember.qname(),)
             
@@ -285,6 +297,11 @@
             owner = (yield self.owner(request))
             returnValue(davxml.Owner(owner))
 
+        elif qname == davxml.SyncToken.qname() and config.EnableSyncReport and (
+            self.isPseudoCalendarCollection() or self.isAddressBookCollection()
+        ):
+            returnValue(davxml.SyncToken.fromString(self.getSyncToken()))
+
         elif qname == davxml.AddMember.qname() and config.EnableAddMember and (
             self.isCalendarCollection() or self.isAddressBookCollection()
         ):
@@ -331,6 +348,15 @@
                 opaque = url in fbset
                 self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
 
+        elif qname == carddavxml.SupportedAddressData.qname():
+            # CardDAV, section 6.2.2
+            returnValue(carddavxml.SupportedAddressData(
+                carddavxml.AddressDataType(**{
+                    "content-type": "text/vcard",
+                    "version"     : "3.0",
+                }),
+            ))
+
         elif qname == customxml.Invite.qname():
             result = (yield self.inviteProperty(request))
             returnValue(result)

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -38,6 +38,7 @@
     "AddressBookHomeUIDProvisioningFile",
     "AddressBookHomeFile",
     "DirectoryBackedAddressBookFile",
+    "GLobalAddressBookFile",
 ]
 
 import datetime
@@ -73,6 +74,7 @@
 from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
+from twistedcaldav.linkresource import LinkResource, LinkFollowerMixIn
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.memcacheprops import MemcachePropertyCollection
 from twistedcaldav.freebusyurl import FreeBusyURLResource
@@ -85,7 +87,8 @@
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
 from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
-from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook
+from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook,\
+    GlobalAddressBookResource
 from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
 from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeTypeProvisioningResource
 from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeUIDProvisioningResource
@@ -107,7 +110,21 @@
 
 log = Logger()
 
-class CalDAVFile (CalDAVResource, DAVFile):
+class ReadOnlyResourceMixIn(object):
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    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 ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
+class CalDAVFile (LinkFollowerMixIn, CalDAVResource, DAVFile):
     """
     CalDAV-accessible L{DAVFile} resource.
     """
@@ -555,6 +572,14 @@
         except:
             return fail(Failure())
 
+    def getSyncToken(self):
+        """
+        Return current sync-token value.
+        """
+        assert self.isCollection()
+        
+        return str(self.readDeadProperty(customxml.GETCTag))
+
     def updateCTag(self, token=None):
         assert self.isCollection()
         
@@ -738,7 +763,7 @@
             if test(parent):
                 returnValue(parent)
 
-class AutoProvisioningFileMixIn (AutoProvisioningResourceMixIn):
+class AutoProvisioningFileMixIn (LinkFollowerMixIn, AutoProvisioningResourceMixIn):
     def provision(self):
         self.provisionFile()
         return super(AutoProvisioningFileMixIn, self).provision()
@@ -1050,7 +1075,7 @@
         return super(CalendarHomeFile, self).readProperty(property, request)
 
 
-class ScheduleFile (AutoProvisioningFileMixIn, CalDAVFile):
+class ScheduleFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, CalDAVFile):
     def __init__(self, path, parent):
         super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
 
@@ -1072,17 +1097,6 @@
         """
         return IndexSchedule(self)
 
-    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 ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, "calendar-collection-location-ok")
-        )
-
 class ScheduleInboxFile (ScheduleInboxResource, ScheduleFile):
     """
     Calendar scheduling inbox collection resource.
@@ -1136,7 +1150,7 @@
     def supportedPrivileges(self, request):
         return succeed(sendSchedulePrivilegeSet)
 
-class IScheduleInboxFile (IScheduleInboxResource, CalDAVFile):
+class IScheduleInboxFile (ReadOnlyResourceMixIn, IScheduleInboxResource, CalDAVFile):
     """
     Server-to-server scheduling inbox resource.
     """
@@ -1156,18 +1170,6 @@
         else:
             return responsecode.NOT_FOUND
 
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    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 ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, "calendar-collection-location-ok")
-        )
-
     def deadProperties(self):
         if not hasattr(self, "_dead_properties"):
             self._dead_properties = NonePropertyStore(self)
@@ -1188,7 +1190,7 @@
 
 
 
-class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
+class FreeBusyURLFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
     """
     Free-busy URL resource.
     """
@@ -1208,18 +1210,6 @@
         else:
             return responsecode.NOT_FOUND
 
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    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 ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, "calendar-collection-location-ok")
-        )
-
     ##
     # ACL
     ##
@@ -1268,7 +1258,7 @@
         else:
             return responsecode.NOT_FOUND
 
-class TimezoneServiceFile (TimezoneServiceResource, CalDAVFile):
+class TimezoneServiceFile (ReadOnlyResourceMixIn, TimezoneServiceResource, CalDAVFile):
     def __init__(self, path, parent):
         CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
         TimezoneServiceResource.__init__(self, parent)
@@ -1281,18 +1271,6 @@
         else:
             return responsecode.NOT_FOUND
 
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    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 ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, "calendar-collection-location-ok")
-        )
-
     def deadProperties(self):
         if not hasattr(self, "_dead_properties"):
             self._dead_properties = NonePropertyStore(self)
@@ -1307,7 +1285,7 @@
     def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
         return succeed(None)
 
-class NotificationCollectionFile(AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
+class NotificationCollectionFile(ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
     """
     Notification collection resource.
     """
@@ -1492,6 +1470,21 @@
         CalDAVFile.__init__(self, path)
         DirectoryAddressBookHomeResource.__init__(self, parent, record)
 
+    def provision(self):
+        result = super(AddressBookHomeFile, self).provision()
+        self.provisionLinks()
+        return result
+
+    def provisionLinks(self):
+        
+        if not hasattr(self, "_provisionedLinks"):
+            if config.GlobalAddressBook.Enabled:
+                self.putChild(
+                    config.GlobalAddressBook.Name,
+                    LinkResource(self, joinURL("/", config.GlobalAddressBook.Name, "/")),
+                )
+            self._provisionedLinks = True
+
     def provisionChild(self, name):
  
         if config.Sharing.Enabled:
@@ -1578,7 +1571,7 @@
         return super(AddressBookHomeFile, self).readProperty(property, request)
 
 
-class DirectoryBackedAddressBookFile (DirectoryBackedAddressBookResource, CalDAVFile):
+class DirectoryBackedAddressBookFile (ReadOnlyResourceMixIn, DirectoryBackedAddressBookResource, CalDAVFile):
     """
     Directory-backed address book, supporting directory vcard search.
     """
@@ -1617,6 +1610,22 @@
             ).getChild(name)
        
  
+class GlobalAddressBookFile (ReadOnlyResourceMixIn, GlobalAddressBookResource, CalDAVFile):
+    """
+    Directory-backed address book, supporting directory vcard search.
+    """
+    def __init__(self, path, principalCollections):
+        CalDAVFile.__init__(self, path, principalCollections=principalCollections)
+        self.clientNotifier = ClientNotifier(self)
+
+    def createSimilarFile(self, path):
+        if self.comparePath(path):
+            return self
+        else:
+            similar = CalDAVFile(path, principalCollections=self.principalCollections())
+            similar.clientNotifier = self.clientNotifier
+            return similar
+
 ##
 # Utilities
 ##

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -349,6 +349,12 @@
     },
     "AnonymousDirectoryAddressBookAccess": False, # Anonymous users may access directory address book
 
+    "GlobalAddressBook": {
+        "Enabled":                   True,
+        "Name":                      "global-addressbook",
+        "EnableAnonymousReadAccess": False,
+    },
+
     "MaxAddressBookQueryResults":1000,
     "MaxAddressBookMultigetHrefs":5000,
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-04-15 20:58:40 UTC (rev 5481)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-04-15 21:03:20 UTC (rev 5482)
@@ -102,7 +102,7 @@
         yield self.resource.upgradeToShare(request)
 
         rtype = (yield self.resource.resourceType(request))
-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, customxml.Invite())
         
@@ -123,7 +123,7 @@
         yield self.resource.upgradeToShare(request)
 
         rtype = (yield self.resource.resourceType(request))
-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, customxml.Invite())
         
@@ -136,10 +136,10 @@
     def test_downgradeFromShare(self):
         request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
 
-        self.resource.writeDeadProperty(davxml.ResourceType.sharedcalendar)
+        self.resource.writeDeadProperty(davxml.ResourceType.sharedownercalendar)
         self.resource.writeDeadProperty(customxml.Invite())
         rtype = (yield self.resource.resourceType(request))
-        self.assertEquals(rtype, davxml.ResourceType.sharedcalendar)
+        self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(propInvite, customxml.Invite())
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100415/d801297a/attachment-0001.html>


More information about the calendarserver-changes mailing list