Ejemplo n.º 1
0
    def test_query_text(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        caldavxml.PropertyFilter(
                            caldavxml.TextMatch.fromString("1234", False),
                            name="UID",
                        ), **{"name": ("VEVENT")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertTrue(
            isinstance(f.child.filters[0].filters[0], PropertyFilter))
        self.assertTrue(
            isinstance(f.child.filters[0].filters[0].qualifier, TextMatch))
        self.assertEqual(f.child.filters[0].filters[0].qualifier.text, "1234")
Ejemplo n.º 2
0
    def eventsInTimeRange(self, calendar, uid, timerange):

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    timerange,
                    name=("VEVENT",),
                ),
                name="VCALENDAR",
            )
        )
        filter = Filter(filter)
        filter.settimezone(None)

        matches = yield calendar.search(filter, useruid=uid, fbtype=False)
        if matches is None:
            returnValue(None)
        for name, _ignore_uid, _ignore_type in matches:
            event = yield calendar.calendarObjectWithName(name)
            ical_data = yield event.componentForUser()
            ical_data.stripStandardTimezones()

            table = tables.Table()
            table.addRow(("Calendar:", calendar.name(),))
            table.addRow(("Resource Name:", name))
            table.addRow(("Resource ID:", event._resourceID))
            table.addRow(("Created", event.created()))
            table.addRow(("Modified", event.modified()))
            print("\n")
            table.printTable()
            print(ical_data.getTextWithoutTimezones())
Ejemplo n.º 3
0
    def test_query_extended(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(**{
                                "start": "20060605T160000Z",
                            })
                        ], **{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertEqual(len(f.child.filters), 2)
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
Ejemplo n.º 4
0
    def eventsInTimeRange(self, calendar, uid, timerange):

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    timerange,
                    name=("VEVENT", ),
                ),
                name="VCALENDAR",
            ))
        filter = Filter(filter)
        filter.settimezone(None)

        matches = yield calendar.search(filter, useruid=uid, fbtype=False)
        if matches is None:
            returnValue(None)
        for name, _ignore_uid, _ignore_type in matches:
            event = yield calendar.calendarObjectWithName(name)
            ical_data = yield event.componentForUser()
            ical_data.stripStandardTimezones()

            table = tables.Table()
            table.addRow((
                "Calendar:",
                calendar.name(),
            ))
            table.addRow(("Resource Name:", name))
            table.addRow(("Resource ID:", event._resourceID))
            table.addRow(("Created", event.created()))
            table.addRow(("Modified", event.modified()))
            print("\n")
            table.printTable()
            print(ical_data.getTextWithoutTimezones())
Ejemplo n.º 5
0
    def test_timerange_query(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[caldavxml.TimeRange(**{"start": "20060605T160000Z", "end": "20060605T170000Z"})],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
                    )
                ],
                **{"name": "VCALENDAR"}
            )
        )
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
        self.assertTrue(isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), "America/New_York")
Ejemplo n.º 6
0
    def test_query_extended(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[caldavxml.TimeRange(**{"start": "20060605T160000Z"})], **{"name": ("VEVENT")}
                    ),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ],
                **{"name": "VCALENDAR", "test": "anyof"}
            )
        )
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertEqual(len(f.child.filters), 2)
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
Ejemplo n.º 7
0
    def test_timerange_query(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
        self.assertTrue(
            isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(),
                         "America/New_York")
Ejemplo n.º 8
0
    def test_query_text(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        caldavxml.PropertyFilter(caldavxml.TextMatch.fromString("1234", False), name="UID"),
                        **{"name": ("VEVENT")}
                    )
                ],
                **{"name": "VCALENDAR", "test": "anyof"}
            )
        )
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertTrue(isinstance(f.child.filters[0].filters[0], PropertyFilter))
        self.assertTrue(isinstance(f.child.filters[0].filters[0].qualifier, TextMatch))
        self.assertEqual(f.child.filters[0].filters[0].qualifier.text, "1234")
Ejemplo n.º 9
0
    def test_vlarm_undefined(self):

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[caldavxml.ComponentFilter(
                    *[caldavxml.ComponentFilter(
                        caldavxml.IsNotDefined(),
                        **{"name":"VALARM"}
                    )],
                    **{"name":"VEVENT"}
                )],
                **{"name": "VCALENDAR"}
            )
        )
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        self.assertFalse(filter.match(
            Component.fromString("""BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Example Inc.//Example Calendar//EN
VERSION:2.0
BEGIN:VTIMEZONE
LAST-MODIFIED:20040110T032845Z
TZID:US/Eastern
BEGIN:DAYLIGHT
DTSTART:20000404T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZNAME:EDT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20001026T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:EST
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20051222T210412Z
CREATED:20060102T150000Z
DTSTART;TZID=US/Eastern:20130102T100000
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=5
SUMMARY:event 5
UID:[email protected]
CATEGORIES:cool,hot
CATEGORIES:warm
BEGIN:VALARM
ACTION:AUDIO
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
END:VCALENDAR
"""
        )))
