示例#1
0
        def _gotResult(_):

            if not request.args:
                # Do normal GET behavior
                return self.render(request)

            method = request.args.get("method", ("", ))
            if len(method) != 1:
                raise HTTPError(
                    ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "valid-method"),
                        "Invalid method query parameter",
                    ))
            method = method[0]

            action = {
                "list": self.doPOSTList,
                "get": self.doPOSTGet,
                "expand": self.doPOSTExpand,
            }.get(method, None)

            if action is None:
                raise HTTPError(
                    ErrorResponse(
                        responsecode.BAD_REQUEST,
                        (calendarserver_namespace, "supported-method"),
                        "Unknown method query parameter",
                    ))

            return action(request)
示例#2
0
    def loadCalendarFromRequest(self, request):
        # Must be content-type text/calendar
        contentType = request.headers.getHeader("content-type")
        format = self.determineType(contentType)
        if format is None:
            self.log.error("MIME type %s not allowed in calendar collection" %
                           (contentType, ))
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (caldav_namespace, "supported-calendar-data"),
                    "Data is not calendar data",
                ))

        # Parse the calendar object from the HTTP request stream
        try:
            calendar = (yield Component.fromIStream(request.stream,
                                                    format=format))
        except:
            # FIXME: Bare except
            self.log.error("Error while handling POST: %s" % (Failure(), ))
            raise HTTPError(
                ErrorResponse(responsecode.FORBIDDEN,
                              (caldav_namespace, "valid-calendar-data"),
                              description="Can't parse calendar data"))

        returnValue((
            calendar,
            format,
        ))
示例#3
0
    def acceptShare(self, request, inviteUID, summary):

        # Accept the share
        try:
            shareeView = yield self._newStoreHome.acceptShare(
                inviteUID, summary)
        except DirectoryRecordNotFoundError:
            # Missing sharer record => fail request
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (calendarserver_namespace, "invalid-share"),
                    "Invite UID not valid",
                ))
        if shareeView is None:
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (calendarserver_namespace, "invalid-share"),
                    "Invite UID not valid",
                ))

        # Return the URL of the shared collection
        sharedAsURL = joinURL(self.url(), shareeView.shareName())
        returnValue(
            XMLResponse(code=responsecode.OK,
                        element=customxml.SharedAs(
                            element.HRef.fromString(sharedAsURL))))
示例#4
0
    def doPOSTGet(self, request):
        """
        Return the specified timezone data.
        """

        tzid = request.args.get("tzid", ())
        if len(tzid) != 1:
            raise HTTPError(
                ErrorResponse(
                    responsecode.BAD_REQUEST,
                    (calendarserver_namespace, "valid-timezone"),
                    "Invalid tzid query parameter",
                ))
        tzid = tzid[0]

        try:
            tzdata = readTZ(tzid)
        except TimezoneException:
            raise HTTPError(
                ErrorResponse(
                    responsecode.NOT_FOUND,
                    (calendarserver_namespace, "timezone-available"),
                    "Timezone not found",
                ))

        response = Response()
        response.stream = MemoryStream(tzdata)
        response.headers.setHeader(
            "content-type",
            MimeType.fromString("text/calendar; charset=utf-8"))
        return response
