[CalendarServer-changes] [9821] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed Sep 19 07:59:42 PDT 2012


Revision: 9821
          http://trac.calendarserver.org//changeset/9821
Author:   cdaboo at apple.com
Date:     2012-09-19 07:59:42 -0700 (Wed, 19 Sep 2012)
Log Message:
-----------
Upgrade/downgrade of shared collection now only happens when the sharee list changes - MKxxx and PROPPATCH no longer allowed.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/method/mkcol.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py

Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py	2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py	2012-09-19 14:59:42 UTC (rev 9821)
@@ -36,7 +36,7 @@
 
 from twistedcaldav import caldavxml, carddavxml, mkcolxml
 from twistedcaldav.config import config
-from twistedcaldav.resource import isAddressBookCollectionResource,\
+from twistedcaldav.resource import isAddressBookCollectionResource, \
     CalDAVResource
 from twistedcaldav.resource import isPseudoCalendarCollectionResource
 
@@ -77,7 +77,7 @@
 
     if config.EnableCalDAV:
         parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource))
-    
+
         if parent is not None:
             raise HTTPError(StatusResponse(
                 responsecode.FORBIDDEN,
@@ -86,7 +86,7 @@
 
     if config.EnableCardDAV:
         parent = (yield self._checkParents(request, isAddressBookCollectionResource))
-    
+
         if parent is not None:
             raise HTTPError(StatusResponse(
                 responsecode.FORBIDDEN,
@@ -123,7 +123,7 @@
 
         errors = PropertyStatusResponseQueue("PROPPATCH", request.uri, responsecode.NO_CONTENT)
         got_an_error = False
-    
+
         set_supported_component_set = False
         if mkcol.children:
             # mkcol -> set -> prop -> property*
@@ -143,6 +143,7 @@
                                 rtype = "calendar"
                             elif property.childrenOfType(carddavxml.AddressBook):
                                 rtype = "addressbook"
+
             if not rtype:
                 error = "No {DAV:}resourcetype property in MKCOL request body: %s" % (mkcol,)
                 log.err(error)
@@ -151,14 +152,14 @@
                 error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
                 log.err(error)
                 raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-                
+
             # Make sure feature is enabled
             if (rtype == "calendar" and not config.EnableCalDAV or
                 rtype == "addressbook" and not config.EnableCardDAV):
                 error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
                 log.err(error)
                 raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
-            
+
             # Now create the special collection
             if rtype == "calendar":
                 yield self.createCalendar(request)
@@ -166,19 +167,19 @@
                 yield self.createAddressBook(request)
 
             # Now handle other properties
-            for property in mkcol.children[0].children[0].children:
+            for property in properties:
                 try:
                     if rtype == "calendar" and property.qname() == (caldavxml.caldav_namespace, "supported-calendar-component-set"):
                         yield self.setSupportedComponentSet(property)
                         set_supported_component_set = True
-                    else:
+                    elif not isinstance(property, davxml.ResourceType):
                         yield self.writeProperty(property, request)
                 except HTTPError:
                     errors.add(Failure(), property)
                     got_an_error = True
                 else:
                     errors.add(responsecode.OK, property)
-    
+
         if got_an_error:
             # Clean up
             self.transactionError()
@@ -187,15 +188,14 @@
                     code=responsecode.FORBIDDEN,
                     stream=mkcolxml.MakeCollectionResponse(errors.response()).toxml()
             ))
-        
+
         # When calendar collections are single component only, default MKCALENDAR is VEVENT only
         if rtype == "calendar" and not set_supported_component_set and config.RestrictCalendarsToOneComponentType:
             yield self.setSupportedComponents(("VEVENT",))
 
         yield returnValue(responsecode.CREATED)
-    
+
     else:
         # No request body so it is a standard MKCOL
         result = yield super(CalDAVResource, self).http_MKCOL(request)
         returnValue(result)
-

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2012-09-19 14:59:42 UTC (rev 9821)
@@ -48,7 +48,7 @@
 from twext.web2 import responsecode, http, http_headers
 from twext.web2.dav.auth import AuthenticationWrapper as SuperAuthenticationWrapper
 from twext.web2.dav.idav import IDAVPrincipalCollectionResource
-from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource,\
+from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource, \
     davPrivilegeSet
 from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.dav.util import joinURL, parentForURL, normalizeURL
@@ -59,7 +59,7 @@
 
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav import carddavxml
-from twistedcaldav.cache import PropfindCacheMixin, DisabledCacheNotifier,\
+from twistedcaldav.cache import PropfindCacheMixin, DisabledCacheNotifier, \
     CacheStoreNotifier
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.carddavxml import carddav_namespace
@@ -69,7 +69,7 @@
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.directory.internal import InternalDirectoryRecord
-from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
+from twistedcaldav.extensions import DAVResource, DAVPrincipalResource, \
     PropertyNotFoundError, DAVResourceWithChildrenMixin
 from twistedcaldav.ical import Component
 
@@ -113,6 +113,8 @@
             + config.CalDAVComplianceClasses
         )
 
+
+
 class ReadOnlyResourceMixIn (object):
     """
     Read only resource.
@@ -121,13 +123,31 @@
     def writeProperty(self, property, request):
         raise HTTPError(self.readOnlyResponse)
 
-    def http_ACL(self, request):       return responsecode.FORBIDDEN
-    def http_DELETE(self, request):    return responsecode.FORBIDDEN
-    def http_MKCOL(self, request):     return responsecode.FORBIDDEN
-    def http_MOVE(self, request):      return responsecode.FORBIDDEN
-    def http_PROPPATCH(self, request): return responsecode.FORBIDDEN
-    def http_PUT(self, request):       return responsecode.FORBIDDEN
 
+    def http_ACL(self, request):
+        return responsecode.FORBIDDEN
+
+
+    def http_DELETE(self, request):
+        return responsecode.FORBIDDEN
+
+
+    def http_MKCOL(self, request):
+        return responsecode.FORBIDDEN
+
+
+    def http_MOVE(self, request):
+        return responsecode.FORBIDDEN
+
+
+    def http_PROPPATCH(self, request):
+        return responsecode.FORBIDDEN
+
+
+    def http_PUT(self, request):
+        return responsecode.FORBIDDEN
+
+
     def http_MKCALENDAR(self, request):
         return ErrorResponse(
             responsecode.FORBIDDEN,
@@ -135,14 +155,19 @@
             "Resource is read-only",
         )
 
+
+
 class ReadOnlyNoCopyResourceMixIn (ReadOnlyResourceMixIn):
     """
     Read only resource that disallows COPY.
     """
 
-    def http_COPY(self, request): return responsecode.FORBIDDEN
+    def http_COPY(self, request):
+        return responsecode.FORBIDDEN
 
-def _calendarPrivilegeSet ():
+
+
+def _calendarPrivilegeSet():
     edited = False
 
     top_supported_privileges = []
@@ -197,6 +222,7 @@
     return fun
 
 
+
 class CalDAVResource (
         CalDAVComplianceMixIn, SharedCollectionMixin,
         DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
@@ -244,7 +270,7 @@
             # Render a monolithic iCalendar file
             if request.path[-1] != "/":
                 # Redirect to include trailing '/' in URI
-                return RedirectResponse(request.unparseURL(path=urllib.quote(urllib.unquote(request.path), safe=':/')+'/'))
+                return RedirectResponse(request.unparseURL(path=urllib.quote(urllib.unquote(request.path), safe=':/') + '/'))
 
             def _defer(data):
                 response = Response()
@@ -258,7 +284,6 @@
 
         return super(CalDAVResource, self).render(request)
 
-
     _associatedTransaction = None
     _transactionError = False
 
@@ -271,7 +296,7 @@
         @param transaction: the transaction to associate this resource and its
             children with.
 
-        @type transaction: L{txdav.caldav.idav.ITransaction} 
+        @type transaction: L{txdav.caldav.idav.ITransaction}
         """
         # FIXME: needs to reject association with transaction if it's already
         # got one (resources associated with a transaction are not reusable)
@@ -339,8 +364,8 @@
         """
         raise NotImplementedError("%s does not implement newStoreProperties" %
                                   (self,))
-        
-    
+
+
     def storeRemove(self, *a, **kw):
         """
         Remove this resource from storage.
@@ -355,36 +380,40 @@
 
         @param stream: The stream containing the data to be stored.
         @type stream: L{IStream}
-        
+
         @return: a L{Deferred} which fires with an HTTP response.
         @rtype: L{Deferred}
         """
-        raise NotImplementedError("%s does not implement storeStream"  %
+        raise NotImplementedError("%s does not implement storeStream" %
                                   (self,))
 
-    # End transitional new-store interface 
+    # End transitional new-store interface
 
+
     @updateCacheTokenOnCallback
     def http_PROPPATCH(self, request):
         return super(CalDAVResource, self).http_PROPPATCH(request)
 
+
     @updateCacheTokenOnCallback
     def http_DELETE(self, request):
         return super(CalDAVResource, self).http_DELETE(request)
 
+
     @updateCacheTokenOnCallback
     def http_ACL(self, request):
         return super(CalDAVResource, self).http_ACL(request)
 
+
     ##
     # WebDAV
     ##
 
     def liveProperties(self):
         baseProperties = (
-            element.Owner.qname(),               # Private Events needs this but it is also OK to return empty
+            element.Owner.qname(), # Private Events needs this but it is also OK to return empty
         )
-        
+
         if self.isPseudoCalendarCollection():
             baseProperties += (
                 caldavxml.SupportedCalendarComponentSet.qname(),
@@ -407,7 +436,7 @@
         if self.isCalendarCollection():
             baseProperties += (
                 element.ResourceID.qname(),
-                
+
                 # These are "live" properties in the sense of WebDAV, however "live" for twext actually means
                 # ones that are also always present, but the default alarm properties are allowed to be absent
                 # and are in fact stored in the property store.
@@ -440,13 +469,13 @@
             baseProperties += (
                 caldavxml.ScheduleTag.qname(),
             )
-            
+
         if config.EnableSyncReport and (element.Report(element.SyncCollection(),) in self.supportedReports()):
             baseProperties += (element.SyncToken.qname(),)
-            
+
         if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
             baseProperties += (element.AddMember.qname(),)
-            
+
         if config.Sharing.Enabled:
             if config.Sharing.Calendars.Enabled and self.isCalendarCollection():
                 baseProperties += (
@@ -460,9 +489,10 @@
                     customxml.Invite.qname(),
                     customxml.AllowedSharingModes.qname(),
                 )
-                
+
         return super(CalDAVResource, self).liveProperties() + baseProperties
 
+
     def isShadowableProperty(self, qname):
         """
         Shadowable properties are ones on shared resources where a "default" exists until
@@ -474,6 +504,7 @@
             carddavxml.AddressBookDescription.qname(),
         )
 