Ejemplo n.º 10
0
    def test_vlarm_undefined(self):

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.ComponentFilter(caldavxml.IsNotDefined(),
                                                      **{"name": "VALARM"})
                        ], **{"name": "VEVENT"})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        self.assertFalse(
            filter.match(
                Component.fromString("""BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Example Inc.//Example Calendar//EN
VERSION:2.0
BEGIN:VTIMEZONE
LAST-MODIFIED:20040110T032845Z
TZID:US/Eastern
BEGIN:DAYLIGHT
DTSTART:20000404T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZNAME:EDT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20001026T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:EST
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20051222T210412Z
CREATED:20060102T150000Z
DTSTART;TZID=US/Eastern:20130102T100000
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=5
SUMMARY:event 5
UID:[email protected]
CATEGORIES:cool,hot
CATEGORIES:warm
BEGIN:VALARM
ACTION:AUDIO
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
END:VCALENDAR
""")))
Ejemplo n.º 11
0
    def test_search(self):
        """
        Test that action=resourcenameforuid works.
        """

        yield self.createShare("user01", "puser01")

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
        yield self.commitTransaction(0)

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[caldavxml.ComponentFilter(
                    **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")}
                )],
                **{"name": "VCALENDAR"}
            )
        )
        filter = Filter(filter)

        calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar")
        names = [item[0] for item in (yield calendar1.search(filter))]
        self.assertEqual(names, ["1.ics", ])
        yield self.commitTransaction(0)

        shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar")
        names = [item[0] for item in (yield shared.search(filter))]
        self.assertEqual(names, ["1.ics", ])
        yield self.commitTransaction(1)
Ejemplo n.º 12
0
    def test_query_not_extended(self):
        """
        Query test - two terms not anyof
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(**{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE = ? and ICALENDAR_TYPE = ?",
                [1234, "VEVENT", "VTODO"]))
        self.assertEqual(args, {})
        self.assertEqual(usedtimerange, False)
Ejemplo n.º 13
0
    def test_query(self):
        """
        Basic query test - no time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE in (?, ?, ?)",
                [1234, Parameter('arg1', 3)]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, False)
Ejemplo n.º 14
0
    def test_query(self):
        """
        Basic query test - no time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[caldavxml.ComponentFilter(**{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})],
                **{"name": "VCALENDAR"}
            )
        )
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
Ejemplo n.º 15
0
    def test_query(self):
        """
        Basic query test - no time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
Ejemplo n.º 16
0
    def test_sqllite_query(self):
        """
        Basic query test - single term.
        Only UID can be queried via sql.
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        sql, args = sqlcalendarquery(filter, 1234)

        self.assertTrue(sql.find("RESOURCE") != -1)
        self.assertTrue(sql.find("TIMESPAN") == -1)
        self.assertTrue(sql.find("PERUSER") == -1)
        self.assertTrue("VEVENT" in args)
Ejemplo n.º 17
0
    def test_query_freebusy(self):
        """
        Basic query test - with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234, "user01", True)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, coalesce(ADJUSTED_START_DATE, START_DATE), coalesce(ADJUSTED_END_DATE, END_DATE), FBTYPE, TIME_RANGE.TRANSPARENT, PERUSER.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join PERUSER on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ? or FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    'user01',
                    Parameter('arg1', 3), False,
                    datetime.datetime(2006, 6, 5, 17, 0),
                    datetime.datetime(2006, 6, 5, 16, 0), True,
                    datetime.datetime(2006, 6, 5, 13, 0),
                    datetime.datetime(2006, 6, 5, 12, 0), 1234
                ]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, True)
Ejemplo n.º 18
0
    def test_query_timerange(self):
        """
        Basic query test - with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE < ? and END_DATE > ? or FLOATING = ? and START_DATE < ? and END_DATE > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    Parameter('arg1', 3), False,
                    datetime.datetime(2006, 6, 5, 17, 0),
                    datetime.datetime(2006, 6, 5, 16, 0), True,
                    datetime.datetime(2006, 6, 5, 13, 0),
                    datetime.datetime(2006, 6, 5, 12, 0), 1234
                ]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, True)
Ejemplo n.º 19
0
    def test_query_extended(self):
        """
        Extended query test - two terms with anyof
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(**{
                                "start": "20060605T160000Z",
                            })
                        ], **{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE > ? or FLOATING = ? and END_DATE > ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    'VEVENT', False,
                    datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True,
                    datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()),
                    'VTODO', 1234
                ]))
        self.assertEqual(args, {})
        self.assertEqual(usedtimerange, True)