示例#5
0
    def loadCalendarFromRequest(self, request):
        # Must be content-type text/calendar
        contentType = request.headers.getHeader("content-type")
        if contentType is not None and (contentType.mediaType, contentType.
                                        mediaSubtype) != ("text", "calendar"):
            self.log.error(
                "MIME type {ct} not allowed in iSchedule POST request",
                ct=contentType,
            )
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (ischedule_namespace, "invalid-calendar-data-type"),
                    "Data is not calendar data",
                ))

        # Parse the calendar object from the HTTP request stream
        try:
            calendar = (yield Component.fromIStream(request.stream))
        except:
            # FIXME: Bare except
            self.log.error(
                "Error while handling iSchedule POST: {f}",
                f=Failure(),
            )
            raise HTTPError(
                ErrorResponse(responsecode.FORBIDDEN,
                              (ischedule_namespace, "invalid-calendar-data"),
                              description="Can't parse calendar data"))

        returnValue(calendar)
    def doPOSTExpand(self, request):
        """
        Expand a timezone within specified start/end dates.
        """

        tzid = request.args.get("tzid", ())
        if len(tzid) != 1:
            raise HTTPError(ErrorResponse(
                responsecode.BAD_REQUEST,
                (calendarserver_namespace, "valid-timezone"),
                "Invalid tzid query parameter",
            ))
        tzid = tzid[0]
        try:
            tzdata = readTZ(tzid)
        except TimezoneException:
            raise HTTPError(ErrorResponse(
                responsecode.NOT_FOUND,
                (calendarserver_namespace, "timezone-available"),
                "Timezone not found",
            ))

        try:
            start = request.args.get("start", ())
            if len(start) != 1:
                raise ValueError()
            start = DateTime.parseText(start[0])
        except ValueError:
            raise HTTPError(ErrorResponse(
                responsecode.BAD_REQUEST,
                (calendarserver_namespace, "valid-start-date"),
                "Invalid start query parameter",
            ))

        try:
            end = request.args.get("end", ())
            if len(end) != 1:
                raise ValueError()
            end = DateTime.parseText(end[0])
            if end <= start:
                raise ValueError()
        except ValueError:
            raise HTTPError(ErrorResponse(
                responsecode.BAD_REQUEST,
                (calendarserver_namespace, "valid-end-date"),
                "Invalid end query parameter",
            ))

        # Now do the expansion (but use a cache to avoid re-calculating TZs)
        observances = self.cache.get((tzid, start, end), None)
        if observances is None:
            observances = tzexpand(tzdata, start, end)
            self.cache[(tzid, start, end)] = observances

        # Turn into XML
        result = customxml.TZData(
            *[customxml.Observance(customxml.Onset(onset), customxml.UTCOffset(utc_offset)) for onset, utc_offset in observances]
        )
        return XMLResponse(responsecode.OK, result)
def report_urn_ietf_params_xml_ns_caldav_free_busy_query(self, request, freebusy):
    """
    Generate a free-busy REPORT.
    (CalDAV-access-09, section 7.8)
    """
    if not self.isCollection():
        log.error("freebusy report is only allowed on collection resources {s!r}", s=self)
        raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not a calendar collection"))

    if freebusy.qname() != (caldavxml.caldav_namespace, "free-busy-query"):
        raise ValueError("{CalDAV:}free-busy-query expected as root element, not %s." % (freebusy.sname(),))

    timerange = freebusy.timerange
    if not timerange.valid():
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid time-range specified"))

    fbset = []

    accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes())
    if accepted_type is None:
        raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type"))

    def getCalendarList(calresource, uri):  # @UnusedVariable
        """
        Store the calendars that match the query in L{fbset} which will then be used with the
        freebusy query.

        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collection resource.
        """

        fbset.append(calresource._newStoreObject)
        return succeed(True)

    # Run report taking depth into account
    depth = request.headers.getHeader("depth", "0")
    yield report_common.applyToCalendarCollections(self, request, request.uri, depth, getCalendarList, (caldavxml.ReadFreeBusy(),))

    # Do the actual freebusy query against the set of matched calendars
    principal = yield self.resourceOwnerPrincipal(request)
    organizer = recipient = LocalCalendarUser(principal.canonicalCalendarUserAddress(), principal.record)
    timerange = Period(timerange.start, timerange.end)
    try:
        fbresult = yield FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange).generateAttendeeFreeBusyResponse(fbset=fbset, method=None)
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in free-busy report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many components"
        ))
    except TimeRangeLowerLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MinDateTime(),
            "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),)
        ))