+
     def isGlobalProperty(self, qname):
         """
         A global property is one that is the same for all users.
@@ -488,12 +519,13 @@
         else:
             return False
 
+
     @inlineCallbacks
     def hasProperty(self, property, request):
         """
         Need to special case schedule-calendar-transp for backwards compatability.
         """
-        
+
         if type(property) is tuple:
             qname = property
         else:
@@ -506,7 +538,7 @@
                 p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
                 if p:
                     returnValue(p)
-                
+
             elif (not self.isGlobalProperty(qname)):
                 result = (yield self._hasSharedProperty(qname, request))
                 returnValue(result)
@@ -514,6 +546,7 @@
         res = (yield self._hasGlobalProperty(property, request))
         returnValue(res)
 
+
     @inlineCallbacks
     def _hasSharedProperty(self, qname, request):
 
@@ -530,11 +563,12 @@
         p = self.deadProperties().contains(qname, uid=ownerPrincipal.principalUID())
         returnValue(p)
 
+
     def _hasGlobalProperty(self, property, request):
         """
         Need to special case schedule-calendar-transp for backwards compatability.
         """
-        
+
         if type(property) is tuple:
             qname = property
         else:
@@ -543,10 +577,11 @@
         # Force calendar collections to always appear to have the property
         if qname == caldavxml.ScheduleCalendarTransp.qname() and self.isCalendarCollection():
             return succeed(True)
-        
+
         else:
             return super(CalDAVResource, self).hasProperty(property, request)
 
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -575,7 +610,6 @@
 
                 returnValue(customxml.PubSubXMPPPushKeyProperty())
 
-
         if isvirt:
             if self.isShadowableProperty(qname):
                 ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
@@ -584,7 +618,7 @@
                     returnValue(p)
                 except PropertyNotFoundError:
                     pass
-                
+
             elif (not self.isGlobalProperty(qname)):
                 result = (yield self._readSharedProperty(qname, request))
                 returnValue(result)
@@ -592,6 +626,7 @@
         res = (yield self._readGlobalProperty(qname, property, request))
         returnValue(res)
 
+
     @inlineCallbacks
     def _readSharedProperty(self, qname, request):
 
@@ -600,6 +635,7 @@
         p = self.deadProperties().get(qname, uid=ownerPrincipal.principalUID())
         returnValue(p)
 
+
     @inlineCallbacks
     def _readGlobalProperty(self, qname, property, request):
 
@@ -698,7 +734,7 @@
 
         elif qname == customxml.Invite.qname():
             if config.Sharing.Enabled and (
-                config.Sharing.Calendars.Enabled and self.isCalendarCollection() or 
+                config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
                 config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
             ):
                 result = (yield self.inviteProperty(request))
@@ -712,7 +748,7 @@
 
         elif qname == customxml.SharedURL.qname():
             isvirt = self.isVirtualShare()
-            
+
             if isvirt:
                 returnValue(customxml.SharedURL(element.HRef.fromString(self._share.hosturl)))
             else:
@@ -721,12 +757,13 @@
         result = (yield super(CalDAVResource, self).readProperty(property, request))
         returnValue(result)
 
+
     @inlineCallbacks
     def writeProperty(self, property, request):
         assert isinstance(property, element.WebDAVElement), (
             "%r is not a WebDAVElement instance" % (property,)
         )
-        
+
         # Per-user Dav props currently only apply to a sharee's copy of a calendar
         isvirt = self.isVirtualShare()
         if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
@@ -734,10 +771,11 @@
             ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
             p = self.deadProperties().set(property, uid=ownerPrincipal.principalUID())
             returnValue(p)
- 
+
         res = (yield self._writeGlobalProperty(property, request))
         returnValue(res)
 
+
     @inlineCallbacks
     def _preProcessWriteProperty(self, property, request, isShare=False):
         if property.qname() == caldavxml.SupportedCalendarComponentSet.qname():
@@ -782,7 +820,7 @@
                     responsecode.FORBIDDEN,
                     "Property %s may only be set on calendar or home collection." % (property,)
                 ))
-                
+
             if not property.valid():
                 raise HTTPError(ErrorResponse(
                     responsecode.CONFLICT,
@@ -799,7 +837,7 @@
 
             # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
             principal = (yield self.resourceOwnerPrincipal(request))
-            
+
             # Map owner to their inbox
             inboxURL = principal.scheduleInboxURL()
             if inboxURL:
@@ -807,62 +845,15 @@
                 myurl = (yield self.canonicalURL(request))
                 inbox.processFreeBusyCalendar(myurl, property.children[0] == caldavxml.Opaque())
 
+
     @inlineCallbacks
     def _writeGlobalProperty(self, property, request):
 
         yield self._preProcessWriteProperty(property, request)
-
-        if property.qname() == element.ResourceType.qname():
-            if self.isCalendarCollection() or self.isAddressBookCollection():
-                sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
-                if sawShare:
-                    if self.isCalendarCollection() and not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
-                        raise HTTPError(StatusResponse(
-                            responsecode.FORBIDDEN,
-                            "Cannot create shared calendars on this server.",
-                        ))
-                    elif self.isAddressBookCollection() and not (config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled):
-                        raise HTTPError(StatusResponse(
-                            responsecode.FORBIDDEN,
-                            "Cannot create shared address books on this server.",
-                        ))
-
-                # Check if adding or removing share
-                shared = (yield self.isShared(request))
-                for child in property.children:
-                    if child.qname() == element.Collection.qname():
-                        break
-                else:
-                    raise HTTPError(StatusResponse(
-                        responsecode.FORBIDDEN,
-                        "Protected property %s may not be set." % (property.sname(),)
-                    ))
-                for child in property.children:
-                    if self.isCalendarCollection and child.qname() == caldavxml.Calendar.qname() or \
-                       self.isAddressBookCollection and child.qname() == carddavxml.AddressBook.qname():
-                        break
-                else:
-                    raise HTTPError(StatusResponse(
-                        responsecode.FORBIDDEN,
-                        "Protected property %s may not be set." % (property.sname(),)
-                    ))
-                sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
-                if not shared and sawShare:
-                    # Owner is trying to share a collection
-                    self.upgradeToShare()
-                elif shared and not sawShare:
-                    # Remove share
-                    yield self.downgradeFromShare(request)
-                returnValue(None)
-            else:
-                # resourcetype cannot be changed but we will allow it to be set to the same value
-                currentType = self.resourceType()
-                if currentType == property:
-                    returnValue(None)
-
         result = (yield super(CalDAVResource, self).writeProperty(property, request))
         returnValue(result)
 
+
     ##
     # ACL
     ##
@@ -874,6 +865,7 @@
         """
         return ""
 