Ejemplo n.º 20
0
    def test_index_timespan_per_user(self):
        data = (
            (
                "#1.1 Single per-user non-recurring component",
                "1.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.1
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080602T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                          'B', 'T'), ),
                    ),
                    (
                        "user02",
                        (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                          'B', 'F'), ),
                    ),
                ),
            ),
            (
                "#1.2 Two per-user non-recurring component",
                "1.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user02
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080602T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                          'B', 'T'), ),
                    ),
                    (
                        "user02",
                        (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                          'B', 'F'), ),
                    ),
                    (
                        "user03",
                        (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                          'B', 'F'), ),
                    ),
                ),
            ),
            (
                "#2.1 Single per-user simple recurring component",
                "2.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.1
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080603T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'T'),
                            ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00",
                             'B', 'T'),
                        ),
                    ),
                    (
                        "user02",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00",
                             'B', 'F'),
                        ),
                    ),
                ),
            ),
            (
                "#2.2 Two per-user simple recurring component",
                "2.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user02
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080603T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'T'),
                            ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00",
                             'B', 'T'),
                        ),
                    ),
                    (
                        "user02",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00",
                             'B', 'F'),
                        ),
                    ),
                    (
                        "user03",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00",
                             'B', 'F'),
                        ),
                    ),
                ),
            ),
            (
                "#3.1 Single per-user complex recurring component",
                "3.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-1.1
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.1
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
BEGIN:X-CALENDARSERVER-PERINSTANCE
RECURRENCE-ID:20080602T120000Z
TRANSP:OPAQUE
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080604T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'T'),
                            ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00",
                             'B', 'F'),
                            ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00",
                             'B', 'T'),
                        ),
                    ),
                    (
                        "user02",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00",
                             'B', 'F'),
                            ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00",
                             'B', 'F'),
                        ),
                    ),
                ),
            ),
            (
                "#3.2 Two per-user complex recurring component",
                "3.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-1.2
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user01
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
BEGIN:X-CALENDARSERVER-PERINSTANCE
RECURRENCE-ID:20080602T120000Z
TRANSP:OPAQUE
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890-1.2
X-CALENDARSERVER-PERUSER-UID:user02
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:X-CALENDARSERVER-PERINSTANCE
BEGIN:X-CALENDARSERVER-PERINSTANCE
RECURRENCE-ID:20080603T120000Z
TRANSP:TRANSPARENT
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080604T000000Z",
                "mailto:[email protected]",
                (
                    (
                        "user01",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'T'),
                            ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00",
                             'B', 'F'),
                            ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00",
                             'B', 'T'),
                        ),
                    ),
                    (
                        "user02",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00",
                             'B', 'F'),
                            ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00",
                             'B', 'T'),
                        ),
                    ),
                    (
                        "user03",
                        (
                            ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00",
                             'B', 'F'),
                            ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00",
                             'B', 'F'),
                            ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00",
                             'B', 'F'),
                        ),
                    ),
                ),
            ),
        )

        for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
            calendar = Component.fromString(calendar_txt)

            with open(os.path.join(self.indexDirPath.path, name), "w") as f:
                f.write(calendar_txt)

            self.db.addResource(name, calendar)
            self.assertTrue(self.db.resourceExists(name), msg=description)

            # Create fake filter element to match time-range
            filter = caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        TimeRange(
                            start=trstart,
                            end=trend,
                        ),
                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                    ),
                    name="VCALENDAR",
                ))
            filter = Filter(filter)

            for useruid, instances in peruserinstances:
                resources = yield self.db.indexedSearch(filter,
                                                        useruid=useruid,
                                                        fbtype=True)
                index_results = set()
                for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
                    self.assertEqual(test_organizer,
                                     organizer,
                                     msg=description)
                    index_results.add((
                        str(float),
                        str(start),
                        str(end),
                        str(fbtype),
                        str(transp),
                    ))

                self.assertEqual(set(instances),
                                 index_results,
                                 msg="%s, user:%s" % (
                                     description,
                                     useruid,
                                 ))

            self.db.deleteResource(name)
Ejemplo n.º 21
0
    def _cancelEvents(self, txn, uid, cuas):

        # Anything in the past is left alone
        whenString = self.when.getText()
        query_filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    caldavxml.TimeRange(start=whenString,),
                    name=("VEVENT",),
                ),
                name="VCALENDAR",
            )
        )
        query_filter = Filter(query_filter)

        count = 0
        txn = self.store.newTransaction()
        storeCalHome = yield txn.calendarHomeWithUID(uid)
        calendarNames = yield storeCalHome.listCalendars()
        yield txn.commit()

        for calendarName in calendarNames:

            txn = self.store.newTransaction(authz_uid=uid)
            storeCalHome = yield txn.calendarHomeWithUID(uid)
            calendar = yield storeCalHome.calendarWithName(calendarName)
            allChildNames = []
            futureChildNames = set()

            # Only purge owned calendars
            if calendar.owned():
                # all events
                for childName in (yield calendar.listCalendarObjects()):
                    allChildNames.append(childName)

                # events matching filter
                for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)):
                    futureChildNames.add(childName)

            yield txn.commit()

            for childName in allChildNames:

                txn = self.store.newTransaction(authz_uid=uid)
                storeCalHome = yield txn.calendarHomeWithUID(uid)
                calendar = yield storeCalHome.calendarWithName(calendarName)
                doScheduling = childName in futureChildNames

                try:
                    childResource = yield calendar.calendarObjectWithName(childName)

                    uri = "/calendars/__uids__/%s/%s/%s" % (storeCalHome.uid(), calendar.name(), childName)
                    incrementCount = self.dryrun
                    if self.verbose:
                        if self.dryrun:
                            print("Would delete%s: %s" % (" with scheduling" if doScheduling else "", uri,))
                        else:
                            print("Deleting%s: %s" % (" with scheduling" if doScheduling else "", uri,))
                    if not self.dryrun:
                        retry = False
                        try:
                            yield childResource.purge(implicitly=doScheduling)
                            incrementCount = True
                        except Exception, e:
                            print("Exception deleting %s: %s" % (uri, str(e)))
                            retry = True

                        if retry and doScheduling:
                            # Try again with implicit scheduling off
                            print("Retrying deletion of %s with scheduling turned off" % (uri,))
                            try:
                                yield childResource.purge(implicitly=False)
                                incrementCount = True
                            except Exception, e:
                                print("Still couldn't delete %s even with scheduling turned off: %s" % (uri, str(e)))

                    if incrementCount:
                        count += 1

                    # Commit
                    yield txn.commit()

                except Exception, e:
                    # Abort
                    yield txn.abort()
                    raise e