示例#8
0
    def writeProperty(self, property, request):
        assert isinstance(property, davxml.WebDAVElement)

        # Strictly speaking CS:calendar-availability is a live property in the sense that the
        # server enforces what can be stored, however it need not actually
        # exist so we cannot list it in liveProperties on this resource, since its
        # its presence there means that hasProperty will always return True for it.
        if property.qname() == customxml.CalendarAvailability.qname():
            if not property.valid():
                raise HTTPError(ErrorResponse(
                    responsecode.CONFLICT,
                    (caldav_namespace, "valid-calendar-data"),
                    description="Invalid property"
                ))
            yield self.parent._newStoreHome.setAvailability(property.calendar())
            returnValue(None)

        elif property.qname() == caldavxml.CalendarFreeBusySet.qname():
            # Verify that the calendars added in the PROPPATCH are valid. We do not check
            # whether existing items in the property are still valid - only new ones.
            property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
            new_calendars = set([str(href) for href in property.children])
            old_calendars = set()
            for cal in (yield self.parent._newStoreHome.calendars()):
                if cal.isUsedForFreeBusy():
                    old_calendars.add(HRef(joinURL(self.parent.url(), cal.name())))
            added_calendars = new_calendars.difference(old_calendars)
            for href in added_calendars:
                cal = (yield request.locateResource(str(href)))
                if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
                    # Validate that href's point to a valid calendar.
                    raise HTTPError(ErrorResponse(
                        responsecode.CONFLICT,
                        (caldav_namespace, "valid-calendar-url"),
                        "Invalid URI",
                    ))

            # Remove old ones
            for href in old_calendars.difference(new_calendars):
                cal = (yield request.locateResource(str(href)))
                if cal is not None and cal.exists() and isCalendarCollectionResource(cal) and cal._newStoreObject.isUsedForFreeBusy():
                    yield cal._newStoreObject.setUsedForFreeBusy(False)

            # Add new ones
            for href in new_calendars:
                cal = (yield request.locateResource(str(href)))
                if cal is not None and cal.exists() and isCalendarCollectionResource(cal) and not cal._newStoreObject.isUsedForFreeBusy():
                    yield cal._newStoreObject.setUsedForFreeBusy(True)

            returnValue(None)

        elif property.qname() in (caldavxml.ScheduleDefaultCalendarURL.qname(), customxml.ScheduleDefaultTasksURL.qname()):
            yield self.writeDefaultCalendarProperty(request, property)
            returnValue(None)

        yield super(ScheduleInboxResource, self).writeProperty(property, request)
示例#9
0
    def writeDefaultCalendarProperty(self, request, property):
        """
        Write either the default VEVENT or VTODO calendar property, validating and canonicalizing the value
        """
        if property.qname() == caldavxml.ScheduleDefaultCalendarURL.qname():
            ctype = "VEVENT"
            error_element = (caldav_namespace,
                             "valid-schedule-default-calendar-URL")
        elif property.qname() == customxml.ScheduleDefaultTasksURL.qname():
            ctype = "VTODO"
            error_element = (calendarserver_namespace,
                             "valid-schedule-default-tasks-URL")
        else:
            returnValue(None)

        # Verify that the calendar added in the PROPPATCH is valid.
        property.children = [
            davxml.HRef(normalizeURL(str(href))) for href in property.children
        ]
        new_calendar = [str(href) for href in property.children]
        cal = None
        if len(new_calendar) == 1:
            cal = (yield request.locateResource(str(new_calendar[0])))
        else:
            raise HTTPError(
                ErrorResponse(
                    responsecode.BAD_REQUEST,
                    error_element,
                    "Invalid HRef in property",
                ))

        if cal is None or not cal.exists():
            raise HTTPError(
                ErrorResponse(
                    responsecode.BAD_REQUEST,
                    error_element,
                    "HRef is not a valid calendar",
                ))

        try:
            # Now set it on the new store object
            yield self.parent._newStoreHome.setDefaultCalendar(
                cal._newStoreObject, ctype)
        except InvalidDefaultCalendar as e:
            raise HTTPError(
                ErrorResponse(
                    responsecode.CONFLICT,
                    error_element,
                    str(e),
                ))
示例#10
0
    def _handleInviteReply(self, request, invitereplydoc):
        """
        Handle a user accepting or declining a sharing invite
        """
        hostUrl = None
        accepted = None
        summary = None
        replytoUID = None
        for item in invitereplydoc.children:
            if isinstance(item, customxml.InviteStatusAccepted):
                accepted = True
            elif isinstance(item, customxml.InviteStatusDeclined):
                accepted = False
            elif isinstance(item, customxml.InviteSummary):
                summary = str(item)
            elif isinstance(item, customxml.HostURL):
                for hosturlItem in item.children:
                    if isinstance(hosturlItem, element.HRef):
                        hostUrl = str(hosturlItem)
            elif isinstance(item, customxml.InReplyTo):
                replytoUID = str(item)

        if accepted is None or hostUrl is None or replytoUID is None:
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (customxml.calendarserver_namespace, "valid-request"),
                    "Missing required XML elements",
                ))
        if accepted:
            return self.acceptShare(request, replytoUID, summary=summary)
        else:
            return self.declineShare(request, replytoUID)
