[CalendarServer-changes] [11037] CalendarServer/branches/users/cdaboo/store-scheduling

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 12 13:32:36 PDT 2013


Revision: 11037
          http://trac.calendarserver.org//changeset/11037
Author:   cdaboo at apple.com
Date:     2013-04-12 13:32:36 -0700 (Fri, 12 Apr 2013)
Log Message:
-----------
COPY/MOVE uses new store api.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove.py
    CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove_contact.py
    CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -53,6 +53,8 @@
     the destination if its a calendar collection.
     """
 
+    raise AssertionError("Never use this")
+
     # Copy of calendar collections isn't allowed.
     if isPseudoCalendarCollectionResource(self):
         returnValue(responsecode.FORBIDDEN)
@@ -128,6 +130,8 @@
     since its effectively being deleted. We do need to do an index update for
     the destination if its a calendar collection
     """
+    raise AssertionError("Never use this")
+
     result, sourcecal, sourceparent, destination_uri, destination, destinationcal, destinationparent = (yield checkForCalendarAction(self, request))
     if not result:
         is_calendar_collection = isPseudoCalendarCollectionResource(self)

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove_contact.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove_contact.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/method/copymove_contact.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -47,6 +47,8 @@
     is not being changed in any way. We do need to do an index update for
     the destination if its an addressbook collection.
     """
+    raise AssertionError("Never use this")
+
     # Copy of addressbook collections isn't allowed.
     if isAddressBookCollectionResource(self):
         returnValue(responsecode.FORBIDDEN)
@@ -120,6 +122,8 @@
     since its effectively being deleted. We do need to do an index update for
     the destination if its an addressbook collection
     """
+    raise AssertionError("Never use this")
+
     result, sourceadbk, sourceparent, destination_uri, destination, destinationadbk, destinationparent = (yield checkForAddressBookAction(self, request))
     if not result or not destinationadbk:
 

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -28,8 +28,7 @@
 from twext.web2.http_headers import ETag, MimeType, MimeDisposition
 from twext.web2.responsecode import \
     FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED, \
-    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE, \
-    INTERNAL_SERVER_ERROR
+    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
 from twext.web2.stream import ProducerStream, readStream, MemoryStream
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
@@ -64,7 +63,8 @@
     AttachmentDropboxNotAllowed, InvalidComponentTypeError, \
     TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError, \
     UIDExistsError, InvalidUIDError, InvalidPerUserDataMerge, \
-    AttendeeAllowedError, ResourceDeletedError, InvalidComponentForStoreError
+    AttendeeAllowedError, ResourceDeletedError, InvalidComponentForStoreError, \
+    InvalidResourceMove
 from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
     _BIND_MODE_DIRECT
 from txdav.common.icommondatastore import NoSuchObjectResourceError, \
@@ -2197,6 +2197,11 @@
         response.headers.setHeader("content-type", self.contentType())
         returnValue(response)
 
+    # The following are used to map store exceptions into HTTP error responses
+    StoreExceptionsStatusErrors = set()
+    StoreExceptionsErrors = {}
+    StoreMoveExceptionsStatusErrors = set()
+    StoreMoveExceptionsErrors = {}
 
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
@@ -2210,6 +2215,14 @@
         return self.storeRemove(request)
 
 
+    def http_COPY(self, request):
+        """
+        Copying of calendar data isn't allowed.
+        """
+        # FIXME: no direct tests
+        return FORBIDDEN
+
+
     @inlineCallbacks
     def http_MOVE(self, request):
         """
@@ -2271,11 +2284,32 @@
         # May need to add a location header
         addLocation(request, destination_uri)
 
-        storer = self.storeResource(request, parent, destination, destination_uri, destinationparent, True, None)
-        result = (yield storer.move())
-        returnValue(result)
+        try:
+            response = (yield self.storeMove(request, destinationparent, destination.name()))
+            returnValue(response)
 
+        # Handle the various store errors
+        except Exception as err:
 
+            # Grab the current exception state here so we can use it in a re-raise - we need this because
+            # an inlineCallback might be called and that raises an exception when it returns, wiping out the
+            # original exception "context".
+            ex = Failure()
+
+            if type(err) in self.StoreMoveExceptionsStatusErrors:
+                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, str(err)))
+
+            elif type(err) in self.StoreMoveExceptionsErrors:
+                raise HTTPError(ErrorResponse(
+                    responsecode.FORBIDDEN,
+                    self.StoreMoveExceptionsErrors[type(err)],
+                    str(err),
+                ))
+            else:
+                # Return the original failure (exception) state
+                ex.raiseException()
+
+
     def http_PROPPATCH(self, request):
         """
         No dead properties allowed on object resources.