Ejemplo n.º 22
0
def generateFreeBusyInfo(
    request,
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param request:     the L{IRequest} for the current request.
    @param calresource: the L{CalDAVResource} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    if not servertoserver:
        try:
            yield calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), principal=organizerPrincipal)
        except AccessDeniedError:
            returnValue(matchtotal)

    # May need organizer principal
    organizer_principal = (yield calresource.principalForCalendarUserAddress(organizer)) if organizer else None
    organizer_uid = organizer_principal.principalUID() if organizer_principal else ""

    # Free busy is per-user
    userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
    if userPrincipal:
        useruid = userPrincipal.principalUID()
    else:
        useruid = ""

    # Get the timezone property from the collection.
    has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request))
    if has_prop:
        tz = (yield calresource.readProperty(CalendarTimeZone(), request))
    else:
        tz = None

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_principal is not None and userPrincipal is not None:

        # Check if organizer is attendee
        if organizer_principal == userPrincipal:
            do_event_details = True
            rich_options["organizer"] = True

        # Check if organizer is a delegate of attendee
        proxy = (yield organizer_principal.isProxyFor(userPrincipal))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and userPrincipal.getCUType() in ("RESOURCE", "ROOM",):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(calresource, useruid, timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if not hasattr(request, "extendedLogItems"):
                request.extendedLogItems = {}
            request.extendedLogItems["fb-uncached"] = request.extendedLogItems.get("fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))

            # If the requested timerange would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime(timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
                      caldavxml.ComponentFilter(
                          caldavxml.ComponentFilter(
                              cache_timerange if caching else timerange,
                              name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                          ),
                          name="VCALENDAR",
                       )
                  )
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)

        try:
            resources = yield calresource.search(filter, useruid=useruid, fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, useruid, cache_timerange, resources)
        except IndexedSearchException:
            raise HTTPError(StatusResponse(
                responsecode.INTERNAL_SERVER_ERROR,
                "Failed freebusy query"
            ))

    else:
        # Log extended item
        if not hasattr(request, "extendedLogItems"):
            request.extendedLogItems = {}
        request.extendedLogItems["fb-cached"] = request.extendedLogItems.get("fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))

    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        test_principal = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
                        test_uid = test_principal.principalUID() if test_principal else ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart), Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                # Add extended details
                if do_event_details:
                    child = (yield request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo)

        else:
            child = (yield request.locateChildResource(calresource, name))
            calendar = (yield child.iCalendarForUser(request))

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error("Calendar %s is missing from calendar collection %r" % (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    test_principal = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
                    test_uid = test_principal.principalUID() if test_principal else ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if filter.match(calendar, None):
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (name, calresource,)

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo)

    returnValue(matchtotal)
Ejemplo n.º 23
0
def generateFreeBusyInfo(
    request,
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param request:     the L{IRequest} for the current request.
    @param calresource: the L{CalDAVResource} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    if not servertoserver:
        try:
            yield calresource.checkPrivileges(request,
                                              (caldavxml.ReadFreeBusy(), ),
                                              principal=organizerPrincipal)
        except AccessDeniedError:
            returnValue(matchtotal)

    # May need organizer principal
    organizer_principal = (yield calresource.principalForCalendarUserAddress(
        organizer)) if organizer else None
    organizer_uid = organizer_principal.principalUID(
    ) if organizer_principal else ""

    # Free busy is per-user
    userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
    if userPrincipal:
        useruid = userPrincipal.principalUID()
    else:
        useruid = ""

    # Get the timezone property from the collection.
    has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request))
    if has_prop:
        tz = (yield calresource.readProperty(CalendarTimeZone(), request))
    else:
        tz = None

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_principal is not None and userPrincipal is not None:

        # Check if organizer is attendee
        if organizer_principal == userPrincipal:
            do_event_details = True
            rich_options["organizer"] = True

        # Check if organizer is a delegate of attendee
        proxy = (yield organizer_principal.isProxyFor(userPrincipal))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and userPrincipal.getCUType(
        ) in (
                "RESOURCE",
                "ROOM",
        ):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(
        calresource, useruid,
        timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if not hasattr(request, "extendedLogItems"):
                request.extendedLogItems = {}
            request.extendedLogItems[
                "fb-uncached"] = request.extendedLogItems.get(
                    "fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(
                days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(
                days=config.FreeBusyCacheDaysForward))

            # If the requested timerange would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start,
                               cache_start) >= 0 and compareDateTime(
                                   timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(),
                                            end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    cache_timerange if caching else timerange,
                    name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                ),
                name="VCALENDAR",
            ))
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)

        try:
            resources = yield calresource.search(filter,
                                                 useruid=useruid,
                                                 fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, useruid,
                                                  cache_timerange, resources)
        except IndexedSearchException:
            raise HTTPError(
                StatusResponse(responsecode.INTERNAL_SERVER_ERROR,
                               "Failed freebusy query"))

    else:
        # Log extended item
        if not hasattr(request, "extendedLogItems"):
            request.extendedLogItems = {}
        request.extendedLogItems["fb-cached"] = request.extendedLogItems.get(
            "fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((
            name,
            uid,
            type,
            test_organizer,
        ), []).append((
            float,
            start,
            end,
            fbtype,
        ))

    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        test_principal = (
                            yield calresource.principalForCalendarUserAddress(
                                test_organizer)) if test_organizer else None
                        test_uid = test_principal.principalUID(
                        ) if test_principal else ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart),
                                     Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                # Add extended details
                if do_event_details:
                    child = (yield
                             request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options,
                                     timerange, tzinfo)

        else:
            child = (yield request.locateChildResource(calresource, name))
            calendar = (yield child.iCalendarForUser(request))

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error(
                    "Calendar %s is missing from calendar collection %r" %
                    (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    test_principal = (
                        yield calresource.principalForCalendarUserAddress(
                            test_organizer)) if test_organizer else None
                    test_uid = test_principal.principalUID(
                    ) if test_principal else ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if filter.match(calendar, None):
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (
                        name,
                        calresource,
                    )

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield
                             request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options,
                                     timerange, tzinfo)

    returnValue(matchtotal)
Ejemplo n.º 24
0
def _internalGenerateFreeBusyInfo(
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
    logItems=None,
    accountingItems=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param calresource: the L{Calendar} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    @param logItems: a C{dict} to store logging info to
    @param accountingItems: a C{dict} to store accounting info to
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy

    # May need organizer principal
    organizer_record = (yield calresource.directoryService(
    ).recordWithCalendarUserAddress(organizer)) if organizer else None
    organizer_uid = organizer_record.uid if organizer_record else ""

    # Free busy is per-user
    attendee_uid = calresource.viewerHome().uid()
    attendee_record = yield calresource.directoryService().recordWithUID(
        attendee_uid.decode("utf-8"))

    # Get the timezone property from the collection.
    tz = calresource.getTimezone()

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_record is not None and attendee_record is not None:

        # Get the principal of the authorized user which may be different from the organizer if a delegate of
        # the organizer is making the request
        authz_uid = organizer_uid
        authz_record = organizer_record
        if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid:
            authz_uid = calresource._txn._authz_uid
            authz_record = yield calresource.directoryService().recordWithUID(
                authz_uid.decode("utf-8"))

        # Check if attendee is also the organizer or the delegate doing the request
        if attendee_uid in (organizer_uid, authz_uid):
            do_event_details = True
            rich_options["organizer"] = True

        # Check if authorized user is a delegate of attendee
        proxy = (yield authz_record.isProxyFor(attendee_record))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and attendee_record.getCUType(
        ) in (
                "RESOURCE",
                "ROOM",
        ):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(
        calresource, attendee_uid,
        timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        if accountingItems is not None:
            accountingItems["fb-uncached"] = accountingItems.get(
                "fb-uncached", 0) + 1

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if logItems is not None:
                logItems["fb-uncached"] = logItems.get("fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(
                days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(
                days=config.FreeBusyCacheDaysForward))

            # If the requested time range would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start,
                               cache_start) >= 0 and compareDateTime(
                                   timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(),
                                            end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    cache_timerange if caching else timerange,
                    name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                ),
                name="VCALENDAR",
            ))
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)
        if accountingItems is not None:
            tr = cache_timerange if caching else timerange
            accountingItems["fb-query-timerange"] = (
                str(tr.start),
                str(tr.end),
            )

        try:
            resources = yield calresource.search(filter,
                                                 useruid=attendee_uid,
                                                 fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid,
                                                  cache_timerange, resources)
        except IndexedSearchException:
            raise InternalDataStoreError("Invalid indexedSearch query")

    else:
        if accountingItems is not None:
            accountingItems["fb-cached"] = accountingItems.get("fb-cached",
                                                               0) + 1

        # Log extended item
        if logItems is not None:
            logItems["fb-cached"] = logItems.get("fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((
            name,
            uid,
            type,
            test_organizer,
        ), []).append((
            float,
            start,
            end,
            fbtype,
        ))

    if accountingItems is not None:
        accountingItems["fb-resources"] = {}
        for k, v in aggregated_resources.items():
            name, uid, type, test_organizer = k
            accountingItems["fb-resources"][uid] = []
            for float, start, end, fbtype in v:
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))
                accountingItems["fb-resources"][uid].append((
                    float,
                    str(fbstart),
                    str(fbend),
                    fbtype,
                ))

    # Cache directory record lookup outside this loop as it is expensive and will likely
    # always end up being called with the same organizer address.
    recordUIDCache = {}
    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        if test_organizer:
                            test_uid = recordUIDCache.get(test_organizer)
                            if test_uid is None:
                                test_record = (yield
                                               calresource.directoryService(
                                               ).recordWithCalendarUserAddress(
                                                   test_organizer))
                                test_uid = test_record.uid if test_record else ""
                                recordUIDCache[test_organizer] = test_uid
                        else:
                            test_uid = ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart),
                                     Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults,
                                            matchtotal)

                # Add extended details
                if do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options,
                                         timerange, tzinfo)

        else:
            child = (yield calresource.calendarObjectWithName(name))
            calendar = (yield child.componentForUser())

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error(
                    "Calendar %s is missing from calendar collection %r" %
                    (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    if test_organizer:
                        test_uid = recordUIDCache.get(test_organizer)
                        if test_uid is None:
                            test_record = (yield calresource.directoryService(
                            ).recordWithCalendarUserAddress(test_organizer))
                            test_uid = test_record.uid if test_record else ""
                            recordUIDCache[test_organizer] = test_uid
                    else:
                        test_uid = ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if accountingItems is not None:
                accountingItems.setdefault("fb-filter-match", []).append(uid)

            if filter.match(calendar, None):
                if accountingItems is not None:
                    accountingItems.setdefault("fb-filter-matched",
                                               []).append(uid)

                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults,
                                            matchtotal)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (
                        name,
                        calresource,
                    )

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options,
                                         timerange, tzinfo)

    returnValue(matchtotal)