示例#11
0
 def _handleInviteSet(inviteset):
     userid = None
     cn = None
     access = None
     summary = None
     for item in inviteset.children:
         if isinstance(item, element.HRef):
             userid = str(item)
             continue
         if isinstance(item, customxml.CommonName):
             cn = str(item)
             continue
         if isinstance(item, customxml.InviteSummary):
             summary = str(item)
             continue
         if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
             access = item
             continue
     if userid and access and summary:
         return (userid, cn, access, summary)
     else:
         error_text = []
         if userid is None:
             error_text.append("missing href")
         if access is None:
             error_text.append("missing access")
         if summary is None:
             error_text.append("missing summary")
         raise HTTPError(ErrorResponse(
             responsecode.FORBIDDEN,
             (customxml.calendarserver_namespace, "valid-request"),
             "%s: %s" % (", ".join(error_text), inviteset,),
         ))
示例#12
0
    def loadOriginatorFromRequestDetails(self, request):
        # Get the originator who is the authenticated user
        originatorPrincipal = None
        originator = ""
        authz_principal = self.currentPrincipal(request).children[0]
        if isinstance(authz_principal, davxml.HRef):
            originatorPrincipalURL = str(authz_principal)
            if originatorPrincipalURL:
                originatorPrincipal = (
                    yield request.locateResource(originatorPrincipalURL))
                if originatorPrincipal:
                    # Pick the canonical CUA:
                    originator = originatorPrincipal.canonicalCalendarUserAddress(
                    )

        if not originator:
            self.log.error("%s request must have Originator" % (self.method, ))
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (caldav_namespace, "originator-specified"),
                    "Missing originator",
                ))
        else:
            returnValue(originator)
示例#13
0
 def test_statusForFailure_HTTPError(self):
     """
     statusForFailure() for HTTPErrors
     """
     for code in responsecode.RESPONSES:
         self._check_exception(HTTPError(code), code)
         self._check_exception(HTTPError(ErrorResponse(code, ("http://twistedmatrix.com/", "bar"))), code)
示例#14
0
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)))
示例#15
0
 def _xmlHandleInviteReply(self, request, docroot):
     # Sharing must be enabled for this collection
     if not self.canShare():
         raise HTTPError(ErrorResponse(
             responsecode.FORBIDDEN,
             (customxml.calendarserver_namespace, "valid-request"),
             "Sharing not supported on this resource",
         ))
     yield self.authorize(request, (element.Read(), element.Write()))
     result = (yield self._handleInviteReply(request, docroot))
     returnValue(result)
        def doDirectoryAddressBookResponse():

            directoryAddressBookLock = None
            try:
                # Verify that requested resources are immediate children of the request-URI
                # and get vCardFilters ;similar to "normal" case below but do not call getChild()
                vCardFilters = []
                valid_hrefs = []
                for href in resources:
                    resource_uri = str(href)
                    resource_name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4:
                        valid_hrefs.append(href)
                        textMatchElement = carddavxml.TextMatch.fromString(resource_name[:-4])
                        textMatchElement.attributes["match-type"] = "equals" # do equals compare. Default is "contains"
                        vCardFilters.append(carddavxml.PropertyFilter(
                            textMatchElement,
                            name="UID", # attributes
                        ))
                    else:
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))

                # exit if not valid
                if not vCardFilters or not valid_hrefs:
                    returnValue(None)

                addressBookFilter = carddavxml.Filter(*vCardFilters)
                addressBookFilter = Filter(addressBookFilter)

                # get vCards and filter
                limit = config.DirectoryAddressBook.MaxQueryResults
                results, limited = (yield self.doAddressBookDirectoryQuery(addressBookFilter, propertyreq, limit, defaultKind=None))
                if limited:
                    log.error("Too many results in multiget report: {count}", count=len(resources))
                    raise HTTPError(ErrorResponse(
                        responsecode.FORBIDDEN,
                        (dav_namespace, "number-of-matches-within-limits"),
                        "Too many results",
                    ))

                for href in valid_hrefs:
                    matchingResource = None
                    for vCardResource in results:
                        if href == vCardResource.hRef(): # might need to compare urls instead - also case sens ok?
                            matchingResource = vCardResource
                            break

                    if matchingResource:
                        yield report_common.responseForHref(request, responses, href, matchingResource, propertiesForResource, propertyreq, vcard=matchingResource.vCard())
                    else:
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
            finally:
                if directoryAddressBookLock:
                    yield directoryAddressBookLock.release()