+
     def _set_accessMode(self, value):
         raise NotImplementedError
 
@@ -931,9 +923,10 @@
                 newacls.extend(acls.children)
 
                 acls = element.ACL(*newacls)
- 
+
         returnValue(acls)
 
+
     @inlineCallbacks
     def owner(self, request):
         """
@@ -951,6 +944,7 @@
         else:
             returnValue(None)
 
+
     @inlineCallbacks
     def ownerPrincipal(self, request):
         """
@@ -1011,7 +1005,7 @@
     def displayName(self):
         if self.isAddressBookCollection() and not self.hasDeadProperty((dav_namespace, "displayname")):
             return None
-        
+
         if 'record' in dir(self):
             if self.record.fullName:
                 return self.record.fullName
@@ -1025,15 +1019,18 @@
                 result = self.name()
             return result
 
+
     def name(self):
         return None
 
+
     def resourceID(self):
         if not self.hasDeadProperty(element.ResourceID.qname()):
             uuidval = uuid.uuid4()
             self.writeDeadProperty(element.ResourceID(element.HRef.fromString(uuidval.urn)))
         return str(self.readDeadProperty(element.ResourceID.qname()).children[0])
 
+
     ##
     # CalDAV
     ##
@@ -1044,26 +1041,31 @@
         """
         return self.isSpecialCollection(caldavxml.Calendar)
 
+
     def isAddressBookCollection(self):
         """
         See L{ICalDAVResource.isAddressBookCollection}.
         """
         return self.isSpecialCollection(carddavxml.AddressBook)
 
+
     def isNotificationCollection(self):
         """
         See L{ICalDAVResource.isNotificationCollection}.
         """
         return self.isSpecialCollection(customxml.Notification)
 
+
     def isDirectoryBackedAddressBookCollection(self):       # ATM - temporary fix? (this one worked)
         return False
 
+
     def isSpecialCollection(self, collectiontype):
         """
         See L{ICalDAVResource.isSpecialCollection}.
         """
-        if not self.isCollection(): return False
+        if not self.isCollection():
+            return False
 
         try:
             resourcetype = self.resourceType()
@@ -1074,18 +1076,22 @@
             return False
         return bool(resourcetype.childrenOfType(collectiontype))
 
+
     def isPseudoCalendarCollection(self):
         """
         See L{ICalDAVResource.isPseudoCalendarCollection}.
         """
         return self.isCalendarCollection()
 
+
     def findCalendarCollections(self, depth, request, callback, privileges=None):
         return self.findSpecialCollections(caldavxml.Calendar, depth, request, callback, privileges)
 
+
     def findAddressBookCollections(self, depth, request, callback, privileges=None):
         return self.findSpecialCollections(carddavxml.AddressBook, depth, request, callback, privileges)
 
+
     @inlineCallbacks
     def findSpecialCollectionsFaster(self, type, depth, request, callback, privileges=None):
         assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
@@ -1103,13 +1109,13 @@
                             continue
                     if child.isSpecialCollection(type):
                         callback(child, childpath)
-                        
+
                     # No more regular collections. If we leave this in then dropbox is scanned at depth:infinity
                     # and that is very painful as it requires scanning all calendar resources too. Eventually we need
                     # to fix drop box and probably re-enable this for the generic case.
     #                elif child.isCollection():
     #                    if depth == "infinity":
-    #                        yield child.findSpecialCollectionsFaster(type, depth, request, callback, privileges)                
+    #                        yield child.findSpecialCollectionsFaster(type, depth, request, callback, privileges)
 
     findSpecialCollections = findSpecialCollectionsFaster
 
@@ -1121,7 +1127,7 @@
         @param request:
         @type request:
         """
-        
+
         # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
         principal = (yield self.resourceOwnerPrincipal(request))
         inboxURL = principal.scheduleInboxURL()
@@ -1129,12 +1135,13 @@
             inbox = (yield request.locateResource(inboxURL))
             inbox.processFreeBusyCalendar(request.path, False)
 
+
     @inlineCallbacks
     def movedCalendar(self, request, defaultCalendarType, destination, destination_uri):
         """
         Calendar has been moved. Need to do some extra clean-up.
         """
-        
+
         # For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
         principal = (yield self.resourceOwnerPrincipal(request))
         inboxURL = principal.scheduleInboxURL()
@@ -1144,26 +1151,28 @@
             inbox = (yield request.locateResource(inboxURL))
             inbox.processFreeBusyCalendar(request.path, False)
             inbox.processFreeBusyCalendar(destination_uri, destination.isCalendarOpaque())
-            
+
             # Adjust the default calendar setting if necessary
             if defaultCalendarType is not None:
-                yield inbox.writeProperty(defaultCalendarType(element.HRef(destination_path)), request)               
+                yield inbox.writeProperty(defaultCalendarType(element.HRef(destination_path)), request)
 
+
     def isCalendarOpaque(self):
-        
+
         assert self.isCalendarCollection()
-        
+
         if self.hasDeadProperty((caldav_namespace, "schedule-calendar-transp")):
             property = self.readDeadProperty((caldav_namespace, "schedule-calendar-transp"))
             return property.children[0] == caldavxml.Opaque()
         else:
             return False
 
