예제 #1
0
def buildFreeBusyResult(fbinfo,
                        timerange,
                        organizer=None,
                        attendee=None,
                        uid=None,
                        method=None,
                        event_details=None):
    """
    Generate a VCALENDAR object containing a single VFREEBUSY that is the
    aggregate of the free busy info passed in.

    @param fbinfo:        the array of busy periods to use.
    @param timerange:     the L{TimeRange} for the query.
    @param organizer:     the L{Property} for the Organizer of the free busy request, or None.
    @param attendee:      the L{Property} for the Attendee responding to the free busy request, or None.
    @param uid:           the UID value from the free busy request.
    @param method:        the METHOD property value to insert.
    @param event_details: VEVENT components to add.
    @return:              the L{Component} containing the calendar data.
    """

    # Merge overlapping time ranges in each fb info section
    normalizePeriodList(fbinfo[0])
    normalizePeriodList(fbinfo[1])
    normalizePeriodList(fbinfo[2])

    # Now build a new calendar object with the free busy info we have
    fbcalendar = Component("VCALENDAR")
    fbcalendar.addProperty(Property("VERSION", "2.0"))
    fbcalendar.addProperty(Property("PRODID", iCalendarProductID))
    if method:
        fbcalendar.addProperty(Property("METHOD", method))
    fb = Component("VFREEBUSY")
    fbcalendar.addComponent(fb)
    if organizer is not None:
        fb.addProperty(organizer)
    if attendee is not None:
        fb.addProperty(attendee)
    fb.addProperty(Property("DTSTART", timerange.start))
    fb.addProperty(Property("DTEND", timerange.end))
    fb.addProperty(Property("DTSTAMP", DateTime.getNowUTC()))
    if len(fbinfo[0]) != 0:
        fb.addProperty(Property("FREEBUSY", fbinfo[0], {"FBTYPE": "BUSY"}))
    if len(fbinfo[1]) != 0:
        fb.addProperty(
            Property("FREEBUSY", fbinfo[1], {"FBTYPE": "BUSY-TENTATIVE"}))
    if len(fbinfo[2]) != 0:
        fb.addProperty(
            Property("FREEBUSY", fbinfo[2], {"FBTYPE": "BUSY-UNAVAILABLE"}))
    if uid is not None:
        fb.addProperty(Property("UID", uid))
    else:
        uid = str(uuid.uuid4())
        fb.addProperty(Property("UID", uid))

    if event_details:
        for vevent in event_details:
            fbcalendar.addComponent(vevent)

    return fbcalendar
