def checkForAddressBookAction(self, request):
    """
    Check to see whether the source or destination of the copy/move
    is an addressbook collection, since we need to do special processing
    if that is the case.
    @return: tuple::
        result:           True if special CalDAV processing required, False otherwise
            NB If there is any type of error with the request, return False
            and allow normal COPY/MOVE processing to return the error.
        sourceadbk:        True if source is in an addressbook collection, False otherwise
        sourceparent:     The parent resource for the source
        destination_uri:  The URI of the destination resource
        destination:      CalDAVResource of destination if special processing required,
        None otherwise
        destinationadbk:   True if the destination is in an addressbook collection,
            False otherwise
        destinationparent:The parent resource for the destination
        
    """
    
    result = False
    sourceadbk = False
    destinationadbk = False
    
    # Check the source path first
    if not self.exists():
        log.err("Resource not found: %s" % (self,))
        raise HTTPError(StatusResponse(
            responsecode.NOT_FOUND,
            "Source resource %s not found." % (request.uri,)
        ))

    # Check for parent addressbook collection
    sourceparent = (yield request.locateResource(parentForURL(request.uri)))
    if isAddressBookCollectionResource(sourceparent):
        result = True
        sourceadbk = True
    
    #
    # Find the destination resource
    #
    destination_uri = request.headers.getHeader("destination")

    if not destination_uri:
        msg = "No destination header in %s request." % (request.method,)
        log.err(msg)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
    
    destination = (yield request.locateResource(destination_uri))

    # Check for parent addressbook collection
    destination_uri = urlsplit(destination_uri)[2]
    destinationparent = (yield request.locateResource(parentForURL(destination_uri)))
    if isAddressBookCollectionResource(destinationparent):
        result = True
        destinationadbk = True

    returnValue((result, sourceadbk, sourceparent, destination_uri, destination, destinationadbk, destinationparent))
def http_PROPFIND(self, request):
    """
    Respond to a PROPFIND request. (RFC 2518, section 8.1)
    """
    if not self.exists():
        # Return 403 if parent does not allow Bind
        parentURL = parentForURL(request.uri)
        parent = (yield request.locateResource(parentURL))
        yield parent.authorize(request, (davxml.Bind(),))

        log.error("Resource not found: %s" % (self,))
        raise HTTPError(responsecode.NOT_FOUND)

    #
    # Check authentication and access controls
    #
    yield self.authorize(request, (davxml.Read(),))

    #
    # Read request body
    #
    try:
        doc = (yield davXMLFromStream(request.stream))
    except ValueError, e:
        log.error("Error while handling PROPFIND body: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_DELETE(self, request):
    #
    # Override base DELETE request handling to ensure that the calendar
    # index file has the entry for the deleted calendar component removed.
    #

    if not self.exists():
        log.err("Resource not found: %s" % (self,))
        raise HTTPError(responsecode.NOT_FOUND)

    depth = request.headers.getHeader("depth", "infinity")

    #
    # Check authentication and access controls
    #
    parentURL = parentForURL(request.uri)
    parent = (yield request.locateResource(parentURL))

    yield parent.authorize(request, (davxml.Unbind(),))

    # Do smart delete taking into account the need to do implicit CANCELs etc
    deleter = DeleteResource(request, self, request.uri, parent, depth)
    response = (yield deleter.run())

    returnValue(response)
Exemple #4
0
def authorize(self, request):
    if self.exists():
        d = self.authorize(request, (davxml.Read(),))
    else:
        d = request.locateResource(parentForURL(request.uri))
        d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(),)))
    return d
Exemple #5
0
def http_MKCOL(self, request):
    """
    Respond to a MKCOL request. (RFC 2518, section 8.3)
    """
    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
    yield parent
    parent = parent.getResult()

    x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
    yield x
    x.getResult()

    if self.exists():
        log.err("Attempt to create collection where file exists: %s"
                % (self,))
        raise HTTPError(responsecode.NOT_ALLOWED)

    if not parent.isCollection():
        log.err("Attempt to create collection with non-collection parent: %s"
                % (self,))
        raise HTTPError(StatusResponse(
            responsecode.CONFLICT,
            "Parent resource is not a collection."
        ))

    #
    # Read request body
    #
    x = waitForDeferred(noDataFromStream(request.stream))
    yield x
    try:
        x.getResult()
    except ValueError, e:
        log.err("Error while handling MKCOL body: %s" % (e,))
        raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