Ejemplo n.º 25
0
    def test_index_timespan(self):
        data = (
            (
                "#1.1 Simple component - busy",
                "1.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080602T000000Z",
                "mailto:[email protected]",
                (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B',
                  'F'), ),
            ),
            (
                "#1.2 Simple component - transparent",
                "1.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.2
DTSTART:20080602T120000Z
DTEND:20080602T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
""",
                "20080602T000000Z",
                "20080603T000000Z",
                "mailto:[email protected]",
                (('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B',
                  'T'), ),
            ),
            (
                "#1.3 Simple component - canceled",
                "1.3",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.3
DTSTART:20080603T120000Z
DTEND:20080603T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
STATUS:CANCELLED
END:VEVENT
END:VCALENDAR
""",
                "20080603T000000Z",
                "20080604T000000Z",
                "mailto:[email protected]",
                (('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'F',
                  'F'), ),
            ),
            (
                "#1.4 Simple component - tentative",
                "1.4",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.4
DTSTART:20080604T120000Z
DTEND:20080604T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
STATUS:TENTATIVE
END:VEVENT
END:VCALENDAR
""",
                "20080604T000000Z",
                "20080605T000000Z",
                "mailto:[email protected]",
                (('N', "2008-06-04 12:00:00", "2008-06-04 13:00:00", 'T',
                  'F'), ),
            ),
            (
                "#2.1 Recurring component - busy",
                "2.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-2.1
DTSTART:20080605T120000Z
DTEND:20080605T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
END:VCALENDAR
""",
                "20080605T000000Z",
                "20080607T000000Z",
                "mailto:[email protected]",
                (
                    ('N', "2008-06-05 12:00:00", "2008-06-05 13:00:00", 'B',
                     'F'),
                    ('N', "2008-06-06 12:00:00", "2008-06-06 13:00:00", 'B',
                     'F'),
                ),
            ),
            (
                "#2.2 Recurring component - busy",
                "2.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-2.2
DTSTART:20080607T120000Z
DTEND:20080607T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-2.2
RECURRENCE-ID:20080608T120000Z
DTSTART:20080608T140000Z
DTEND:20080608T150000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
""",
                "20080607T000000Z",
                "20080609T000000Z",
                "mailto:[email protected]",
                (
                    ('N', "2008-06-07 12:00:00", "2008-06-07 13:00:00", 'B',
                     'F'),
                    ('N', "2008-06-08 14:00:00", "2008-06-08 15:00:00", 'B',
                     'T'),
                ),
            ),
        )

        for description, name, calendar_txt, trstart, trend, organizer, instances in data:
            calendar = Component.fromString(calendar_txt)

            with open(os.path.join(self.indexDirPath.path, name), "w") as f:
                f.write(calendar_txt)

            self.db.addResource(name, calendar)
            self.assertTrue(self.db.resourceExists(name), msg=description)

            # Create fake filter element to match time-range
            filter = caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        TimeRange(
                            start=trstart,
                            end=trend,
                        ),
                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                    ),
                    name="VCALENDAR",
                ))
            filter = Filter(filter)

            resources = yield self.db.indexedSearch(filter, fbtype=True)
            index_results = set()
            for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
                self.assertEqual(test_organizer, organizer, msg=description)
                index_results.add((
                    float,
                    start,
                    end,
                    fbtype,
                    transp,
                ))

            self.assertEqual(set(instances), index_results, msg=description)
Ejemplo n.º 26
0
    def _matchCalendarResources(self, calresource):

        # Get the timezone property from the collection.
        tz = calresource.getTimezone()

        # Try cache
        aggregated_resources = (yield FBCacheEntry.getCacheEntry(calresource, self.attendee_uid, self.timerange)) if config.EnableFreeBusyCache else None

        if aggregated_resources is None:

            if self.accountingItems is not None:
                self.accountingItems["fb-uncached"] = self.accountingItems.get("fb-uncached", 0) + 1

            caching = False
            if config.EnableFreeBusyCache:
                # Log extended item
                if self.logItems is not None:
                    self.logItems["fb-uncached"] = self.logItems.get("fb-uncached", 0) + 1

                # We want to cache a large range of time based on the current date
                cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
                cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))

                # If the requested time range would fit in our allowed cache range, trigger the cache creation
                if compareDateTime(self.timerange.getStart(), cache_start) >= 0 and compareDateTime(self.timerange.getEnd(), cache_end) <= 0:
                    cache_timerange = Period(cache_start, cache_end)
                    caching = True

            #
            # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
            # We then take those results and merge them into one VFREEBUSY component
            # with appropriate FREEBUSY properties, and return that single item as iCal data.
            #

            # Create fake filter element to match time-range
            tr = TimeRange(
                start=(cache_timerange if caching else self.timerange).getStart().getText(),
                end=(cache_timerange if caching else self.timerange).getEnd().getText(),
            )
            filter = caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        tr,
                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                    ),
                    name="VCALENDAR",
                )
            )
            filter = Filter(filter)
            tzinfo = filter.settimezone(tz)
            if self.accountingItems is not None:
                self.accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),)

            try:
                resources = yield calresource.search(filter, useruid=self.attendee_uid, fbtype=True)

                aggregated_resources = {}
                for name, uid, comptype, test_organizer, float, start, end, fbtype, transp in resources:
                    if transp == 'T' and fbtype != '?':
                        fbtype = 'F'
                    aggregated_resources.setdefault((name, uid, comptype, test_organizer,), []).append((
                        float,
                        tupleFromDateTime(parseSQLTimestampToPyCalendar(start)),
                        tupleFromDateTime(parseSQLTimestampToPyCalendar(end)),
                        fbtype,
                    ))

                if caching:
                    yield FBCacheEntry.makeCacheEntry(calresource, self.attendee_uid, cache_timerange, aggregated_resources)
            except IndexedSearchException:
                raise InternalDataStoreError("Invalid indexedSearch query")

        else:
            if self.accountingItems is not None:
                self.accountingItems["fb-cached"] = self.accountingItems.get("fb-cached", 0) + 1

            # Log extended item
            if self.logItems is not None:
                self.logItems["fb-cached"] = self.logItems.get("fb-cached", 0) + 1

            # Determine appropriate timezone (UTC is the default)
            tzinfo = tz.gettimezone() if tz is not None else Timezone.UTCTimezone
            filter = None

        returnValue((aggregated_resources, tzinfo, filter,))
Ejemplo n.º 27
0
def _internalGenerateFreeBusyInfo(
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
    logItems=None,
    accountingItems=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param calresource: the L{Calendar} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    @param logItems: a C{dict} to store logging info to
    @param accountingItems: a C{dict} to store accounting info to
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy

    # May need organizer principal
    organizer_record = (yield calresource.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
    organizer_uid = organizer_record.uid if organizer_record else ""

    # Free busy is per-user
    attendee_uid = calresource.viewerHome().uid()
    attendee_record = yield calresource.directoryService().recordWithUID(attendee_uid.decode("utf-8"))

    # Get the timezone property from the collection.
    tz = calresource.getTimezone()

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_record is not None and attendee_record is not None:

        # Get the principal of the authorized user which may be different from the organizer if a delegate of
        # the organizer is making the request
        authz_uid = organizer_uid
        authz_record = organizer_record
        if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid:
            authz_uid = calresource._txn._authz_uid
            authz_record = yield calresource.directoryService().recordWithUID(authz_uid.decode("utf-8"))

        # Check if attendee is also the organizer or the delegate doing the request
        if attendee_uid in (organizer_uid, authz_uid):
            do_event_details = True
            rich_options["organizer"] = True

        # Check if authorized user is a delegate of attendee
        proxy = (yield authz_record.isProxyFor(attendee_record))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and attendee_record.getCUType() in ("RESOURCE", "ROOM",):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(calresource, attendee_uid, timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        if accountingItems is not None:
            accountingItems["fb-uncached"] = accountingItems.get("fb-uncached", 0) + 1

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if logItems is not None:
                logItems["fb-uncached"] = logItems.get("fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))

            # If the requested time range would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start, cache_start) >= 0 and compareDateTime(timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(), end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    cache_timerange if caching else timerange,
                    name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                ),
                name="VCALENDAR",
            )
        )
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)
        if accountingItems is not None:
            tr = cache_timerange if caching else timerange
            accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),)

        try:
            resources = yield calresource.search(filter, useruid=attendee_uid, fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid, cache_timerange, resources)
        except IndexedSearchException:
            raise InternalDataStoreError("Invalid indexedSearch query")

    else:
        if accountingItems is not None:
            accountingItems["fb-cached"] = accountingItems.get("fb-cached", 0) + 1

        # Log extended item
        if logItems is not None:
            logItems["fb-cached"] = logItems.get("fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))

    if accountingItems is not None:
        accountingItems["fb-resources"] = {}
        for k, v in aggregated_resources.items():
            name, uid, type, test_organizer = k
            accountingItems["fb-resources"][uid] = []
            for float, start, end, fbtype in v:
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))
                accountingItems["fb-resources"][uid].append((
                    float,
                    str(fbstart),
                    str(fbend),
                    fbtype,
                ))

    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer)) if test_organizer else None
                        test_uid = test_record.uid if test_record else ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart), Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults, matchtotal)

                # Add extended details
                if do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo)

        else:
            child = (yield calresource.calendarObjectWithName(name))
            calendar = (yield child.componentForUser())

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error("Calendar %s is missing from calendar collection %r" % (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    test_record = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
                    test_uid = test_record.principalUID() if test_record else ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if accountingItems is not None:
                accountingItems.setdefault("fb-filter-match", []).append(uid)

            if filter.match(calendar, None):
                if accountingItems is not None:
                    accountingItems.setdefault("fb-filter-matched", []).append(uid)

                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults, matchtotal)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (name, calresource,)

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options, timerange, tzinfo)

    returnValue(matchtotal)
Ejemplo n.º 28
0
    def test_index_timerange(self):
        """
        A plain (not freebusy) time range test.
        """
        data = (
            (
                "#1.1 Simple component - busy",
                "1.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""",
                "20080601T000000Z",
                "20080602T000000Z",
            ),
            (
                "#1.2 Simple component - transparent",
                "1.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.2
DTSTART:20080602T120000Z
DTEND:20080602T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
""",
                "20080602T000000Z",
                "20080603T000000Z",
            ),
            (
                "#1.3 Simple component - canceled",
                "1.3",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.3
DTSTART:20080603T120000Z
DTEND:20080603T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
STATUS:CANCELLED
END:VEVENT
END:VCALENDAR
""",
                "20080603T000000Z",
                "20080604T000000Z",
            ),
            (
                "#1.4 Simple component - tentative",
                "1.4",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-1.4
DTSTART:20080604T120000Z
DTEND:20080604T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
STATUS:TENTATIVE
END:VEVENT
END:VCALENDAR
""",
                "20080604T000000Z",
                "20080605T000000Z",
            ),
            (
                "#2.1 Recurring component - busy",
                "2.1",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-2.1
DTSTART:20080605T120000Z
DTEND:20080605T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
END:VCALENDAR
""",
                "20080605T000000Z",
                "20080607T000000Z",
            ),
            (
                "#2.2 Recurring component - busy",
                "2.2",
                """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890-2.2
DTSTART:20080607T120000Z
DTEND:20080607T130000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-2.2
RECURRENCE-ID:20080608T120000Z
DTSTART:20080608T140000Z
DTEND:20080608T150000Z
DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
""",
                "20080607T000000Z",
                "20080609T000000Z",
            ),
        )

        for description, name, calendar_txt, trstart, trend in data:
            calendar = Component.fromString(calendar_txt)

            f = open(os.path.join(self.indexDirPath.path, name), "w")
            f.write(calendar_txt)
            del f

            self.db.addResource(name, calendar)
            self.assertTrue(self.db.resourceExists(name), msg=description)

            # Create fake filter element to match time-range
            filter = caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        TimeRange(
                            start=trstart,
                            end=trend,
                        ),
                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                    ),
                    name="VCALENDAR",
                ))
            filter = Filter(filter)

            resources = yield self.db.indexedSearch(filter)
            index_results = set()
            for found_name, _ignore_uid, _ignore_type in resources:
                index_results.add(found_name)

            self.assertEqual(set((name, )), index_results, msg=description)
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",
        ))