예제 #2
0
    def buildFreeBusyResult(self, fbinfo, method=None):
        """
        Generate a VCALENDAR object containing a single VFREEBUSY that is the
        aggregate of the free busy info passed in.

        @param fbinfo:        the array of busy periods to use.
        @param method:        the METHOD property value to insert.
        @return:              the L{Component} containing the calendar data.
        """

        # Merge overlapping time ranges in each fb info section
        normalizePeriodList(fbinfo.busy)
        normalizePeriodList(fbinfo.tentative)
        normalizePeriodList(fbinfo.unavailable)

        # Now build a new calendar object with the free busy info we have
        fbcalendar = Component("VCALENDAR")
        fbcalendar.addProperty(Property("VERSION", "2.0"))
        fbcalendar.addProperty(Property("PRODID", iCalendarProductID))
        if method:
            fbcalendar.addProperty(Property("METHOD", method))
        fb = Component("VFREEBUSY")
        fbcalendar.addComponent(fb)
        if self.organizerProp is not None:
            fb.addProperty(self.organizerProp)
        if self.attendeeProp is not None:
            fb.addProperty(self.attendeeProp)
        fb.addProperty(Property("DTSTART", self.timerange.getStart()))
        fb.addProperty(Property("DTEND", self.timerange.getEnd()))
        fb.addProperty(Property("DTSTAMP", DateTime.getNowUTC()))
        if len(fbinfo.busy) != 0:
            fb.addProperty(
                Property("FREEBUSY", fbinfo.busy, {"FBTYPE": "BUSY"}))
        if len(fbinfo.tentative) != 0:
            fb.addProperty(
                Property("FREEBUSY", fbinfo.tentative,
                         {"FBTYPE": "BUSY-TENTATIVE"}))
        if len(fbinfo.unavailable) != 0:
            fb.addProperty(
                Property("FREEBUSY", fbinfo.unavailable,
                         {"FBTYPE": "BUSY-UNAVAILABLE"}))
        if self.uid is not None:
            fb.addProperty(Property("UID", self.uid))
        else:
            uid = str(uuid.uuid4())
            fb.addProperty(Property("UID", uid))

        if self.event_details:
            for vevent in self.event_details:
                fbcalendar.addComponent(vevent)

        return fbcalendar
    def test_single_events(self):
        """
        Single events in calendar collection
        """
        work = []

        stream = self.openHolidays()
        calendar = Component.fromStream(stream)

        for subcomponent in calendar.subcomponents():
            if subcomponent.name() == "VEVENT":
                subcalendar = Component("VCALENDAR")
                subcalendar.addComponent(subcomponent)
                for property in calendar.properties():
                    subcalendar.addProperty(property)
                work.append((MemoryStream(str(subcalendar)), responsecode.CREATED))

        return self._test_file_in_calendar("single event in calendar", *work)
예제 #4
0
    def compFilter(self, comp, component):
        """
        Returns a calendar component object containing the data in the given
        component which is specified by this CalendarComponent.
        """
        if comp.type != component.name():
            raise ValueError(
                "%s of type %r can't get data from component of type %r" %
                (comp.sname(), comp.type, component.name()))

        result = Component(comp.type)

        xml_components = comp.components
        xml_properties = comp.properties

        # Empty element means do all properties and components
        if xml_components is None and xml_properties is None:
            xml_components = AllComponents()
            xml_properties = AllProperties()

        if xml_components is not None:
            if xml_components == AllComponents():
                for ical_subcomponent in component.subcomponents():
                    result.addComponent(ical_subcomponent)
            else:
                for xml_subcomponent in xml_components:
                    for ical_subcomponent in component.subcomponents():
                        if ical_subcomponent.name() == xml_subcomponent.type:
                            result.addComponent(
                                self.compFilter(xml_subcomponent,
                                                ical_subcomponent))

        if xml_properties is not None:
            if xml_properties == AllProperties():
                for ical_property in component.properties():
                    result.addProperty(ical_property)
            else:
                for xml_property in xml_properties:
                    name = xml_property.property_name
                    for ical_property in component.properties(name):
                        result.addProperty(ical_property)

        return result
예제 #5
0
    def test_calendar_query_timezone(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        tzid1 = "Etc/GMT+1"
        tz1 = Component(None, pycalendar=readVTZ(tzid1))

        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
            caldavxml.TimeZone.fromCalendar(tz1),
        )

        def got_xml(doc):
            if not isinstance(doc.root_element, davxml.MultiStatus):
                self.fail(
                    "REPORT response XML root element is not multistatus: %r" %
                    (doc.root_element, ))

        return self.calendar_query(query, got_xml)