Exemple #6
0
def http_PUT(self, request):

    parentURL = parentForURL(request.uri)
    parent = (yield request.locateResource(parentURL))

    if isPseudoCalendarCollectionResource(parent):

        # Content-type check
        content_type = request.headers.getHeader("content-type")
        if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
            log.err("MIME type %s not allowed in calendar collection" % (content_type,))
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "supported-calendar-data"),
                "Invalid MIME type for calendar collection",
            ))

        # Read the calendar component from the stream
        try:
            calendardata = (yield allDataFromStream(request.stream))
            if not hasattr(request, "extendedLogItems"):
                request.extendedLogItems = {}
            request.extendedLogItems["cl"] = str(len(calendardata)) if calendardata else "0"

            # We must have some data at this point
            if calendardata is None:
                # Use correct DAV:error response
                raise HTTPError(ErrorResponse(
                    responsecode.FORBIDDEN,
                    (caldav_namespace, "valid-calendar-data"),
                    description="No calendar data"
                ))

            storer = StoreCalendarObjectResource(
                request=request,
                destination=self,
                destination_uri=request.uri,
                destinationcal=True,
                destinationparent=parent,
                calendar=calendardata,
            )
            result = (yield storer.run())

            # Look for Prefer header
            prefer = request.headers.getHeader("prefer", {})
            returnRepresentation = any([key == "return" and value == "representation" for key, value, _ignore_args in prefer])

            if returnRepresentation and result.code / 100 == 2:
                oldcode = result.code
                result = (yield self.http_GET(request))
                if oldcode == responsecode.CREATED:
                    result.code = responsecode.CREATED
                result.headers.setHeader("content-location", request.path)

            returnValue(result)

        except ValueError, e:
            log.err("Error while handling (calendar) PUT: %s" % (e,))
            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_COPY(self, request):
    """
    Respond to a COPY request. (RFC 2518, section 8.8)
    """
    r = waitForDeferred(prepareForCopy(self, request))
    yield r
    r = r.getResult()

    destination, destination_uri, depth = r

    #
    # Check authentication and access controls
    #
    x = waitForDeferred(self.authorize(request, (davxml.Read(),), recurse=True))
    yield x
    x.getResult()

    if destination.exists():
        x = waitForDeferred(destination.authorize(
            request,
            (davxml.WriteContent(), davxml.WriteProperties()),
            recurse=True
        ))
        yield x
        x.getResult()
    else:
        destparent = waitForDeferred(request.locateResource(parentForURL(destination_uri)))
        yield destparent
        destparent = destparent.getResult()

        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
        yield x
        x.getResult()

        # May need to add a location header
        addLocation(request, destination_uri)

    #x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth))
    x = waitForDeferred(put_common.storeResource(request,
                                                 source=self,
                                                 source_uri=request.uri,
                                                 destination=destination,
                                                 destination_uri=destination_uri,
                                                 deletesource=False,
                                                 depth=depth
                                                 ))
    yield x
    yield x.getResult()
    def hasCalendarResourceUIDSomewhereElse(self, check_resource, check_uri, type):
        """
        See if a calendar component with a matching UID exists anywhere in the calendar home of the
        current recipient owner and is not the resource being targeted.
        """

        # Don't care in some cases
        if self.internal_request or self.action == "remove":
            returnValue(None)

        # Get owner's calendar-home
        calendar_owner_principal = (yield self.resource.resourceOwnerPrincipal(self.request))
        calendar_home = calendar_owner_principal.calendarHome(self.request)

        check_parent_uri = parentForURL(check_uri)[:-1] if check_uri else None

        # FIXME: because of the URL->resource request mapping thing, we have to force the request
        # to recognize this resource
        self.request._rememberResource(calendar_home, calendar_home.url())

        # Run a UID query against the UID

        @inlineCallbacks
        def queryCalendarCollection(collection, collection_uri):
            rname = collection.index().resourceNameForUID(self.uid)
            if rname:
                child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
                if child == check_resource:
                    returnValue(True)
                is_scheduling_object = (yield self.checkSchedulingObjectResource(child))
                matched_type = "schedule" if is_scheduling_object else "calendar"
                if (
                    collection_uri != check_parent_uri and
                    (type == "schedule" or matched_type == "schedule")
                ):
                    log.debug("Implicit - found component with same UID in a different collection: %s" % (check_uri,))
                    raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "unique-scheduling-object-resource")))

                # Here we can always return true as the unique UID in a calendar collection
                # requirement will already have been tested.

            returnValue(True)

        # NB We are by-passing privilege checking here. That should be OK as the data found is not
        # exposed to the user.
        yield report_common.applyToCalendarCollections(calendar_home, self.request, calendar_home.url(), "infinity", queryCalendarCollection, None)