+
     @inlineCallbacks
     def isDefaultCalendar(self, request):
-        
+
         assert self.isCalendarCollection()
-        
+
         # Not allowed to delete the default calendar
         principal = (yield self.resourceOwnerPrincipal(request))
         inboxURL = principal.scheduleInboxURL()
@@ -1174,6 +1183,7 @@
 
         returnValue(None)
 
+
     @inlineCallbacks
     def iCalendarForUser(self, request):
 
@@ -1207,6 +1217,7 @@
                 return principal
         return None
 
+
     def principalForUID(self, principalUID):
         for principalCollection in self.principalCollections():
             principal = principalCollection.principalForUID(principalUID)
@@ -1220,19 +1231,20 @@
         """
         AddressBook has been moved. Need to do some extra clean-up.
         """
-        
+
         # Adjust the default addressbook setting if necessary
         if defaultAddressBook:
             principal = (yield self.resourceOwnerPrincipal(request))
             home = (yield principal.addressBookHome(request))
             (_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
-            yield home.writeProperty(carddavxml.DefaultAddressBookURL(element.HRef(destination_path)), request)               
+            yield home.writeProperty(carddavxml.DefaultAddressBookURL(element.HRef(destination_path)), request)
 
+
     @inlineCallbacks
     def isDefaultAddressBook(self, request):
-        
+
         assert self.isAddressBookCollection()
-        
+
         # Not allowed to delete the default address book
         principal = (yield self.resourceOwnerPrincipal(request))
         home = (yield principal.addressBookHome(request))
@@ -1244,6 +1256,7 @@
 
         returnValue(False)
 
+
     @inlineCallbacks
     def vCard(self):
         """
@@ -1289,6 +1302,7 @@
             result.append(element.Report(element.SyncCollection(),))
         return result
 
+
     def writeNewACEs(self, newaces):
         """
         Write a new ACL to the resource's property store. We override this for calendar collections
@@ -1316,6 +1330,7 @@
         # Do inherited with possibly modified set of aces
         super(CalDAVResource, self).writeNewACEs(edited_aces)
 
+
     ##
     # Utilities
     ##
@@ -1328,15 +1343,16 @@
         """
         return request.locateResource(parentForURL(uri))
 
+
     @inlineCallbacks
     def canonicalURL(self, request):
-        
+
         if not hasattr(self, "_canonical_url"):
-    
+
             myurl = request.urlForResource(self)
             _ignore_scheme, _ignore_host, path, _ignore_query, _ignore_fragment = urlsplit(normalizeURL(myurl))
             lastpath = path.split("/")[-1]
-            
+
             parent = (yield request.locateResource(parentForURL(myurl)))
             if parent and isinstance(parent, CalDAVResource):
                 canonical_parent = (yield parent.canonicalURL(request))
@@ -1346,6 +1362,7 @@
 
         returnValue(self._canonical_url)
 
+
     ##
     # Quota
     ##
@@ -1355,18 +1372,20 @@
         Quota root only ever set on calendar homes.
         """
         return False
-    
+
+
     def quotaRoot(self, request):
         """
         Quota root only ever set on calendar homes.
         """
-        return None 
+        return None
 
+
     @inlineCallbacks
     def quotaRootResource(self, request):
         """
         Return the quota root for this resource.
-        
+
         @return: L{DAVResource} or C{None}
         """
 
@@ -1390,9 +1409,9 @@
 
         returnValue(result)
 
+
     # Collection sync stuff
 
-
     @inlineCallbacks
     def whatchanged(self, client_token, depth):
         current_token = (yield self.getSyncToken())
@@ -1405,7 +1424,7 @@
                     raise ValueError
                 caluuid, revision = client_token[6:].split("_", 1)
                 revision = int(revision)
-                
+
                 # Check client token validity
                 if caluuid != current_uuid:
                     raise ValueError
@@ -1431,25 +1450,29 @@
 
         returnValue((changed, removed, notallowed, current_token))
 
+
     def _indexWhatChanged(self, revision, depth):
         # Now handled directly by newstore
         raise NotImplementedError
 
+
     @inlineCallbacks
     def getSyncToken(self):
         """
         Return current sync-token value.
         """
-        
+
         internal_token = (yield self.getInternalSyncToken())
         returnValue("data:,%s" % (internal_token,))
 
+
     def getInternalSyncToken(self):
         """
         Return current internal sync-token value.
         """
         raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "Property not supported"))
 
+
     #
     # Stuff from CalDAVFile
     #
@@ -1460,18 +1483,18 @@
         We override the base class to handle the special implicit scheduling weak ETag behavior
         for compatibility with old clients using If-Match.
         """
-        
+
         if config.Scheduling.CalDAV.ScheduleTagCompatibility:
-            
+
             if self.exists() and hasattr(self, "scheduleEtags"):
                 etags = self.scheduleEtags
                 if len(etags) > 1:
                     # This is almost verbatim from twext.web2.static.checkPreconditions
                     if request.method not in ("GET", "HEAD"):
-                        
+
                         # Always test against the current etag first just in case schedule-etags is out of sync
                         etag = (yield self.etag())
-                        etags = (etag, ) + tuple([http_headers.ETag(etag) for etag in etags])
+                        etags = (etag,) + tuple([http_headers.ETag(etag) for etag in etags])
 
                         # Loop over each tag and succeed if any one matches, else re-raise last exception
                         exists = self.exists()
@@ -1481,9 +1504,9 @@
                             try:
                                 http.checkPreconditions(
                                     request,
-                                    entityExists = exists,
-                                    etag = etag,
-                                    lastModified = last_modified,
+                                    entityExists=exists,
+                                    etag=etag,
+                                    lastModified=last_modified,
                                 )
                             except HTTPError, e:
                                 last_exception = e
@@ -1492,7 +1515,7 @@
                         else:
                             if last_exception:
                                 raise last_exception
-            
+
                     # Check per-method preconditions
                     method = getattr(self, "preconditions_" + request.method, None)
                     if method:
@@ -1503,6 +1526,7 @@
         result = (yield super(CalDAVResource, self).checkPreconditions(request))
         returnValue(result)
 
+
     @inlineCallbacks
     def createCalendar(self, request):
         """
@@ -1551,7 +1575,7 @@
                     customxml.MaxCollections(),
                     "Too many calendar collections",
                 ))
-                
+
         returnValue((yield self.createCalendarCollection()))
 
 
@@ -1569,9 +1593,7 @@
         """
         Only implemented by calendar collections; see storebridge.
         """
-        
 
-
     @inlineCallbacks
     def iCalendarFiltered(self, isowner, accessUID=None):
 
@@ -1646,9 +1668,10 @@
                     customxml.MaxCollections(),
                     "Too many address book collections",
                 ))
-                
+
         returnValue((yield self.createAddressBookCollection()))
 
+
     def createAddressBookCollection(self):
         """
         Internal API for creating an addressbook collection.
@@ -1658,9 +1681,10 @@
         """
         return fail(NotImplementedError())
 
+
     @inlineCallbacks
     def vCardRolledup(self, request):
-        # TODO: just catenate all the vCards together 
+        # TODO: just catenate all the vCards together
         yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
 
 
@@ -1697,6 +1721,7 @@
 
         return super(CalDAVResource, self).supportedPrivileges(request)
 
+
     ##
     # Quota
     ##
@@ -1743,6 +1768,7 @@
 #            return succeed(self.fp.getsize())
         return succeed(0)
 
+
     ##
     # Utilities
     ##