示例#17
0
 def failForRecipient(recipient):
     err = HTTPError(
         ErrorResponse(
             responsecode.FORBIDDEN,
             (caldav_namespace, "recipient-failed"),
             "iMIP request failed",
         ))
     self.responses.add(recipient.cuaddr,
                        Failure(exc_value=err),
                        reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE,
                        suppressErrorLog=True)
示例#18
0
    def xmlRequestHandler(self, request):

        # Need to read the data and get the root element first
        xmldata = (yield allDataFromStream(request.stream))
        try:
            doc = element.WebDAVDocument.fromString(xmldata)
        except ValueError, e:
            self.log.error("Error parsing doc ({ex}) Doc:\n {x}", ex=str(e), x=xmldata)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (customxml.calendarserver_namespace, "valid-request"),
                "Invalid XML",
            ))
示例#19
0
 def loadOriginatorFromRequestHeaders(self, request):
     # Must have Originator header
     originator = request.headers.getRawHeaders("originator")
     if originator is None or (len(originator) != 1):
         self.log.error("iSchedule POST request must have Originator header")
         raise HTTPError(ErrorResponse(
             responsecode.FORBIDDEN,
             (ischedule_namespace, "originator-missing"),
             "Missing originator",
         ))
     else:
         originator = originator[0]
     return originator
示例#20
0
    def declineShare(self, request, inviteUID):

        # Remove it if it is in the DB
        try:
            result = yield self._newStoreHome.declineShare(inviteUID)
        except DirectoryRecordNotFoundError:
            # Missing sharer record => just treat decline as success
            result = True
        if not result:
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (calendarserver_namespace, "invalid-share"),
                "Invite UID not valid",
            ))
        returnValue(Response(code=responsecode.NO_CONTENT))
示例#21
0
    def generateFreeBusyResponse(self, recipient, responses, organizerProp, organizerPrincipal, uid, event_details):

        # Extract the ATTENDEE property matching current recipient from the calendar data
        cuas = recipient.record.calendarUserAddresses
        attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas)

        remote = isinstance(self.scheduler.organizer, RemoteCalendarUser)

        try:
            fbresult = (yield self.generateAttendeeFreeBusyResponse(
                recipient,
                organizerProp,
                organizerPrincipal,
                uid,
                attendeeProp,
                remote,
                event_details,
            ))
        except Exception:
            log.failure(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr, level=LogLevel.debug
            )
            log.error(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr
            )
            err = HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-permissions"),
                "Could not determine free busy information",
            ))
            responses.add(
                recipient.cuaddr,
                Failure(exc_value=err),
                reqstatus=iTIPRequestStatus.NO_AUTHORITY
            )
            returnValue(False)
        else:
            responses.add(
                recipient.cuaddr,
                responsecode.OK,
                reqstatus=iTIPRequestStatus.SUCCESS,
                calendar=fbresult
            )
            returnValue(True)
示例#22
0
    def generateFreeBusyResponse(self, recipient, responses, organizerProp, uid, event_details):

        # Extract the ATTENDEE property matching current recipient from the calendar data
        cuas = recipient.record.calendarUserAddresses
        attendeeProp = self.scheduler.calendar.getAttendeeProperty(cuas)

        try:
            fbresult = yield FreebusyQuery(
                organizer=self.scheduler.organizer,
                organizerProp=organizerProp,
                recipient=recipient,
                attendeeProp=attendeeProp,
                uid=uid,
                timerange=self.scheduler.timeRange,
                excludeUID=self.scheduler.excludeUID,
                logItems=self.scheduler.logItems,
                event_details=event_details,
            ).generateAttendeeFreeBusyResponse()
        except Exception as e:
            log.failure(
                "Could not determine free busy information for recipient {cuaddr}",
                cuaddr=recipient.cuaddr, level=LogLevel.debug
            )
            log.error(
                "Could not determine free busy information for recipient {cuaddr}: {ex}",
                cuaddr=recipient.cuaddr, ex=e
            )
            err = HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-permissions"),
                "Could not determine free busy information",
            ))
            responses.add(
                recipient.cuaddr,
                Failure(exc_value=err),
                reqstatus=iTIPRequestStatus.NO_AUTHORITY
            )
            returnValue(False)
        else:
            responses.add(
                recipient.cuaddr,
                responsecode.OK,
                reqstatus=iTIPRequestStatus.SUCCESS,
                calendar=fbresult
            )
            returnValue(True)