Exemple #9
0
def preconditions_PUT(self, request):
    #
    # Check authentication and access controls
    #
    if self.exists():
        x = waitForDeferred(self.authorize(request, (davxml.WriteContent(),)))
        yield x
        x.getResult()
    else:
        parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
        yield parent
        parent = parent.getResult()

        if not parent.exists():
            raise HTTPError(
                StatusResponse(
                    responsecode.CONFLICT,
                    "cannot PUT to non-existent parent"))
        x = waitForDeferred(parent.authorize(request, (davxml.Bind(),)))
        yield x
        x.getResult()


    #
    # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not
    # Implemented error if we get a Content-* header which we don't
    # recognize and handle properly.
    #
    for header, value in request.headers.getAllRawHeaders():
        if header.startswith("Content-") and header not in (
           #"Content-Base",     # Doesn't make sense in PUT?
           #"Content-Encoding", # Requires that we decode it?
            "Content-Language",
            "Content-Length",
           #"Content-Location", # Doesn't make sense in PUT?
            "Content-MD5",
           #"Content-Range",    # FIXME: Need to implement this
            "Content-Type",
        ):
            log.error("Client sent unrecognized content header in PUT request: %s"
                      % (header,))
            raise HTTPError(StatusResponse(
                responsecode.NOT_IMPLEMENTED,
                "Unrecognized content header %r in request." % (header,)
            ))
Exemple #10
0
def http_ACL(self, request):
    #
    # Override base ACL request handling to ensure that the calendar/address book
    # homes cannot have ACL's set, and calendar/address object resources too.
    #

    if self.exists():
        if isinstance(self, CalendarHomeResource) or isinstance(self, AddressBookHomeResource):
            raise HTTPError(responsecode.NOT_ALLOWED)

        parentURL = parentForURL(request.uri)
        parent = (yield request.locateResource(parentURL))
        if isPseudoCalendarCollectionResource(parent) or isAddressBookCollectionResource(parent):
            raise HTTPError(responsecode.NOT_ALLOWED)

    # Do normal ACL behavior
    response = (yield super(CalDAVResource, self).http_ACL(request))
    returnValue(response)
def http_MKCALENDAR(self, request):
    """
    Respond to a MKCALENDAR request.
    (CalDAV-access-09, section 5.3.1)
    """

    #
    # Check authentication and access controls
    #
    parent = (yield request.locateResource(parentForURL(request.uri)))
    yield parent.authorize(request, (davxml.Bind(),))

    if self.exists():
        log.error("Attempt to create collection where resource exists: %s" % (self,))
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (davxml.dav_namespace, "resource-must-be-null"),
            "Resource already exists",
        ))

    if not parent.isCollection():
        log.error("Attempt to create collection with non-collection parent: %s"
                  % (self,))
        raise HTTPError(ErrorResponse(
            responsecode.CONFLICT,
            (caldavxml.caldav_namespace, "calendar-collection-location-ok"),
            "Cannot create calendar inside another calendar",
        ))

    #
    # Read request body
    #
    try:
        doc = (yield davXMLFromStream(request.stream))
        yield self.createCalendar(request)
    except ValueError, e:
        log.error("Error while handling MKCALENDAR: %s" % (e,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
Exemple #12
0
def http_DELETE(self, request):
    """
    Respond to a DELETE request. (RFC 2518, section 8.6)
    """
    if not self.exists():
        log.err("File not found: %s" % (self,))
        raise HTTPError(responsecode.NOT_FOUND)

    depth = request.headers.getHeader("depth", "infinity")

    #
    # Check authentication and access controls
    #
    parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
    yield parent
    parent = parent.getResult()

    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
    yield x
    x.getResult()

    x = waitForDeferred(deleteResource(request, self, request.uri, depth))
    yield x
    yield x.getResult()
def http_MOVE(self, request):
    """
    Respond to a MOVE request. (RFC 2518, section 8.9)
    """
    r = waitForDeferred(prepareForCopy(self, request))
    yield r
    r = r.getResult()

    destination, destination_uri, depth = r

    #
    # Check authentication and access controls
    #
    parentURL = parentForURL(request.uri)
    parent = waitForDeferred(request.locateResource(parentURL))
    yield parent
    parent = parent.getResult()

    x = waitForDeferred(parent.authorize(request, (davxml.Unbind(),)))
    yield x
    x.getResult()

    if destination.exists():
        x = waitForDeferred(destination.authorize(
            request,
            (davxml.Bind(), davxml.Unbind()),
            recurse=True
        ))
        yield x
        x.getResult()
    else:
        destparentURL = parentForURL(destination_uri)
        destparent = waitForDeferred(request.locateResource(destparentURL))
        yield destparent
        destparent = destparent.getResult()

        x = waitForDeferred(destparent.authorize(request, (davxml.Bind(),)))
        yield x
        x.getResult()

        # May need to add a location header
        addLocation(request, destination_uri)

    #
    # RFC 2518, section 8.9 says that we must act as if the Depth header is set
    # to infinity, and that the client must omit the Depth header or set it to
    # infinity.
    #
    # This seems somewhat at odds with the notion that a bad request should be
    # rejected outright; if the client sends a bad depth header, the client is
    # broken, and section 8 suggests that a bad request should be rejected...
    #
    # Let's play it safe for now and ignore broken clients.
    #
    if self.isCollection() and depth != "infinity":
        msg = "Client sent illegal depth header value for MOVE: %s" % (depth,)
        log.err(msg)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

    # Lets optimise a move within the same directory to a new resource as a simple move
    # rather than using the full transaction based storeResource api. This allows simple
    # "rename" operations to work quickly.
    if (not destination.exists()) and destparent == parent:
        x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth))
    else:
        x = waitForDeferred(put_common.storeResource(request,
                                                     source=self,
                                                     source_uri=request.uri,
                                                     destination=destination,
                                                     destination_uri=destination_uri,
                                                     deletesource=True,
                                                     depth=depth))
    yield x
    yield x.getResult()
    def run(self):
        """
        Function that does common PUT/COPY/MOVE behavior.

        @return: a Deferred with a status response result.
        """

        try:
            reservation = None
            
            # Handle all validation operations here.
            yield self.fullValidation()

            # Reservation and UID conflict checking is next.
            if self.destinationadbk:    
                # Reserve UID
                self.destination_index = self.destinationparent.index()
                reservation = StoreAddressObjectResource.UIDReservation(
                    self.destination_index, self.uid, self.destination_uri
                )
                if self.indexdestination:
                    yield reservation.reserve()
            
                # UID conflict check - note we do this after reserving the UID to avoid a race condition where two requests
                # try to write the same vcard data to two different resource URIs.
                result, message, rname = self.noUIDConflict(self.uid)
                if not result:
                    log.err(message)
                    raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
                        NoUIDConflict(davxml.HRef.fromString(joinURL(parentForURL(self.destination_uri), rname.encode("utf-8"))))
                    ))
            
            # Get current quota state.
            yield self.checkQuota()

            # Do the actual put or copy
            response = (yield self.doStore())
            
            # Remember the resource's content-type.
            if self.destinationadbk:
                content_type = self.request.headers.getHeader("content-type")
                if content_type is None:
                    content_type = MimeType("text", "vcard",
                                            params={"charset":"utf-8"})
                self.destination.writeDeadProperty(
                    davxml.GETContentType.fromString(generateContentType(content_type))
                )

            # Delete the original source if needed.
            if self.deletesource:
                yield self.doSourceQuotaCheck()

            # Do quota check on destination
            if self.destquota is not None:
                yield self.doDestinationQuotaCheck()
    
            if reservation:
                yield reservation.unreserve()
    
            returnValue(response)
    
        except Exception, err:

            if reservation:
                yield reservation.unreserve()
    
            # FIXME: transaction needs to be rolled back.

            raise err