@@ -1757,7 +1783,8 @@
         @return: True if the supplied URI is a child resource
                  False if not
         """
-        if uri is None: return False
+        if uri is None:
+            return False
 
         #
         # Parse the URI
@@ -1776,6 +1803,7 @@
 
         return path.startswith(parent) and (len(path) > len(parent)) and (not immediateChild or (path.find("/", len(parent)) == -1))
 
+
     @inlineCallbacks
     def _checkParents(self, request, test):
         """
@@ -1790,13 +1818,16 @@
 
         while True:
             parent_uri = parentForURL(parent_uri)
-            if not parent_uri: break
+            if not parent_uri:
+                break
 
             parent = yield request.locateResource(parent_uri)
 
             if test(parent):
                 returnValue(parent)
 
+
+
 class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
     """
     CalDAV principal collection.
@@ -1806,18 +1837,23 @@
     def isCollection(self):
         return True
 
+
     def isCalendarCollection(self):
         return False
 
+
     def isAddressBookCollection(self):
         return False
 
+
     def isDirectoryBackedAddressBookCollection(self):
         return False
 
+
     def principalForCalendarUserAddress(self, address):
         return None
 
+
     def supportedReports(self):
         """
         Principal collections are the only resources supporting the
@@ -1827,6 +1863,7 @@
         result.append(element.Report(element.PrincipalSearchPropertySet(),))
         return result
 
+
     def principalSearchPropertySet(self):
         return element.PrincipalSearchPropertySet(
             element.PrincipalSearchProperty(
@@ -1835,7 +1872,7 @@
                 ),
                 element.Description(
                     element.PCDATAElement("Display Name"),
-                    **{"xml:lang":"en"}
+                    **{"xml:lang": "en"}
                 ),
             ),
             element.PrincipalSearchProperty(
@@ -1844,11 +1881,13 @@
                 ),
                 element.Description(
                     element.PCDATAElement("Calendar User Addresses"),
-                    **{"xml:lang":"en"}
+                    **{"xml:lang": "en"}
                 ),
             ),
         )
 
+
+
 class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVResourceWithChildrenMixin, DAVPrincipalResource):
     """
     CalDAV principal resource.
@@ -1858,22 +1897,22 @@
     implements(ICalendarPrincipalResource)
 
     def liveProperties(self):
-        
+
         baseProperties = ()
-        
+
         if self.calendarsEnabled():
             baseProperties += (
-                (caldav_namespace, "calendar-home-set"        ),
+                (caldav_namespace, "calendar-home-set"),
                 (caldav_namespace, "calendar-user-address-set"),
-                (caldav_namespace, "schedule-inbox-URL"       ),
-                (caldav_namespace, "schedule-outbox-URL"      ),
-                (caldav_namespace, "calendar-user-type"       ),
-                (calendarserver_namespace, "calendar-proxy-read-for"  ),
-                (calendarserver_namespace, "calendar-proxy-write-for" ),
-                (calendarserver_namespace, "auto-schedule" ),
-                (calendarserver_namespace, "auto-schedule-mode" ),
+                (caldav_namespace, "schedule-inbox-URL"),
+                (caldav_namespace, "schedule-outbox-URL"),
+                (caldav_namespace, "calendar-user-type"),
+                (calendarserver_namespace, "calendar-proxy-read-for"),
+                (calendarserver_namespace, "calendar-proxy-write-for"),
+                (calendarserver_namespace, "auto-schedule"),
+                (calendarserver_namespace, "auto-schedule-mode"),
             )
-        
+
         if self.addressBooksEnabled():
             baseProperties += (carddavxml.AddressBookHomeSet.qname(),)
             if self.directoryAddressBookEnabled():
@@ -1887,18 +1926,23 @@
 
         return super(CalendarPrincipalResource, self).liveProperties() + baseProperties
 
+
     def isCollection(self):
         return True
 
+
     def calendarsEnabled(self):
         return config.EnableCalDAV
 
+
     def addressBooksEnabled(self):
         return config.EnableCardDAV
 
+
     def directoryAddressBookEnabled(self):
         return config.DirectoryAddressBook.Enabled and config.EnableSearchAddressBook
 
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -1984,6 +2028,7 @@
         result = (yield super(CalendarPrincipalResource, self).readProperty(property, request))
         returnValue(result)
 
+
     def calendarFreeBusyURIs(self, request):
         def gotInbox(inbox):
             if inbox is None:
@@ -2008,12 +2053,14 @@
         d.addCallback(gotInbox)
         return d
 
+
     def scheduleInbox(self, request):
         """
         @return: the deferred schedule inbox for this principal.
         """
         return request.locateResource(self.scheduleInboxURL())
 
+
     ##
     # Quota
     ##
@@ -2023,30 +2070,33 @@
         Quota root only ever set on calendar homes.
         """
         return False
-    
+
+
     def quotaRoot(self, request):
         """
         Quota root only ever set on calendar homes.
         """
         return None
 
+
+
 class DefaultAlarmPropertyMixin(object):
     """
     A mixin for use with calendar home and calendars to allow direct access to
     the default alarm properties in a more useful way that using readProperty.
     In particular it will handle inheritance of the property from the home if a
-    calendar does not explicitly have the property. 
+    calendar does not explicitly have the property.
     """
-    
+
     def getDefaultAlarm(self, vevent, timed):
-        
+
         if vevent:
             propname = caldavxml.DefaultAlarmVEventDateTime if timed else caldavxml.DefaultAlarmVEventDate
         else:
             propname = caldavxml.DefaultAlarmVToDoDateTime if timed else caldavxml.DefaultAlarmVToDoDate
-        
+
         if self.isCalendarCollection():
-            
+
             # Get from calendar or inherit from home
             try:
                 prop = self.deadProperties().get(propname.qname())
@@ -2063,6 +2113,8 @@
 
         return str(prop) if prop is not None else None
 
+
+
 class CommonHomeResource(PropfindCacheMixin, SharedHomeMixin, CalDAVResource):
     """
     Logic common to Calendar and Addressbook home resources.
@@ -2114,21 +2166,24 @@
     def _setupProvisions(self):
         pass
 
+
     def postCreateHome(self):
         pass
 
+
     def liveProperties(self):
 
         props = super(CommonHomeResource, self).liveProperties() + (
             (customxml.calendarserver_namespace, "push-transports"),
             (customxml.calendarserver_namespace, "pushkey"),
         )
-        
+
         if config.MaxCollectionsPerHome:
             props += (customxml.MaxCollections.qname(),)
 
         return props
 
+
     def sharesDB(self):
         """
         Retrieve the new-style shares DB wrapper.
@@ -2141,13 +2196,16 @@
     def url(self):
         return joinURL(self.parent.url(), self.name, "/")
 
+
     def canonicalURL(self, request):
         return succeed(self.url())
 
+
     def exists(self):
         # FIXME: tests
         return True
 
+
     def isCollection(self):
         return True
 
@@ -2180,9 +2238,10 @@
     def currentQuotaUse(self, request):
         """
         Get the quota use value
-        """  
+        """
         return maybeDeferred(self._newStoreHome.quotaUsedBytes)
 
+
     def supportedReports(self):
         result = super(CommonHomeResource, self).supportedReports()
         if config.EnableSyncReport and config.EnableSyncReportHome:
@@ -2190,6 +2249,7 @@
             result.append(element.Report(element.SyncCollection(),))
         return result
 
+
     def _mergeSyncTokens(self, hometoken, notificationtoken):
         """
         Merge two sync tokens, choosing the higher revision number of the two,
@@ -2201,6 +2261,7 @@
             hometoken = "%s_%s" % (homekey, notrev,)
         return hometoken
 