예제 #6
0
    def test_calendar_query_wrong_timezone_elements(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        tzid1 = "Etc/GMT+1"
        tz1 = Component(None, pycalendar=readVTZ(tzid1))

        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
            caldavxml.TimeZone.fromCalendar(tz1),
        )
        query.children += (caldavxml.TimeZoneID.fromString(tzid1), )

        result = yield self.calendar_query(
            query, got_xml=None, expected_code=responsecode.BAD_REQUEST)
        self.assertTrue("Only one of" in result)
    def _calendarTimezoneUpgrade_setup(self):

        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        tz1 = Component(None, pycalendar=readVTZ("Etc/GMT+1"))
        tz2 = Component(None, pycalendar=readVTZ("Etc/GMT+2"))
        tz3 = Component(None, pycalendar=readVTZ("Etc/GMT+3"))

        # Share user01 calendar with user03
        calendar = (yield self.calendarUnderTest(name="calendar_1",
                                                 home="user01"))
        home3 = yield self.homeUnderTest(name="user03")
        shared_name = yield calendar.shareWith(home3, _BIND_MODE_WRITE)

        user_details = (
            ("user01", "calendar_1", tz1),
            ("user02", "calendar_1", tz2),
            ("user03", "calendar_1", None),
            ("user03", shared_name, tz3),
        )

        # Set dead properties on calendars
        for user, calname, tz in user_details:
            calendar = (yield self.calendarUnderTest(name=calname, home=user))
            if tz:
                calendar.properties()[PropertyName.fromElement(
                    caldavxml.CalendarTimeZone
                )] = caldavxml.CalendarTimeZone.fromString(str(tz))

            # Force data version to previous
            home = (yield self.homeUnderTest(name=user))
            ch = home._homeSchema
            yield Update(
                {
                    ch.DATAVERSION: 4
                },
                Where=ch.RESOURCE_ID == home._resourceID,
            ).on(self.transactionUnderTest())

        yield self.commit()

        for user, calname, tz in user_details:
            calendar = (yield self.calendarUnderTest(name=calname, home=user))
            self.assertEqual(calendar.getTimezone(), None)
            self.assertEqual(
                PropertyName.fromElement(caldavxml.CalendarTimeZone)
                in calendar.properties(), tz is not None)
        yield self.commit()

        # Create "fake" entry for non-existent share
        txn = self.transactionUnderTest()
        calendar = (yield self.calendarUnderTest(name="calendar_1",
                                                 home="user01"))
        rp = schema.RESOURCE_PROPERTY
        yield Insert({
            rp.RESOURCE_ID:
            calendar._resourceID,
            rp.NAME:
            PropertyName.fromElement(caldavxml.CalendarTimeZone).toString(),
            rp.VALUE:
            caldavxml.CalendarTimeZone.fromString(str(tz3)).toxml(),
            rp.VIEWER_UID:
            "user04",
        }).on(txn)
        yield self.commit()

        returnValue(user_details)
예제 #8
0
 def init_peruser_component():
     peruser = Component(PERUSER_COMPONENT)
     peruser.addProperty(Property("UID", ical.resourceUID()))
     peruser.addProperty(Property(PERUSER_UID, self.uid))
     return peruser