Exemple #15
0
def http_GET(self, request):

    if self.exists():
        # Special sharing request on a calendar or address book
        if self.isCalendarCollection() or self.isAddressBookCollection():
            
            # Check for action=share
            if request.args:
                action = request.args.get("action", ("",))
                if len(action) != 1:
                    raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action")))
                action = action[0]
                    
                dispatch = {
                    "share"   : self.directShare,
                }.get(action, None)
                
                if dispatch is None:
                    raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action")))
        
                response = (yield dispatch(request))
                returnValue(response)
        
        else:
            # Look for calendar access restriction on existing resource.
            parentURL = parentForURL(request.uri)
            parent = (yield request.locateResource(parentURL))
            if isPseudoCalendarCollectionResource(parent):
        
                # Check authorization first
                yield self.authorize(request, (davxml.Read(),))
    
                caldata = (yield self.iCalendarForUser(request))
    
                try:
                    access = self.readDeadProperty(TwistedCalendarAccessProperty)
                except HTTPError:
                    access = None
                    
                if access:
            
                    # Non DAV:owner's have limited access to the data
                    isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
                    
                    # Now "filter" the resource calendar data
                    caldata = PrivateEventFilter(access, isowner).filter(caldata)
        
                response = Response()
                response.stream = MemoryStream(str(caldata))
                response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
        
                # Add Schedule-Tag header if property is present
                if self.hasDeadProperty(ScheduleTag):
                    scheduletag = self.readDeadProperty(ScheduleTag)
                    if scheduletag:
                        response.headers.setHeader("Schedule-Tag", str(scheduletag))
            
                returnValue(response)

    # Do normal GET behavior
    response = (yield super(CalDAVResource, self).http_GET(request))
    returnValue(response)