+
     def canShare(self):
         raise NotImplementedError
 
@@ -2213,16 +2274,17 @@
         """
         Override to pre-load children in certain collection types for better performance.
         """
-        
+
         if depth == "1":
             yield self._newStoreHome.loadChildren()
-        
+
         result = (yield super(CommonHomeResource, self).findChildrenFaster(
             depth, request, okcallback, badcallback, missingcallback, names, privileges, inherited_aces
         ))
-        
+
         returnValue(result)
-    
+
+
     @inlineCallbacks
     def makeChild(self, name):
         # Try built-in children first
@@ -2264,7 +2326,7 @@
             notifications,
             self,
             self._newStoreHome,
-            principalCollections = self.principalCollections(),
+            principalCollections=self.principalCollections(),
         )
         self.propagateTransaction(similar)
         returnValue(similar)
@@ -2411,6 +2473,7 @@
 
         returnValue((yield super(CommonHomeResource, self).readProperty(property, request)))
 
+
     ##
     # ACL
     ##
@@ -2418,12 +2481,15 @@
     def owner(self, request):
         return succeed(element.HRef(self.principalForRecord().principalURL()))
 
+
     def ownerPrincipal(self, request):
         return succeed(self.principalForRecord())
 
+
     def resourceOwnerPrincipal(self, request):
         return succeed(self.principalForRecord())
 
+
     def defaultAccessControlList(self):
         myPrincipal = self.principalForRecord()
 
@@ -2451,19 +2517,23 @@
 
         # Give all access to config.AdminPrincipals
         aces += config.AdminACEs
-        
+
         return element.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())
 
+
     def principalCollections(self):
         return self.parent.principalCollections()
 
+
     def principalForRecord(self):
         raise NotImplementedError("Subclass must implement principalForRecord()")
 
+
     @inlineCallbacks
     def etag(self):
         """
@@ -2478,15 +2548,19 @@
         else:
             returnValue(None)
 
+
     def lastModified(self):
         return self._newStoreHome.modified() if self._newStoreHome else None
 
+
     def creationDate(self):
         return self._newStoreHome.created() if self._newStoreHome else None
 
+
     def notifierID(self, label="default"):
         self._newStoreHome.notifierID(label)
 
+
     def notifyChanged(self):
         return self._newStoreHome.notifyChanged()
 
@@ -2496,6 +2570,7 @@
     http_MOVE = None
 
 
+
 class CalendarHomeResource(DefaultAlarmPropertyMixin, CommonHomeResource):
     """
     Calendar home collection classmethod.
@@ -2514,11 +2589,11 @@
 
 
     def liveProperties(self):
-        
+
         existing = super(CalendarHomeResource, self).liveProperties()
         existing += (
             caldavxml.SupportedCalendarComponentSets.qname(),
-                
+
             # These are "live" properties in the sense of WebDAV, however "live" for twext actually means
             # ones that are also always present, but the default alarm properties are allowed to be absent
             # and are in fact stored in the property store.
@@ -2535,6 +2610,7 @@
         )
         return existing
 
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -2559,10 +2635,11 @@
             else:
                 prop = caldavxml.SupportedCalendarComponentSets()
             returnValue(prop)
-            
+
         result = (yield super(CalendarHomeResource, self).readProperty(property, request))
         returnValue(result)
 
+
     def _setupProvisions(self):
 
         # Cache children which must be of a specific type
@@ -2614,19 +2691,21 @@
     def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, type):
         """
         Test if there are other child object resources with the specified UID.
-        
+
         Pass through direct to store.
         """
         return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, type)
 
+
     def getCalendarResourcesForUID(self, uid, allow_shared=False):
         """
         Return all child object resources with the specified UID.
-        
+
         Pass through direct to store.
         """
         return self._newStoreHome.getCalendarResourcesForUID(uid, allow_shared)
 
+
     def defaultAccessControlList(self):
         myPrincipal = self.principalForRecord()
 
@@ -2660,7 +2739,7 @@
 
         # Give all access to config.AdminPrincipals
         aces += config.AdminACEs
-        
+
         if config.EnableProxyPrincipals:
             # Server may be read only
             if config.EnableReadOnlyServer:
@@ -2745,6 +2824,7 @@
         returnValue((changed, deleted, notallowed))
 
 
+
 class AddressBookHomeResource (CommonHomeResource):
     """
     Address book home collection resource.
@@ -2763,11 +2843,12 @@
 
 
     def liveProperties(self):
-        
+
         return super(AddressBookHomeResource, self).liveProperties() + (
             carddavxml.DefaultAddressBookURL.qname(),
         )
 
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -2785,15 +2866,16 @@
                 defaultAddressBook = str(defaultAddressBookProperty.children[0])
                 adbk = (yield request.locateResource(str(defaultAddressBook)))
                 if adbk is not None and isAddressBookCollectionResource(adbk) and adbk.exists() and not adbk.isVirtualShare():
-                    returnValue(defaultAddressBookProperty) 
-            
+                    returnValue(defaultAddressBookProperty)
+
             # Default is not valid - we have to try to pick one
             defaultAddressBookProperty = (yield self.pickNewDefaultAddressBook(request))
             returnValue(defaultAddressBookProperty)
-            
+
         result = (yield super(AddressBookHomeResource, self).readProperty(property, request))
         returnValue(result)
 
+
     @inlineCallbacks
     def writeProperty(self, property, request):
         assert isinstance(property, element.WebDAVElement)
@@ -2822,6 +2904,7 @@
 
         yield super(AddressBookHomeResource, self).writeProperty(property, request)
 
+
     def _setupProvisions(self):
 
         # Cache children which must be of a specific type
@@ -2832,12 +2915,15 @@
         if config.GlobalAddressBook.Enabled:
             self._provisionedLinks[config.GlobalAddressBook.Name] = "/addressbooks/public/global/addressbook/"
 
+
     def makeNewStore(self):
         return self._associatedTransaction.addressbookHomeWithUID(self.name, create=True), False     # Don't care about created
 
+
     def canShare(self):
         return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
 
+
     @inlineCallbacks
     def makeRegularChild(self, name):
 
@@ -2893,6 +2979,7 @@
             element.HRef(defaultAddressBookURL))
         )
 
+
     @inlineCallbacks
     def getInternalSyncToken(self):
         # The newstore implementation supports this directly
@@ -2900,7 +2987,7 @@
 
         if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
             notifcationtoken = yield (yield self.getChild("notification")).getInternalSyncToken()
-            
+
             # Merge tokens
             adbkkey, adbkrev = adbktoken.split("_", 1)
             notrev = notifcationtoken.split("_", 1)[1]
@@ -2934,6 +3021,7 @@
         returnValue((changed, deleted, notallowed))
 
 
+
 class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
     """
     Global address book. All we care about is making sure permissions are setup.
@@ -2942,6 +3030,7 @@
     def resourceType(self):
         return element.ResourceType.sharedaddressbook #@UndefinedVariable
 
+
     def defaultAccessControlList(self):
 
         aces = (
@@ -2956,7 +3045,7 @@
                 TwistedACLInheritable(),
            ),
         )
-        
+
         if config.GlobalAddressBook.EnableAnonymousReadAccess:
             aces += (
                 element.ACE(
@@ -2970,11 +3059,13 @@
             )
         return element.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())
 
 