示例#23
0
    def loadOriginatorFromRequestDetails(self, request):
        # The originator is the owner of the Outbox. We will have checked prior to this
        # that the authenticated user has privileges to schedule as the owner.
        originator = ""
        originatorPrincipal = (yield self.ownerPrincipal(request))
        if originatorPrincipal:
            # Pick the canonical CUA:
            originator = originatorPrincipal.canonicalCalendarUserAddress()

        if not originator:
            self.log.error("{m} request must have Originator", m=self.method)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "originator-specified"),
                "Missing originator",
            ))
        else:
            returnValue(originator)
示例#24
0
    def loadRecipientsFromCalendarData(self, calendar):

        # Get the ATTENDEEs
        attendees = list()
        unique_set = set()
        for attendee, _ignore in calendar.getAttendeesByInstance():
            if attendee not in unique_set:
                attendees.append(attendee)
                unique_set.add(attendee)

        if not attendees:
            self.log.error("POST request must have at least one ATTENDEE")
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "recipient-specified"),
                "Must have recipients",
            ))
        else:
            return(list(attendees))
示例#25
0
 def _handleInviteRemove(inviteremove):
     userid = None
     access = []
     for item in inviteremove.children:
         if isinstance(item, element.HRef):
             userid = str(item)
             continue
         if isinstance(item, customxml.ReadAccess) or isinstance(item, customxml.ReadWriteAccess):
             access.append(item)
             continue
     if userid is None:
         raise HTTPError(ErrorResponse(
             responsecode.FORBIDDEN,
             (customxml.calendarserver_namespace, "valid-request"),
             "Missing href: %s" % (inviteremove,),
         ))
     if len(access) == 0:
         access = None
     else:
         access = set(access)
     return (userid, access)
示例#26
0
    def loadRecipientsFromRequestHeaders(self, request):
        # Get list of Recipient headers
        rawRecipients = request.headers.getRawHeaders("recipient")
        if rawRecipients is None or (len(rawRecipients) == 0):
            self.log.error(
                "{method} request must have at least one Recipient header",
                method=self.method,
            )
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (ischedule_namespace, "recipient-missing"),
                "No recipients",
            ))

        # Recipient header may be comma separated list
        recipients = []
        for rawRecipient in rawRecipients:
            for r in rawRecipient.split(","):
                r = r.strip()
                if len(r):
                    recipients.append(r)

        return recipients