def http_MOVE(self, request):
    """
    Special handling of MOVE request if parent is a calendar collection.
    When moving we may need to remove the index entry for the source resource
    since its effectively being deleted. We do need to do an index update for
    the destination if its a calendar collection
    """
    result, sourcecal, sourceparent, destination_uri, destination, destinationcal, destinationparent = (yield checkForCalendarAction(self, request))
    if not result:
        is_calendar_collection = isPseudoCalendarCollectionResource(self)
        defaultCalendar = (yield self.isDefaultCalendar(request)) if is_calendar_collection else False

        if not is_calendar_collection:
            result = yield maybeMOVEContact(self, request)
            if result is not KEEP_GOING:
                returnValue(result)

        # Do default WebDAV action
        result = (yield super(CalDAVFile, self).http_MOVE(request))
        
        if is_calendar_collection:
            # Do some clean up
            yield self.movedCalendar(request, defaultCalendar, destination, destination_uri)

        returnValue(result)
        
    #
    # Check authentication and access controls
    #
    parent = (yield request.locateResource(parentForURL(request.uri)))
    yield parent.authorize(request, (davxml.Unbind(),))

    if destination.exists():
        yield destination.authorize(request, (davxml.Bind(), davxml.Unbind()), recurse=True)
    else:
        destparent = (yield request.locateResource(parentForURL(destination_uri)))
        yield destparent.authorize(request, (davxml.Bind(),))

    # Check for existing destination resource
    overwrite = request.headers.getHeader("overwrite", True)
    if destination.exists() and not overwrite:
        log.err("Attempt to copy onto existing resource without overwrite flag enabled: %s"
                % (destination,))
        raise HTTPError(StatusResponse(
            responsecode.PRECONDITION_FAILED,
            "Destination %s already exists." % (destination_uri,)
        ))

    if destinationcal:
        # Checks for copying a calendar collection
        if self.isCalendarCollection():
            log.err("Attempt to move a calendar collection into another calendar collection %s" % destination)
            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "calendar-collection-location-ok")))
    
        # We also do not allow regular collections in calendar collections
        if self.isCollection():
            log.err("Attempt to move a collection into a calendar collection")
            raise HTTPError(StatusResponse(
                responsecode.FORBIDDEN,
                "Cannot create collection within special collection %s" % (destination,)
            ))

    # May need to add a location header
    addLocation(request, destination_uri)

    storer = StoreCalendarObjectResource(
        request = request,
        source = self,
        source_uri = request.uri,
        sourceparent = sourceparent,
        sourcecal = sourcecal,
        deletesource = True,
        destination = destination,
        destination_uri = destination_uri,
        destinationparent = destinationparent,
        destinationcal = destinationcal,
    )
    result = (yield storer.run())
    returnValue(result)
def http_COPY(self, request):
    """
    Special handling of COPY request if parents are calendar collections.
    When copying we do not have to worry about the source resource as it
    is not being changed in any way. We do need to do an index update for
    the destination if its a calendar collection.
    """

    # Copy of calendar collections isn't allowed.
    if isPseudoCalendarCollectionResource(self):
        returnValue(responsecode.FORBIDDEN)

    result, sourcecal, sourceparent, destination_uri, destination, destinationcal, destinationparent = (yield checkForCalendarAction(self, request))
    if not result or not destinationcal:
        # Check with CardDAV first (XXX might want to check EnableCardDAV switch?)
        result = yield maybeCOPYContact(self, request)
        if result is KEEP_GOING:
            result = yield super(CalDAVFile, self).http_COPY(request)
        returnValue(result)

    #
    # Check authentication and access controls
    #
    yield self.authorize(request, (davxml.Read(),), recurse=True)

    if destination.exists():
        yield destination.authorize(request, (davxml.WriteContent(), davxml.WriteProperties()), recurse=True)
    else:
        destparent = (yield request.locateResource(parentForURL(destination_uri)))
        yield destparent.authorize(request, (davxml.Bind(),))

    # Check for existing destination resource
    overwrite = request.headers.getHeader("overwrite", True)
    if destination.exists() and not overwrite:
        log.err("Attempt to copy onto existing resource without overwrite flag enabled: %s"
                % (destination,))
        raise HTTPError(StatusResponse(
            responsecode.PRECONDITION_FAILED,
            "Destination %s already exists." % (destination_uri,))
        )

    # Checks for copying a calendar collection
    if self.isCalendarCollection():
        log.err("Attempt to copy a calendar collection into another calendar collection %s" % destination)
        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "calendar-collection-location-ok")))

    # We also do not allow regular collections in calendar collections
    if self.isCollection():
        log.err("Attempt to copy a collection into a calendar collection")
        raise HTTPError(StatusResponse(
            responsecode.FORBIDDEN,
            "Cannot create collection within special collection %s" % (destination,))
        )

    # May need to add a location header
    addLocation(request, destination_uri)

    storer = StoreCalendarObjectResource(
        request = request,
        source = self,
        source_uri = request.uri,
        sourceparent = sourceparent,
        sourcecal = sourcecal,
        destination = destination,
        destination_uri = destination_uri,
        destinationparent = destinationparent,
        destinationcal = destinationcal,
    )
    result = (yield storer.run())
    returnValue(result)