Ejemplo n.º 30
0
    def _matchCalendarResources(self, calresource):

        # Get the timezone property from the collection.
        tz = calresource.getTimezone()

        # Try cache
        aggregated_resources = (yield FBCacheEntry.getCacheEntry(calresource, self.attendee_uid, self.timerange)) if config.EnableFreeBusyCache else None

        if aggregated_resources is None:

            if self.accountingItems is not None:
                self.accountingItems["fb-uncached"] = self.accountingItems.get("fb-uncached", 0) + 1

            caching = False
            if config.EnableFreeBusyCache:
                # Log extended item
                if self.logItems is not None:
                    self.logItems["fb-uncached"] = self.logItems.get("fb-uncached", 0) + 1

                # We want to cache a large range of time based on the current date
                cache_start = normalizeToUTC(DateTime.getToday() + Duration(days=0 - config.FreeBusyCacheDaysBack))
                cache_end = normalizeToUTC(DateTime.getToday() + Duration(days=config.FreeBusyCacheDaysForward))

                # If the requested time range would fit in our allowed cache range, trigger the cache creation
                if compareDateTime(self.timerange.getStart(), cache_start) >= 0 and compareDateTime(self.timerange.getEnd(), cache_end) <= 0:
                    cache_timerange = Period(cache_start, cache_end)
                    caching = True

            #
            # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
            # We then take those results and merge them into one VFREEBUSY component
            # with appropriate FREEBUSY properties, and return that single item as iCal data.
            #

            # Create fake filter element to match time-range
            tr = TimeRange(
                start=(cache_timerange if caching else self.timerange).getStart().getText(),
                end=(cache_timerange if caching else self.timerange).getEnd().getText(),
            )
            filter = caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        tr,
                        name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                    ),
                    name="VCALENDAR",
                )
            )
            filter = Filter(filter)
            tzinfo = filter.settimezone(tz)
            if self.accountingItems is not None:
                self.accountingItems["fb-query-timerange"] = (str(tr.start), str(tr.end),)

            try:
                resources = yield calresource.search(filter, useruid=self.attendee_uid, fbtype=True)

                aggregated_resources = {}
                for name, uid, comptype, test_organizer, float, start, end, fbtype, transp in resources:
                    if transp == 'T' and fbtype != '?':
                        fbtype = 'F'
                    aggregated_resources.setdefault((name, uid, comptype, test_organizer,), []).append((
                        float,
                        tupleFromDateTime(parseSQLTimestampToPyCalendar(start)),
                        tupleFromDateTime(parseSQLTimestampToPyCalendar(end)),
                        fbtype,
                    ))

                if caching:
                    yield FBCacheEntry.makeCacheEntry(calresource, self.attendee_uid, cache_timerange, aggregated_resources)
            except IndexedSearchException:
                raise InternalDataStoreError("Invalid indexedSearch query")

        else:
            if self.accountingItems is not None:
                self.accountingItems["fb-cached"] = self.accountingItems.get("fb-cached", 0) + 1

            # Log extended item
            if self.logItems is not None:
                self.logItems["fb-cached"] = self.logItems.get("fb-cached", 0) + 1

            # Determine appropriate timezone (UTC is the default)
            tzinfo = tz.gettimezone() if tz is not None else Timezone.UTCTimezone
            filter = None

        returnValue((aggregated_resources, tzinfo, filter,))