+
 class AuthenticationWrapper(SuperAuthenticationWrapper):
 
     """ AuthenticationWrapper implementation which allows overriding
@@ -2992,6 +3083,7 @@
                 self.overrides[path] = dict([(factory.scheme, factory)
                     for factory in factories])
 
+
     def hook(self, req):
         """ Uses the default credentialFactories unless the request is for
             one of the overridden paths """
@@ -3003,11 +3095,11 @@
         req.credentialFactories = factories
 
 
+
 ##
 # Utilities
 ##
 
-
 def isCalendarCollectionResource(resource):
     try:
         resource = ICalDAVResource(resource)
@@ -3017,6 +3109,7 @@
         return resource.isCalendarCollection()
 
 
+
 def isPseudoCalendarCollectionResource(resource):
     try:
         resource = ICalDAVResource(resource)
@@ -3026,6 +3119,7 @@
         return resource.isPseudoCalendarCollection()
 
 
+
 def isAddressBookCollectionResource(resource):
     try:
         resource = ICalDAVResource(resource)
@@ -3033,4 +3127,3 @@
         return False
     else:
         return resource.isAddressBookCollection()
-

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2012-09-19 14:59:42 UTC (rev 9821)
@@ -461,7 +461,9 @@
                 record.state = "INVALID"
                 yield self.invitesDB().addOrUpdateRecord(record)
 
+        returnValue(len(records))
 
+
     def inviteUserToShare(self, userid, cn, ace, summary, request):
         """ Send out in invite first, and then add this user to the share list
             @param userid:
@@ -715,6 +717,7 @@
         returnValue(result)
 
 
+    @inlineCallbacks
     def _handleInvite(self, request, invitedoc):
         def _handleInviteSet(inviteset):
             userid = None
@@ -772,85 +775,84 @@
                 access = set(access)
             return (userid, access)
 
-        def _autoShare(isShared, request):
-            if not isShared:
-                self.upgradeToShare()
+        setDict, removeDict, updateinviteDict = {}, {}, {}
+        okusers = set()
+        badusers = set()
+        for item in invitedoc.children:
+            if isinstance(item, customxml.InviteSet):
+                userid, cn, access, summary = _handleInviteSet(item)
+                setDict[userid] = (cn, access, summary)
 
-        @inlineCallbacks
-        def _processInviteDoc(_, request):
-            setDict, removeDict, updateinviteDict = {}, {}, {}
+                # Validate each userid on add only
+                uid = (yield self.validUserIDForShare(userid, request))
+                (okusers if uid is not None else badusers).add(userid)
+            elif isinstance(item, customxml.InviteRemove):
+                userid, access = _handleInviteRemove(item)
+                removeDict[userid] = access
+
+                # Treat removed userids as valid as we will fail invalid ones silently
+                okusers.add(userid)
+
+        # Only make changes if all OK
+        if len(badusers) == 0:
             okusers = set()
             badusers = set()
-            for item in invitedoc.children:
-                if isinstance(item, customxml.InviteSet):
-                    userid, cn, access, summary = _handleInviteSet(item)
-                    setDict[userid] = (cn, access, summary)
+            # Special case removing and adding the same user and treat that as an add
+            sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
+            for u in sameUseridInRemoveAndSet:
+                removeACL = removeDict[u]
+                cn, newACL, summary = setDict[u]
+                updateinviteDict[u] = (cn, removeACL, newACL, summary)
+                del removeDict[u]
+                del setDict[u]
+            for userid, access in removeDict.iteritems():
+                result = (yield self.uninviteUserToShare(userid, access, request))
+                # If result is False that means the user being removed was not
+                # actually invited, but let's not return an error in this case.
+                okusers.add(userid)
+            for userid, (cn, access, summary) in setDict.iteritems():
+                result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
+                (okusers if result else badusers).add(userid)
+            for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
+                result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
+                (okusers if result else badusers).add(userid)
 
-                    # Validate each userid on add only
-                    uid = (yield self.validUserIDForShare(userid, request))
-                    (okusers if uid is not None else badusers).add(userid)
-                elif isinstance(item, customxml.InviteRemove):
-                    userid, access = _handleInviteRemove(item)
-                    removeDict[userid] = access
+            # In this case bad items do not prevent ok items from being processed
+            ok_code = responsecode.OK
+        else:
+            # In this case a bad item causes all ok items not to be processed so failed dependency is returned
+            ok_code = responsecode.FAILED_DEPENDENCY
 
-                    # Treat removed userids as valid as we will fail invalid ones silently
-                    okusers.add(userid)
+        # Do a final validation of the entire set of invites
+        numRecords = (yield self.validateInvites(request))
 
-            # Only make changes if all OK
-            if len(badusers) == 0:
-                okusers = set()
-                badusers = set()
-                # Special case removing and adding the same user and treat that as an add
-                sameUseridInRemoveAndSet = [u for u in removeDict.keys() if u in setDict]
-                for u in sameUseridInRemoveAndSet:
-                    removeACL = removeDict[u]
-                    cn, newACL, summary = setDict[u]
-                    updateinviteDict[u] = (cn, removeACL, newACL, summary)
-                    del removeDict[u]
-                    del setDict[u]
-                for userid, access in removeDict.iteritems():
-                    result = (yield self.uninviteUserToShare(userid, access, request))
-                    # If result is False that means the user being removed was not
-                    # actually invited, but let's not return an error in this case.
-                    okusers.add(userid)
-                for userid, (cn, access, summary) in setDict.iteritems():
-                    result = (yield self.inviteUserToShare(userid, cn, access, summary, request))
-                    (okusers if result else badusers).add(userid)
-                for userid, (cn, removeACL, newACL, summary) in updateinviteDict.iteritems():
-                    result = (yield self.inviteUserUpdateToShare(userid, cn, removeACL, newACL, summary, request))
-                    (okusers if result else badusers).add(userid)
+        # Set the sharing state on the collection
+        shared = (yield self.isShared(request))
+        if shared and numRecords == 0:
+            yield self.downgradeFromShare(request)
+        elif not shared and numRecords != 0:
+            self.upgradeToShare()
 
-                # In this case bad items do not prevent ok items from being processed
-                ok_code = responsecode.OK
-            else:
-                # In this case a bad item causes all ok items not to be processed so failed dependency is returned
-                ok_code = responsecode.FAILED_DEPENDENCY
+        # Create the multistatus response - only needed if some are bad
+        if badusers:
+            xml_responses = []
+            xml_responses.extend([
+                element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(ok_code))
+                for userid in sorted(okusers)
+            ])
+            xml_responses.extend([
+                element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(responsecode.FORBIDDEN))
+                for userid in sorted(badusers)
+            ])
 
-            # Do a final validation of the entire set of invites
-            yield self.validateInvites(request)
+            #
+            # Return response
+            #
+            returnValue(MultiStatusResponse(xml_responses))
+        else:
+            returnValue(responsecode.OK)
 
-            # Create the multistatus response - only needed if some are bad
-            if badusers:
-                xml_responses = []
-                xml_responses.extend([
-                    element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(ok_code))
-                    for userid in sorted(okusers)
-                ])
-                xml_responses.extend([
-                    element.StatusResponse(element.HRef(userid), element.Status.fromResponseCode(responsecode.FORBIDDEN))
-                    for userid in sorted(badusers)
-                ])
 
-                #
-                # Return response
-                #
-                returnValue(MultiStatusResponse(xml_responses))
-            else:
-                returnValue(responsecode.OK)
-
-        return self.isShared(request).addCallback(_autoShare, request).addCallback(_processInviteDoc, request)
-
-
     @inlineCallbacks
     def _xmlHandleInviteReply(self, request, docroot):
         yield self.authorize(request, (element.Read(), element.Write()))

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2012-09-19 14:34:40 UTC (rev 9820)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2012-09-19 14:59:42 UTC (rev 9821)
@@ -38,9 +38,9 @@
 class SharingTests(HomeTestCase):
 
     class FakePrincipal(object):