def http_MKCOL(self, request):

    #
    # Check authentication and access controls
    #
    parent = (yield request.locateResource(parentForURL(request.uri)))

    yield parent.authorize(request, (davxml.Bind(),))

    if self.exists():
        log.err("Attempt to create collection where resource exists: %s"
                % (self,))
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (davxml.dav_namespace, "resource-must-be-null"))
        )

    if not parent.isCollection():
        log.err("Attempt to create collection with non-collection parent: %s"
                % (self,))
        raise HTTPError(ErrorResponse(
            responsecode.CONFLICT,
            (davxml.dav_namespace, "collection-location-ok"))
        )

    #
    # Don't allow DAV collections in a calendar or address book collection
    #

    if config.EnableCalDAV:
        parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource))
    
        if parent is not None:
            raise HTTPError(StatusResponse(
                responsecode.FORBIDDEN,
                "Cannot create collection within calendar collection %s" % (parent,)
            ))

    if config.EnableCardDAV:
        parent = (yield self._checkParents(request, isAddressBookCollectionResource))
    
        if parent is not None:
            raise HTTPError(StatusResponse(
                responsecode.FORBIDDEN,
                "Cannot create collection within address book collection %s" % (parent,)
            ))

    #
    # Read request body
    #
    try:
        doc = (yield davXMLFromStream(request.stream))
    except ValueError, e:
        log.err("Error while handling MKCOL: %s" % (e,))
        # TODO: twext.web2.dav 'MKCOL' tests demand this particular response
        # code, but should we really be looking at the XML content or the
        # content-type header?  It seems to me like this ought to be considered
        # a BAD_REQUEST if it claims to be XML but isn't, but an
        # UNSUPPORTED_MEDIA_TYPE if it claims to be something else. -glyph
        raise HTTPError(
            StatusResponse(responsecode.UNSUPPORTED_MEDIA_TYPE, str(e))
        )
def maybeCOPYContact(self, request):
    """
    Special handling of COPY request if parents are addressbook collections.
    When copying we do not have to worry about the source resource as it
    is not being changed in any way. We do need to do an index update for
    the destination if its an addressbook collection.
    """
    # Copy of addressbook collections isn't allowed.
    if isAddressBookCollectionResource(self):
        returnValue(responsecode.FORBIDDEN)

    result, sourceadbk, sourceparent, destination_uri, destination, destinationadbk, destinationparent = (yield checkForAddressBookAction(self, request))
    if not result or not destinationadbk:
        # Give up, do default action.
        
        returnValue(KEEP_GOING)

    #
    # Check authentication and access controls
    #
    yield self.authorize(request, (davxml.Read(),), recurse=True)

    if destination.exists():
        yield destination.authorize(request, (davxml.WriteContent(), davxml.WriteProperties()), recurse=True)
    else:
        destparent = (yield request.locateResource(parentForURL(destination_uri)))
        yield destparent.authorize(request, (davxml.Bind(),))

    # Check for existing destination resource
    overwrite = request.headers.getHeader("overwrite", True)
    if destination.exists() and not overwrite:
        log.err("Attempt to copy onto existing resource without overwrite flag enabled: %s"
                % (destination,))
        raise HTTPError(StatusResponse(
            responsecode.PRECONDITION_FAILED,
            "Destination %s already exists." % (destination_uri,))
        )

    # Checks for copying an addressbook collection
    if self.isAddressBookCollection():
        log.err("Attempt to copy an addressbook collection into another addressbook collection %s" % destination)
        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "addressbook-collection-location-ok")))

    # We also do not allow regular collections in addressbook collections
    if self.isCollection():
        log.err("Attempt to copy a collection into an addressbook collection")
        raise HTTPError(StatusResponse(
            responsecode.FORBIDDEN,
            "Cannot create collection within special collection %s" % (destination,))
        )

    # May need to add a location header
    addLocation(request, destination_uri)

    storer = StoreAddressObjectResource(
        request = request,
        source = self,
        source_uri = request.uri,
        sourceparent = sourceparent,
        sourceadbk = sourceadbk,
        destination = destination,
        destination_uri = destination_uri,
        destinationparent = destinationparent,
        destinationadbk = destinationadbk,
    )
    result = (yield storer.run())
    returnValue(result)