@@ -2332,12 +2366,7 @@
         @type destination_name: C{str}
         """
 
-        try:
-            yield self._newStoreObject.moveTo(destinationparent._newStoreObject, destination_name)
-        except Exception, e:
-            log.err(e)
-            raise HTTPError(INTERNAL_SERVER_ERROR)
-
+        yield self._newStoreObject.moveTo(destinationparent._newStoreObject, destination_name)
         returnValue(CREATED)
 
 
@@ -2536,6 +2565,17 @@
         AttachmentStoreValidManagedID: (caldav_namespace, "valid-managed-id"),
     }
 
+    StoreMoveExceptionsStatusErrors = set((
+        ObjectResourceNameNotAllowedError,
+        ObjectResourceNameAlreadyExistsError,
+    ))
+
+    StoreMoveExceptionsErrors = {
+        TooManyObjectResourcesError: customxml.MaxResources(),
+        InvalidResourceMove: (calendarserver_namespace, "valid-move"),
+        InvalidComponentTypeError: (caldav_namespace, "supported-component"),
+    }
+
     @inlineCallbacks
     def http_PUT(self, request):
 
@@ -2578,6 +2618,17 @@
                     "Can't parse calendar data"
                 ))
 
+            # storeComponent needs to know who the auth'd user is for access control
+            # TODO: this needs to be done in a better way - ideally when the txn is created for the request,
+            # we should set a txn.authzid attribute.
+            authz = None
+            authz_principal = self._parentResource.currentPrincipal(request).children[0]
+            if isinstance(authz_principal, davxml.HRef):
+                principalURL = str(authz_principal)
+                if principalURL:
+                    authz = (yield request.locateResource(principalURL))
+                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.guid
+
             try:
                 response = (yield self.storeComponent(component))
             except ResourceDeletedError:
@@ -2586,6 +2637,19 @@
                 response = responsecode.CREATED if self.exists() else responsecode.NO_CONTENT
             response = IResponse(response)
 
+            if self._newStoreObject.isScheduleObject:
+                # Add a response header
+                response.headers.setHeader("Schedule-Tag", self._newStoreObject.scheduleTag)
+
+            # Must not set ETag in response if data changed
+            if self._newStoreObject._componentChanged:
+                def _removeEtag(request, response):
+                    response.headers.removeHeader('etag')
+                    return response
+                _removeEtag.handleErrors = True
+
+                request.addResponseFilter(_removeEtag, atEnd=True)
+
             # Look for Prefer header
             prefer = request.headers.getHeader("prefer", {})
             returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])
@@ -2618,8 +2682,8 @@
                     str(err),
                 ))
             elif isinstance(err, ValueError):
-                log.err("Error while handling (calendar) PUT: %s" % (e,))
-                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+                log.err("Error while handling (calendar) PUT: %s" % (err,))
+                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(err)))
             else:
                 # Return the original failure (exception) state
                 ex.raiseException()

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -46,7 +46,7 @@
 from twext.web2.http_headers import MimeType, generateContentType
 from twext.web2.stream import readStream
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.python import hashlib
 
 from twistedcaldav import caldavxml, customxml
@@ -72,7 +72,7 @@
     InvalidUIDError, UIDExistsError, ResourceDeletedError, \
     AttendeeAllowedError, InvalidPerUserDataMerge, ComponentUpdateState, \
     ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
-    InvalidComponentForStoreError
+    InvalidComponentForStoreError, InvalidResourceMove
 from txdav.caldav.icalendarstore import QuotaExceeded
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
     CommonObjectResource, ECALENDARTYPE
@@ -330,7 +330,7 @@
 
 
     def fullName(self):
-        return "%s %s" % (self.principal_uid[:4], self.principal_uid[4:])
+        return "%s %s" % (self.principal_uid[:4].capitalize(), self.principal_uid[4:])
 
 
     def displayName(self):
@@ -2378,6 +2378,43 @@
         returnValue(self._cachedCommponentPerUser[user_uuid])
 
 
+    def moveValidation(self, destination, name):
+        """
+        Validate whether a move to the specified collection is allowed.
+
+        @param destination: destination calendar collection
+        @type destination: L{CalendarCollection}
+        @param name: name of new resource
+        @type name: C{str}
+        """
+
+        # Calendar to calendar moves are OK if the resource (viewer) owner is the same.
+        # Use resourceOwnerPrincipal for this as that takes into account sharing such that the
+        # returned principal relates to the URI path used to access the resource rather than the
+        # underlying resource owner (sharee).
+        sourceowner = self.calendar().viewerHome().uid()
+        destowner = destination.viewerHome().uid()
+
+        if sourceowner != destowner:
+            msg = "Calendar-to-calendar moves with different homes are not supported."
+            log.debug(msg)
+            raise InvalidResourceMove(msg)
+
+        # Calendar to calendar moves where Organizer is present are not OK if the owners are different.
+        sourceowner = self.calendar().ownerHome().uid()
+        destowner = destination.ownerHome().uid()
+
+        if sourceowner != destowner and self._schedule_object:
+            msg = "Calendar-to-calendar moves with an organizer property present and different owners are not supported."
+            log.debug(msg)
+            raise InvalidResourceMove(msg)
+
+        # NB there is no need to do a UID lock and test here as we are moving an existing resource
+        # with the already imposed constraint of unique UIDs.
+
+        return succeed(None)
+
+
     def remove(self, implicitly=True):
         return self._removeInternal(
             internal_state=ComponentRemoveState.NORMAL if implicitly else ComponentRemoveState.NORMAL_NO_IMPLICIT

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -199,6 +199,13 @@
 
 
 
+class InvalidResourceMove(CommonStoreError):
+    """
+    Moving a resource failed.
+    """
+
+
+
 class AttachmentStoreFailed(Exception):
     """
     Unable to store an attachment.

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py	2013-04-12 19:42:10 UTC (rev 11036)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py	2013-04-12 20:32:36 UTC (rev 11037)
@@ -3770,15 +3770,26 @@
         @type newname: C{str} or C{None} for existing name
         """
 
-        if newname and newname.startswith("."):
-            raise ObjectResourceNameNotAllowedError(newname)
-
         name = child.name()
         uid = child.uid()
 
         if newname is None:
             newname = name
 
+        # Create => a new resource name
+        if newname.startswith("."):
+            raise ObjectResourceNameNotAllowedError(newname)
+
+        # Make sure name is not already used - i.e., overwrite not allowed
+        if (yield newparent.objectResourceWithName(newname)) is not None:
+            raise ObjectResourceNameAlreadyExistsError(newname)
+
+        # Apply check to the size of the collection
+        if config.MaxResourcesPerCollection:
+            child_count = (yield self.countObjectResources())
+            if child_count >= config.MaxResourcesPerCollection:
+                raise TooManyObjectResourcesError()
+
         # Clean this collections cache and signal sync change
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
@@ -4325,6 +4336,7 @@
         return Delete(cls._objectSchema, Where=cls._objectSchema.RESOURCE_ID == Parameter("resourceID"))
 
 
+    @inlineCallbacks
     def moveTo(self, destination, name):
         """
         Move object to another collection.
@@ -4335,11 +4347,14 @@
         @type name: C{str} or C{None} to use existing name
         """
 
-        if name and name.startswith("."):
-            raise ObjectResourceNameNotAllowedError(name)
-        return self._parentCollection.moveObjectResource(self, destination, name)
+        yield self.moveValidation(destination, name)
+        yield self._parentCollection.moveObjectResource(self, destination, name)
 
 
+    def moveValidation(self, destination, name):
+        raise NotImplementedError
+
+
     @inlineCallbacks
     def remove(self):
         yield self._deleteQuery.on(self._txn, NoSuchObjectResourceError,
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130412/a6767bee/attachment-0001.html>


More information about the calendarserver-changes mailing list