예제 #9
0
    def _splitPerUserData(self, ical):
        """
        Split the per-user data out of the "normal" iCalendar components into separate per-user
        components. Along the way keep the iCalendar representation in a "minimal" state by eliminating
        any components that are the same as the master derived component.

        @param ical: calendar data to process
        @type ical: L{Component}
        """
        def init_peruser_component():
            peruser = Component(PERUSER_COMPONENT)
            peruser.addProperty(Property("UID", ical.resourceUID()))
            peruser.addProperty(Property(PERUSER_UID, self.uid))
            return peruser

        components = tuple(ical.subcomponents())
        peruser_component = init_peruser_component() if self.uid else None
        perinstance_components = {}

        for component in components:
            if component.name() == "VTIMEZONE":
                continue
            rid = component.propertyValue("RECURRENCE-ID")
            rid = rid.duplicate() if rid is not None else None

            perinstance_component = Component(
                PERINSTANCE_COMPONENT) if self.uid else None
            perinstance_id_different = False

            # Special case certain default property values
            self._defaultMerge(component)

            # Transfer per-user properties from main component to per-instance component
            for property in tuple(component.properties()):
                if property.name(
                ) in PerUserDataFilter.PERUSER_PROPERTIES or property.name(
                ).startswith("X-") and property.name(
                ) not in PerUserDataFilter.IGNORE_X_PROPERTIES:
                    if self.uid:
                        perinstance_component.addProperty(property)
                    component.removeProperty(property)
                    perinstance_id_different = True

            # Transfer per-user components from main component to per-instance component
            for subcomponent in tuple(component.subcomponents()):
                if subcomponent.name(
                ) in PerUserDataFilter.PERUSER_SUBCOMPONENTS or subcomponent.name(
                ).startswith("X-"):
                    if self.uid:
                        perinstance_component.addComponent(subcomponent)
                    component.removeComponent(subcomponent)
                    perinstance_id_different = True

            if perinstance_id_different and perinstance_component:
                perinstance_components[rid] = perinstance_component

        if self.uid:
            # Add unique per-instance components into the per-user component
            peruser_component_different = False
            master_perinstance = perinstance_components.get(None)
            if master_perinstance:
                peruser_component.addComponent(master_perinstance)
                peruser_component_different = True
            for rid, perinstance in sorted(perinstance_components.iteritems(),
                                           key=lambda x: x[0]):
                if rid is None:
                    continue
                if master_perinstance is None or perinstance != master_perinstance:
                    perinstance.addProperty(Property("RECURRENCE-ID", rid))
                    peruser_component.addComponent(perinstance)
                    peruser_component_different = True

            if peruser_component_different:
                ical.addComponent(peruser_component)

            self._compactInstances(ical)
예제 #10
0
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!r}",
                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
    if calendar_query.timezone:
        query_tz = calendar_query.timezone
        if 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",
                ))
        filter.settimezone(query_tz)
        query_timezone = tuple(query_tz.calendar().subcomponents())[0]
    elif calendar_query.timezone_id:
        query_tzid = calendar_query.timezone_id.toString()
        try:
            query_tz = Component(None, pycalendar=readVTZ(query_tzid))
        except TimezoneException:
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (caldav_namespace, "valid-timezone"),
                    "Invalid timezone-id",
                ))
        filter.settimezone(query_tz)
        query_timezone = tuple(query_tz.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: {f!r}", f=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: {h}", h=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
            timezone = query_timezone
            if timezone is None:
                has_prop = (yield
                            calresource.hasProperty(CalendarTimeZone(),
                                                    request))
                if 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.componentForUser())
                        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 timezone 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.componentForUser())
            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",
            ))
예제 #11
0
            hour -= 12
        else:
            assert meridian == "PM"
    else:
        if meridian == "PM":
            hour += 12
        else:
            assert meridian == "AM"

    start_time = datetime.time(hour, int(time[3:5]))

    return datetime.datetime.combine(start_date, start_time)

reader = csv.reader(file(csv_filename, "rb"))

calendar = Component("VCALENDAR")

# Ignore first line
reader.next()

priorities = {
    "High"   : "1",
    "Medium" : "5",
    "Low"    : "9",
}

for row in reader:
    event = Component("VEVENT")

    title = row[0]
    event.addProperty(Property("SUMMARY", title))
예제 #12
0
# for each subcomponent therein.
# These split-up calendars are useable as CalDAV resources.
#

import os

from twistedcaldav.ical import Component

monolithic_filename = os.path.join(os.path.dirname(__file__), "Holidays.ics")

calendar = Component.fromStream(file(monolithic_filename))

assert calendar.name() == "VCALENDAR"

for subcomponent in calendar.subcomponents():
    subcalendar = Component("VCALENDAR")

    #
    # Add top-level properties from monolithic calendar to top-level properties
    # of subcomponent calendar.
    #
    for property in calendar.properties():
        subcalendar.addProperty(property)

    subcalendar.addComponent(subcomponent)

    uid = subcalendar.resourceUID()
    subcalendar_filename = os.path.join(os.path.dirname(__file__), "Holidays",
                                        uid + ".ics")

    print "Writing %s" % (subcalendar_filename, )