Exemple #20
0
def http_GET(self, request):

    if self.exists():
        # Special sharing request on a calendar or address book
        if self.isCalendarCollection() or self.isAddressBookCollection():
            
            # Check for action=share
            if request.args:
                action = request.args.get("action", ("",))
                if len(action) != 1:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "valid-action"),
                        "Invalid action parameter: %s" % (action,),
                    ))
                action = action[0]
                    
                dispatch = {
                    "share"   : self.directShare,
                }.get(action, None)
                
                if dispatch is None:
                    raise HTTPError(ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "supported-action"),
                        "Action not supported: %s" % (action,),
                    ))
        
                response = (yield dispatch(request))
                returnValue(response)
        
        else:
            # Look for calendar access restriction on existing resource.
            parentURL = parentForURL(request.uri)
            parent = (yield request.locateResource(parentURL))
            if isPseudoCalendarCollectionResource(parent):
        
                # Check authorization first
                yield self.authorize(request, (davxml.Read(),))
    
                caldata = (yield self.iCalendarForUser(request))
    
                # Filter any attendee hidden instances        
                caldata = HiddenInstanceFilter().filter(caldata)

                if self.accessMode:
            
                    # Non DAV:owner's have limited access to the data
                    isowner = (yield self.isOwner(request))
                    
                    # Now "filter" the resource calendar data
                    caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)

                response = Response()
                response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference))
                response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
        
                # Add Schedule-Tag header if property is present
                if self.scheduleTag:
                    response.headers.setHeader("Schedule-Tag", self.scheduleTag)
            
                returnValue(response)

    # Do normal GET behavior
    response = (yield super(CalDAVResource, self).http_GET(request))
    returnValue(response)
def maybeMOVEContact(self, request):
    """
    Special handling of MOVE request if parent is an addressbook collection.
    When moving we may need to remove the index entry for the source resource
    since its effectively being deleted. We do need to do an index update for
    the destination if its an addressbook collection
    """
    result, sourceadbk, sourceparent, destination_uri, destination, destinationadbk, destinationparent = (yield checkForAddressBookAction(self, request))
    if not result or not destinationadbk:

        # Do default WebDAV action
        returnValue(KEEP_GOING)
        
    #
    # Check authentication and access controls
    #
    parent = (yield request.locateResource(parentForURL(request.uri)))
    yield parent.authorize(request, (davxml.Unbind(),))

    if destination.exists():
        yield destination.authorize(request, (davxml.Bind(), davxml.Unbind()), recurse=True)
    else:
        destparent = (yield request.locateResource(parentForURL(destination_uri)))
        yield destparent.authorize(request, (davxml.Bind(),))

    # Check for existing destination resource
    overwrite = request.headers.getHeader("overwrite", True)
    if destination.exists() and not overwrite:
        log.err("Attempt to copy onto existing resource without overwrite flag enabled: %s"
                % (destination,))
        raise HTTPError(StatusResponse(
            responsecode.PRECONDITION_FAILED,
            "Destination %s already exists." % (destination_uri,)
        ))

    if destinationadbk:
        # Checks for copying an addressbook collection
        if self.isAddressBookCollection():
            log.err("Attempt to move an addressbook collection into another addressbook collection %s" % destination)
            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "addressbook-collection-location-ok")))
    
        # We also do not allow regular collections in addressbook collections
        if self.isCollection():
            log.err("Attempt to move a collection into an addressbook collection")
            raise HTTPError(StatusResponse(
                responsecode.FORBIDDEN,
                "Cannot create collection within special collection %s" % (destination,)
            ))

    # May need to add a location header
    addLocation(request, destination_uri)

    storer = StoreAddressObjectResource(
        request = request,
        source = self,
        source_uri = request.uri,
        sourceparent = sourceparent,
        sourceadbk = sourceadbk,
        deletesource = True,
        destination = destination,
        destination_uri = destination_uri,
        destinationparent = destinationparent,
        destinationadbk = destinationadbk,
    )
    result = (yield storer.run())
    returnValue(result)