def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
    """
    Generate a calendar-query REPORT.
    (CalDAV-access-09, section 7.6)
    """

    # Verify root element
    if calendar_query.qname() != (caldav_namespace, "calendar-query"):
        raise ValueError("{CalDAV:}calendar-query expected as root element, not %s." % (calendar_query.sname(),))

    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))
        if not parent.isPseudoCalendarCollection():
            log.error("calendar-query report is not allowed on a resource outside of a calendar collection %s" % (self,))
            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar collection or calendar resource"))

    responses = []

    xmlfilter = calendar_query.filter
    filter = Filter(xmlfilter)
    props = calendar_query.props

    assert props is not None

    # Get the original timezone provided in the query, if any, and validate it now
    query_timezone = None
    query_tz = calendar_query.timezone
    if query_tz is not None and not query_tz.valid():
        msg = "CalDAV:timezone must contain one VTIMEZONE component only: %s" % (query_tz,)
        log.error(msg)
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (caldav_namespace, "valid-calendar-data"),
            "Invalid calendar-data",
        ))
    if query_tz:
        filter.settimezone(query_tz)
        query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0]

    if props.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource
        generate_calendar_data = False

    elif props.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource
        generate_calendar_data = False

    elif props.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource

        # Verify that any calendar-data element matches what we can handle
        result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props)
        if not result:
            log.error(message)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                (caldav_namespace, "supported-calendar-data"),
                "Invalid calendar-data",
            ))

    else:
        raise AssertionError("We shouldn't be here")

    # Verify that the filter element is valid
    if (filter is None) or not filter.valid():
        log.error("Invalid filter element: %r" % (xmlfilter,))
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            (caldav_namespace, "valid-filter"),
            "Invalid filter element",
        ))

    matchcount = [0]
    max_number_of_results = [config.MaxQueryWithDataResults if generate_calendar_data else None, ]

    @inlineCallbacks
    def doQuery(calresource, uri):
        """
        Run a query on the specified calendar collection
        accumulating the query responses.
        @param calresource: the L{CalDAVResource} for a calendar collection.
        @param uri: the uri for the calendar collection resource.
        """

        @inlineCallbacks
        def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True):
            """
            Run a query on the specified calendar.
            @param resource: the L{CalDAVResource} for the calendar.
            @param uri: the uri of the resource.
            @param name: the name of the resource.
            @param calendar: the L{Component} calendar read from the resource.
            """

            # Handle private events access restrictions
            if not isowner:
                access = resource.accessMode
            else:
                access = None

            if query_ok or filter.match(calendar, access):
                # Check size of results is within limit
                matchcount[0] += 1
                if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]:
                    raise NumberOfMatchesWithinLimits(max_number_of_results[0])

                if name:
                    href = davxml.HRef.fromString(joinURL(uri, name))
                else:
                    href = davxml.HRef.fromString(uri)

                try:
                    yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone)
                except ConcurrentModification:
                    # This can happen because of a race-condition between the
                    # time we determine which resources exist and the deletion
                    # of one of these resources in another request.  In this
                    # case, we ignore the now missing resource rather
                    # than raise an error for the entire report.
                    log.error("Missing resource during query: %s" % (href,))

        # Check whether supplied resource is a calendar or a calendar object resource
        if calresource.isPseudoCalendarCollection():
            # Get the timezone property from the collection if one was not set in the query,
            # and store in the query filter for later use
            has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request))
            timezone = query_timezone
            if query_tz is None and has_prop:
                tz = (yield calresource.readProperty(CalendarTimeZone(), request))
                filter.settimezone(tz)
                timezone = tuple(tz.calendar().subcomponents())[0]

            # Do some optimization 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 calresource.inheritedACEsforChildren(request))

            # Check private events access status
            isowner = (yield calresource.isOwner(request))

            # Check for disabled access
            if filteredaces is not None:
                index_query_ok = True
                try:
                    # Get list of children that match the search and have read access
                    names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))]
                except IndexedSearchException:
                    names = yield calresource.listChildren()
                    index_query_ok = False

                if not names:
                    returnValue(True)

                # Now determine which valid resources are readable and which are not
                ok_resources = []
                yield calresource.findChildrenFaster(
                    "1",
                    request,
                    lambda x, y: ok_resources.append((x, y)),
                    None,
                    None,
                    None,
                    names,
                    (davxml.Read(),),
                    inherited_aces=filteredaces
                )

                for child, child_uri in ok_resources:
                    child_uri_name = child_uri[child_uri.rfind("/") + 1:]

                    if generate_calendar_data or not index_query_ok:
                        calendar = (yield child.iCalendarForUser(request))
                        assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self)
                    else:
                        calendar = None

                    yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner)
        else:
            # Get the timezone property from the collection if one was not set in the query,
            # and store in the query object for later use
            timezone = query_timezone
            if query_tz is None:

                parent = (yield calresource.locateParent(request, uri))
                assert parent is not None and parent.isPseudoCalendarCollection()

                has_prop = (yield parent.hasProperty(CalendarTimeZone(), request))
                if has_prop:
                    tz = (yield parent.readProperty(CalendarTimeZone(), request))
                    filter.settimezone(tz)
                    timezone = tuple(tz.calendar().subcomponents())[0]

            # Check private events access status
            isowner = (yield calresource.isOwner(request))

            calendar = (yield calresource.iCalendarForUser(request))
            yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)

        returnValue(True)

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
    except TooManyInstancesError, ex:
        log.error("Too many instances need to be computed in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            MaxInstances.fromString(str(ex.max_allowed)),
            "Too many instances",
        ))
    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
    except TooManyInstancesError, ex:
        log.error("Too many instances need to be computed in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            MaxInstances.fromString(str(ex.max_allowed)),
            "Too many instances",
        ))
    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in calendar-query report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many components",
        ))
    except TimeRangeLowerLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MinDateTime(),
            "Time-range value too far in the past. Must be on or after %s." % (str(e.limit),)
        ))
    except TimeRangeUpperLimit, e:
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            caldavxml.MaxDateTime(),
            "Time-range value too far in the future. Must be on or before %s." % (str(e.limit),)
        ))
示例#29
0
        error = "Request XML body is required."
        log.error("Error: {err}", err=error)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))

    #
    # Parse request
    #
    acl = doc.root_element
    if not isinstance(acl, davxml.ACL):
        error = ("Request XML body must be an acl element."
                 % (davxml.PropertyUpdate.sname(),))
        log.error("Error: {err}", err=error)
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))

    #
    # Do ACL merger
    #
    result = waitForDeferred(self.mergeAccessControlList(acl, request))
    yield result
    result = result.getResult()

    #
    # Return response
    #
    if result is None:
        yield responsecode.OK
    else:
        yield ErrorResponse(responsecode.FORBIDDEN, result)