-        
+
         class FakeRecord(object):
-            
+
             def __init__(self, name, cuaddr):
                 self.fullName = name
                 self.guid = name
@@ -54,12 +54,13 @@
 
         def calendarHome(self, request):
             class FakeHome(object):
-                def removeShareByUID(self, request, uid):pass
+                def removeShareByUID(self, request, uid):
+                    pass
             return FakeHome()
-        
+
         def principalURL(self):
             return self.path
-        
+
         def displayName(self):
             return self.displayname
 
@@ -121,7 +122,7 @@
 
 
     @inlineCallbacks
-    def _doPOST(self, body, resultcode = responsecode.OK):
+    def _doPOST(self, body, resultcode=responsecode.OK):
         request = SimpleRequest(self.site, "POST", "/calendar/")
         request.headers.setHeader("content-type", MimeType("text", "xml"))
         request.stream = MemoryStream(body)
@@ -130,80 +131,61 @@
         self.assertEqual(response.code, resultcode)
         returnValue(response)
 
+
     def _clearUIDElementValue(self, xml):
-        
+
         for user in xml.children:
             for element in user.children:
                 if type(element) == customxml.UID:
                     element.children[0].data = ""
         return xml
 
+
     @inlineCallbacks
-    def test_upgradeToShareOnCreate(self):
-        request = SimpleRequest(self.site, "MKCOL", "/calendar/")
+    def test_upgradeToShare(self):
 
         rtype = self.resource.resourceType()
         self.assertEquals(rtype, regularCalendarType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, request))
-        self.assertEquals(propInvite, None)
+        isShared = (yield self.resource.isShared(None))
+        self.assertFalse(isShared)
+        isVShared = self.resource.isVirtualShare()
+        self.assertFalse(isVShared)
 
         self.resource.upgradeToShare()
 
         rtype = self.resource.resourceType()
         self.assertEquals(rtype, sharedOwnerType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, request))
-        self.assertEquals(propInvite, customxml.Invite())
-        
-        isShared = (yield self.resource.isShared(request))
+        isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
         isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
+
     @inlineCallbacks
-    def test_upgradeToShareAfterCreate(self):
-        request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
+    def test_downgradeFromShare(self):
 
-        rtype = self.resource.resourceType()
-        self.assertEquals(rtype, regularCalendarType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, request))
-        self.assertEquals(propInvite, None)
-
         self.resource.upgradeToShare()
 
         rtype = self.resource.resourceType()
         self.assertEquals(rtype, sharedOwnerType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, request))
-        self.assertEquals(propInvite, customxml.Invite())
-        
-        isShared = (yield self.resource.isShared(request))
+        isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
         isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
-    @inlineCallbacks
-    def test_downgradeFromShare(self):
-        self.resource.writeDeadProperty(sharedOwnerType)
-        self.resource.writeDeadProperty(customxml.Invite())
-        rtype = self.resource.resourceType()
-        self.assertEquals(rtype, sharedOwnerType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
-        self.assertEquals(propInvite, customxml.Invite())
-
         yield self.resource.downgradeFromShare(None)
 
         rtype = self.resource.resourceType()
         self.assertEquals(rtype, regularCalendarType)
-        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
-        self.assertEquals(propInvite, None)
-        
         isShared = (yield self.resource.isShared(None))
         self.assertFalse(isShared)
         isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
+
     @inlineCallbacks
     def test_POSTaddInviteeAlreadyShared(self):
-        
+
         self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -226,15 +208,16 @@
                 customxml.InviteStatusNoResponse(),
             )
         ))
-        
+
         isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
         isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
+
     @inlineCallbacks
     def test_POSTaddInviteeNotAlreadyShared(self):
-        
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:set>
@@ -256,17 +239,19 @@
                 customxml.InviteStatusNoResponse(),
             )
         ))
-        
+
         isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
         isVShared = (yield self.resource.isVirtualShare())
         self.assertFalse(isVShared)
 
+
     @inlineCallbacks
     def test_POSTupdateInvitee(self):
-        
-        self.resource.upgradeToShare()
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertFalse(isShared)
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:set>
@@ -277,6 +262,9 @@
 </CS:share>
 """)
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertTrue(isShared)
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:set>
@@ -287,6 +275,9 @@
 </CS:share>
 """)
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertTrue(isShared)
+
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
@@ -298,11 +289,13 @@
             )
         ))
 
+
     @inlineCallbacks
     def test_POSTremoveInvitee(self):
-        
-        self.resource.upgradeToShare()
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertFalse(isShared)
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:set>
@@ -313,6 +306,9 @@
 </CS:share>
 """)
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertTrue(isShared)
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:remove>
@@ -321,12 +317,16 @@
 </CS:share>
 """)
 
+        isShared = (yield self.resource.isShared(None))
+        self.assertFalse(isShared)
+
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
-        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+        self.assertEquals(propInvite, None)
 
+
     @inlineCallbacks
     def test_POSTaddMoreInvitees(self):
-        
+
         self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -378,9 +378,10 @@
             ),
         ))
 
+
     @inlineCallbacks
     def test_POSTaddRemoveInvitees(self):
-        
+
         self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -428,6 +429,7 @@
             ),
         ))
 
+
     @inlineCallbacks
     def test_POSTaddRemoveSameInvitee(self):
 
@@ -478,6 +480,7 @@
             ),
         ))
 
+
     @inlineCallbacks
     def test_POSTremoveNonInvitee(self):
         """
@@ -521,7 +524,7 @@
 """)
 
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
-        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+        self.assertEquals(propInvite, None)
 
 
     @inlineCallbacks
@@ -551,14 +554,12 @@
         )
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
 
-        self.assertEquals(
-            self._clearUIDElementValue(propInvite), customxml.Invite()
-        )
+        self.assertEquals(propInvite, None)
 
 
     @inlineCallbacks
     def test_POSTremoveInvalidInvitee(self):
-        
+
         self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
@@ -596,7 +597,7 @@
                 customxml.InviteStatusInvalid(),
             )
         ))
-        
+
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:remove>
@@ -606,7 +607,7 @@
 """)
 
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
-        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+        self.assertEquals(propInvite, None)
 
 
     @inlineCallbacks
@@ -617,6 +618,8 @@
         access.
         """
 
+        access = "read"
+
         def stubWikiAccessMethod(userID, wikiID):
             return access
 
@@ -664,19 +667,21 @@
 
             def locateChild(self, req, segments):
                 pass
-            def renderHTTP(req):
+
+
+            def renderHTTP(self, req):
                 pass
+
+
             def ownerPrincipal(self, req):
                 return succeed(StubWikiPrincipal())
 
-
         collection = TestCollection()
         collection._share = StubShare()
         self.site.resource.putChild("wikifoo", StubWikiResource())
         request = SimpleRequest(self.site, "GET", "/wikifoo")
 
         # Simulate the wiki server granting Read access
-        access = "read"
         acl = (yield collection.shareeAccessControlList(request,
             wikiAccessMethod=stubWikiAccessMethod))
         self.assertFalse("<write/>" in acl.toxml())
@@ -688,6 +693,7 @@
         self.assertTrue("<write/>" in acl.toxml())
 
 
+
 class DatabaseSharingTests(SharingTests):
 
     @inlineCallbacks
@@ -698,5 +704,3 @@
 
     def createDataStore(self):
         return self.calendarStore
-
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120919/b0b4f3c8/attachment-0001.html>


More information about the calendarserver-changes mailing list