Exemple #22
0
 def test_parentForURL(self):
     """
     parentForURL()
     """
     self.assertEquals(util.parentForURL("http://server/"), None)
     self.assertEquals(util.parentForURL("http://server//"), None)
     self.assertEquals(util.parentForURL("http://server/foo/.."), None)
     self.assertEquals(util.parentForURL("http://server/foo/../"), None)
     self.assertEquals(util.parentForURL("http://server/foo/."), "http://server/")
     self.assertEquals(util.parentForURL("http://server/foo/./"), "http://server/")
     self.assertEquals(util.parentForURL("http://server/foo"), "http://server/")
     self.assertEquals(util.parentForURL("http://server//foo"), "http://server/")
     self.assertEquals(util.parentForURL("http://server/foo/bar/.."), "http://server/")
     self.assertEquals(util.parentForURL("http://server/foo/bar/."), "http://server/foo/")
     self.assertEquals(util.parentForURL("http://server/foo/bar"), "http://server/foo/")
     self.assertEquals(util.parentForURL("http://server/foo/bar/"), "http://server/foo/")
     self.assertEquals(util.parentForURL("/"), None)
     self.assertEquals(util.parentForURL("/foo/.."), None)
     self.assertEquals(util.parentForURL("/foo/../"), None)
     self.assertEquals(util.parentForURL("/foo/."), "/")
     self.assertEquals(util.parentForURL("/foo/./"), "/")
     self.assertEquals(util.parentForURL("/foo"), "/")
     self.assertEquals(util.parentForURL("/foo"), "/")
     self.assertEquals(util.parentForURL("/foo/bar/.."), "/")
     self.assertEquals(util.parentForURL("/foo/bar/."), "/foo/")
     self.assertEquals(util.parentForURL("/foo/bar"), "/foo/")
     self.assertEquals(util.parentForURL("/foo/bar/"), "/foo/")
Exemple #23
0
def report_DAV__expand_property(self, request, expand_property):
    """
    Generate an expand-property REPORT. (RFC 3253, section 3.8)
    
    TODO: for simplicity we will only support one level of expansion.
    """
    # Verify root element
    if not isinstance(expand_property, davxml.ExpandProperty):
        raise ValueError("%s expected as root element, not %s."
                         % (davxml.ExpandProperty.sname(), expand_property.sname()))

    # Only handle Depth: 0
    depth = request.headers.getHeader("depth", "0")
    if depth != "0":
        log.err("Non-zero depth is not allowed: %s" % (depth,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
    
    #
    # Get top level properties to expand and make sure we only have one level
    #
    properties = {}

    for property in expand_property.children:
        namespace = property.attributes.get("namespace", dav_namespace)
        name      = property.attributes.get("name", "")
        
        # Make sure children have no children
        props_to_find = []
        for child in property.children:
            if child.children:
                log.err("expand-property REPORT only supports single level expansion")
                raise HTTPError(StatusResponse(
                    responsecode.NOT_IMPLEMENTED,
                    "expand-property REPORT only supports single level expansion"
                ))
            child_namespace = child.attributes.get("namespace", dav_namespace)
            child_name      = child.attributes.get("name", "")
            props_to_find.append((child_namespace, child_name))

        properties[(namespace, name)] = props_to_find

    #
    # Generate the expanded responses status for each top-level property
    #
    properties_by_status = {
        responsecode.OK        : [],
        responsecode.NOT_FOUND : [],
    }
    
    filteredaces = None
    lastParent = None

    for qname in properties.iterkeys():
        try:
            prop = (yield self.readProperty(qname, request))
            
            # Form the PROPFIND-style DAV:prop element we need later
            props_to_return = davxml.PropertyContainer(*properties[qname])

            # Now dereference any HRefs
            responses = []
            for href in prop.children:
                if isinstance(href, davxml.HRef):
                    
                    # Locate the Href resource and its parent
                    resource_uri = str(href)
                    child = (yield request.locateResource(resource_uri))
    
                    if not child or not child.exists():
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
                        continue
                    parent = (yield request.locateResource(parentForURL(resource_uri)))
    
                    # Check privileges on parent - must have at least DAV:read
                    try:
                        yield parent.checkPrivileges(request, (davxml.Read(),))
                    except AccessDeniedError:
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                        continue
                    
                    # Cache the last parent's inherited aces for checkPrivileges optimization
                    if lastParent != parent:
                        lastParent = parent
                
                        # Do some optimisation of access control calculation by determining any inherited ACLs outside of
                        # the child resource loop and supply those to the checkPrivileges on each child.
                        filteredaces = (yield parent.inheritedACEsforChildren(request))

                    # Check privileges - must have at least DAV:read
                    try:
                        yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
                    except AccessDeniedError:
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                        continue
            
                    # Now retrieve all the requested properties on the HRef resource
                    yield prop_common.responseForHref(
                        request,
                        responses,
                        href,
                        child,
                        prop_common.propertyListForResource,
                        props_to_return,
                    )
            
            prop.children = responses
            properties_by_status[responsecode.OK].append(prop)
        except:
            f = Failure()

            log.err("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))

            status = statusForFailure(f, "getting property: %s" % (qname,))
            if status not in properties_by_status: properties_by_status[status] = []
            properties_by_status[status].append(propertyName(qname))

    # Build the overall response
    propstats = [
        davxml.PropertyStatus(
            davxml.PropertyContainer(*properties_by_status[status]),
            davxml.Status.fromResponseCode(status)
        )
        for status in properties_by_status if properties_by_status[status]
    ]

    returnValue(MultiStatusResponse((davxml.PropertyStatusResponse(davxml.HRef(request.uri), *propstats),)))