http_ACL = deferredGenerator(http_ACL)
示例#30
0
def report_DAV__principal_property_search(self, request, principal_property_search):
    """
    Generate a principal-property-search REPORT. (RFC 3744, section 9.4)
    """

    # Verify root element
    if not isinstance(principal_property_search, element.PrincipalPropertySearch):
        raise ValueError("%s expected as root element, not %s."
                         % (element.PrincipalPropertySearch.sname(), principal_property_search.sname()))

    # Only handle Depth: 0
    depth = request.headers.getHeader("depth", "0")
    if depth != "0":
        log.error("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
        raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
    
    # Get a single DAV:prop element from the REPORT request body
    propertiesForResource = None
    propElement = None
    propertySearches = []
    applyTo = False
    for child in principal_property_search.children:
        if child.qname() == (dav_namespace, "prop"):
            propertiesForResource = prop_common.propertyListForResource
            propElement = child
        elif child.qname() == (dav_namespace, "apply-to-principal-collection-set"):
            applyTo = True
        elif child.qname() == (dav_namespace, "property-search"):
            props = child.childOfType(element.PropertyContainer)
            props.removeWhitespaceNodes()
            match = child.childOfType(element.Match)
            propertySearches.append((props.children, str(match).lower()))
    
    def nodeMatch(node, match):
        """
        See if the content of the supplied node matches the supplied text.
        Try to follow the matching guidance in rfc3744 section 9.4.1.
        @param prop:  the property element to match.
        @param match: the text to match against.
        @return:      True if the property matches, False otherwise.
        """
        node.removeWhitespaceNodes()
        for child in node.children:
            if isinstance(child, PCDATAElement):
                comp = str(child).lower()
                if comp.find(match) != -1:
                    return True
            else:
                return nodeMatch(child, match)
        else:
            return False
        
    def propertySearch(resource, request):
        """
        Test the resource to see if it contains properties matching the
        property-search specification in this report.
        @param resource: the L{DAVFile} for the resource to test.
        @param request:  the current request.
        @return:         True if the resource has matching properties, False otherwise.
        """
        for props, match in propertySearches:
            # Test each property
            for prop in props:
                try:
                    propvalue = waitForDeferred(resource.readProperty(prop.qname(), request))
                    yield propvalue
                    propvalue = propvalue.getResult()
                    if propvalue and not nodeMatch(propvalue, match):
                        yield False
                        return
                except HTTPError:
                    # No property => no match
                    yield False
                    return
        
        yield True

    propertySearch = deferredGenerator(propertySearch)

    # Run report
    try:
        resources = []
        responses = []
        matchcount = 0

        if applyTo:
            for principalCollection in self.principalCollections():
                uri = principalCollection.principalCollectionURL()
                resource = waitForDeferred(request.locateResource(uri))
                yield resource
                resource = resource.getResult()
                if resource:
                    resources.append((resource, uri))
        else:
            resources.append((self, request.uri))

        # Loop over all collections and principal resources within
        for resource, ruri in resources:

            # 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 = waitForDeferred(resource.inheritedACEsforChildren(request))
            yield filteredaces
            filteredaces = filteredaces.getResult()

            children = []
            d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
                                                      privileges=(element.Read(),), inherited_aces=filteredaces))
            yield d
            d.getResult()

            for child, uri in children:
                if isPrincipalResource(child):
                    d = waitForDeferred(propertySearch(child, request))
                    yield d
                    d = d.getResult()
                    if d:
                        # Check size of results is within limit
                        matchcount += 1
                        if matchcount > max_number_of_matches:
                            raise NumberOfMatchesWithinLimits(max_number_of_matches)
    
                        d = waitForDeferred(prop_common.responseForHref(
                            request,
                            responses,
                            element.HRef.fromString(uri),
                            child,
                            propertiesForResource,
                            propElement
                        ))
                        yield d
                        d.getResult()

    except NumberOfMatchesWithinLimits:
        log.error("Too many matching components in prinicpal-property-search report")
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            element.NumberOfMatchesWithinLimits()
        ))

    yield MultiStatusResponse(responses)