Beispiel #1
0
    def updateVPOLLData(from_component, to_component, attendee):
        """
        Update VPOLL sub-components with voter's response.

        @param from_component: component to copy from
        @type from_component: L{Component}
        @param to_component: component to copy to
        @type to_component: L{Component}
        @param attendee: attendee being processed
        @type attendee: L{Property}
        """

        responses = {}
        for prop in from_component.properties("POLL-ITEM-ID"):
            responses[prop.value()] = prop

        for component in to_component.subcomponents():
            if component.name() in ignoredComponents:
                continue
            poll_item_id = component.propertyValue("POLL-ITEM-ID")
            if poll_item_id is None:
                continue
            voter = component.getVoterProperty((attendee.value(),))

            # If no response - remove
            if poll_item_id not in responses or not responses[poll_item_id].hasParameter("RESPONSE"):
                if voter is not None:
                    component.removeProperty(voter)
                continue

            # Add or update voter
            if voter is None:
                voter = Property("VOTER", attendee.value())
                component.addProperty(voter)
            voter.setParameter("RESPONSE", responses[poll_item_id].parameterValue("RESPONSE"))
Beispiel #2
0
    def _addTask(self):
        # Don't perform any operations until the client is up and running
        if not self._client.started:
            return succeed(None)

        calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")

        while calendars:
            calendar = self.random.choice(calendars)
            calendars.remove(calendar)

            # Copy the template task and fill in some of its fields
            # to make a new task to create on the calendar.
            vcalendar = self._taskTemplate.duplicate()
            vtodo = vcalendar.mainComponent()
            uid = str(uuid4())
            due = self._taskStartDistribution.sample()
            vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
            vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
            vtodo.replaceProperty(Property("DUE", due))
            vtodo.replaceProperty(Property("UID", uid))

            href = '%s%s.ics' % (calendar.url, uid)
            d = self._client.addEvent(href, vcalendar)
            return self._newOperation("create", d)
Beispiel #3
0
def exportToDirectory(collections, dirname, convertToMailto=False):
    """
    Export some calendars to a file as their owner would see them.

    @param calendars: an iterable of L{ICalendar} providers (or L{Deferred}s of
        same).

    @param dirname: the path to a directory to store calendar files in; each
        calendar being exported will have its own .ics file

    @return: a L{Deferred} which fires when the export is complete.  (Note that
        the file will not be closed.)
    @rtype: L{Deferred} that fires with C{None}
    """

    for collection in collections:

        if isinstance(collection, Calendar):
            homeUID = collection.ownerCalendarHome().uid()

            calendarProperties = collection.properties()
            comp = Component.newCalendar()
            for element, propertyName in (
                (davxml.DisplayName, "NAME"),
                (customxml.CalendarColor, "COLOR"),
            ):

                value = calendarProperties.get(PropertyName.fromElement(element), None)
                if value:
                    comp.addProperty(Property(propertyName, str(value)))

            source = "/calendars/__uids__/{}/{}/".format(homeUID, collection.name())
            comp.addProperty(Property("SOURCE", source))

            for obj in (yield collection.calendarObjects()):
                evt = yield obj.filteredComponent(homeUID, True)
                for sub in evt.subcomponents():
                    if sub.name() != 'VTIMEZONE':
                        # Omit all VTIMEZONE components here - we will include them later
                        # when we serialize the whole calendar.
                        if convertToMailto:
                            convertCUAsToMailto(sub)
                        comp.addComponent(sub)

            filename = os.path.join(dirname, "{}_{}.ics".format(homeUID, collection.name()))
            with open(filename, 'wb') as fileobj:
                fileobj.write(comp.getTextWithTimezones(True))

        else: # addressbook

            homeUID = collection.ownerAddressBookHome().uid()
            filename = os.path.join(dirname, "{}_{}.vcf".format(homeUID, collection.name()))
            with open(filename, 'wb') as fileobj:
                for obj in (yield collection.addressbookObjects()):
                    vcard = yield obj.component()
                    fileobj.write(vcard.getText())
Beispiel #4
0
    def _attendeeDecline(self, component):
        """
        Mark attendee as DECLINED in the component.

        @param component:
        @type component:

        @return: C{bool} indicating whether the PARTSTAT value was in fact changed
        """
        attendee = component.getAttendeeProperty((self.attendee, ))

        # Possible case where ATTENDEE prop is missing - this happens with a "fake" master sometimes
        if attendee is None:
            log.error(
                "ATTENDEE for user making an attendee change is missing: {attendee}",
                attendee=self.attendee)
            return False

        partstatChanged = attendee.parameterValue("PARTSTAT",
                                                  "NEEDS-ACTION") != "DECLINED"
        attendee.setParameter("PARTSTAT", "DECLINED")
        prop = component.getProperty("X-APPLE-NEEDS-REPLY")
        if prop:
            component.removeProperty(prop)
        component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
        return partstatChanged
Beispiel #5
0
    def _updateEvent(self):
        """
        Set the ACKNOWLEDGED property on an event.

        @return: C{None} if there are no events to play with,
            otherwise a L{Deferred} which fires when the acknowledged
            change has been made.
        """

        # Only do updates when we reach of the designated minutes past the hour
        if not self._shouldUpdate(datetime.now().minute):
            return succeed(None)

        if not self._client.started:
            return succeed(None)

        if self.myEventHref is None:
            return self._initEvent()

        event = self._client.eventByHref(self.myEventHref)

        # Add/update the ACKNOWLEDGED property
        component = event.component.mainComponent()
        component.replaceProperty(Property("ACKNOWLEDGED", DateTime.getNowUTC()))
        d = self._client.changeEvent(event.url)
        return self._newOperation("update", d)
Beispiel #6
0
    def _updateEvent(self):
        """
        Try to add a new attendee to an event, or perhaps remove an
        existing attendee from an event.

        @return: C{None} if there are no events to play with,
            otherwise a L{Deferred} which fires when the attendee
            change has been made.
        """

        if not self._client.started:
            return succeed(None)

        # If it does not exist, try to create it
        calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
        if not calendar.events:
            return self._initEvent()
        events = [
            event for event in calendar.events.values()
            if event.url.endswith("event_to_update.ics")
        ]
        if not events:
            return self._initEvent()
        event = events[0]

        # Add/update the ACKNOWLEDGED property
        component = event.component.mainComponent()
        component.replaceProperty(
            Property("ACKNOWLEDGED", DateTime.getNowUTC()))
        d = self._client.changeEvent(event.url)
        return self._newOperation("update", d)
Beispiel #7
0
    def attendeeProperty(self, params={}):
        """
        Returns a pycalendar ATTENDEE property for this record.

        @params: extra parameters such as MEMBER to add to ATTENDEE property
        @type: C{dict}

        @return: the attendee property
        @rtype: C{Property}
        """
        params = params.copy() if params else params
        if "PARTSTAT" not in params:
            params["PARTSTAT"] = "NEEDS-ACTION"
        if "CN"not in params:
            if self.displayName:
                params["CN"] = self.displayName.encode("utf-8")
        if "EMAIL" not in params:
            if hasattr(self, "emailAddresses"):
                params["EMAIL"] = list(self.emailAddresses)[0].encode("utf-8")
        if "CUTYPE" not in params:
            cuType = self.getCUType()
            if cuType != "INDIVIDUAL":
                params["CUTYPE"] = cuType

        return Property("ATTENDEE", self.canonicalCalendarUserAddress().encode("utf-8"), params=params)
Beispiel #8
0
    def _updateEvent(self):
        """
        Set the ACKNOWLEDGED property on an event.

        @return: C{None} if there are no events to play with,
            otherwise a L{Deferred} which fires when the acknowledged
            change has been made.
        """

        # Only do updates when we reach of the designated minutes past the hour
        if not self._shouldUpdate(datetime.now().minute):
            return succeed(None)

        if not self._client.started:
            return succeed(None)

        # If it does not exist, try to create it
        calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
        if not calendar.events:
            return self._initEvent()
        events = [
            event for event in calendar.events.values()
            if event.url.endswith("event_to_update.ics")
        ]
        if not events:
            return self._initEvent()
        event = events[0]

        # Add/update the ACKNOWLEDGED property
        component = event.component.mainComponent()
        component.replaceProperty(
            Property("ACKNOWLEDGED", DateTime.getNowUTC()))
        d = self._client.changeEvent(event.url)
        return self._newOperation("update", d)
    def filter(self, ical):
        """
        Filter the supplied iCalendar object using the request information.

        @param ical: iCalendar object
        @type ical: L{Component} or C{str}

        @return: L{Component} for the filtered calendar data
        """

        master = ical.masterComponent()
        for component in tuple(ical.subcomponents()):
            if component.name() in ignoredComponents:
                continue
            rid = component.getRecurrenceIDUTC()
            if rid is None:
                continue
            if component.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
                rid = component.getRecurrenceIDUTC()
                ical.removeComponent(component)

                # Add EXDATE and try to preserve same timezone as DTSTART
                if master is not None:
                    dtstart = master.getProperty("DTSTART")
                    if dtstart is not None and not dtstart.value().isDateOnly(
                    ) and dtstart.value().local():
                        rid.adjustTimezone(dtstart.value().getTimezone())
                    master.addProperty(Property("EXDATE", [
                        rid,
                    ]))

        return ical
Beispiel #10
0
    def _initEvent(self):
        # Don't perform any operations until the client is up and running
        if not self._client.started:
            return succeed(None)
        try:
            calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
        except IndexError:
            # There is no calendar
            return succeed(None)

        self.myEventHref = '{}{}.ics'.format(calendar.url, str(uuid4()))

        # Copy the template event and fill in some of its fields
        # to make a new event to create on the calendar.
        vcalendar = self._eventTemplate.duplicate()
        vevent = vcalendar.mainComponent()
        uid = str(uuid4())
        dtstart = self._eventStartDistribution.sample()
        dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
        vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTART", dtstart))
        vevent.replaceProperty(Property("DTEND", dtend))
        vevent.replaceProperty(Property("UID", uid))
        vevent.replaceProperty(Property("DESCRIPTION", "AlarmAcknowledger"))

        rrule = self._recurrenceDistribution.sample()
        if rrule is not None:
            vevent.addProperty(Property(None, None, None, pycalendar=rrule))

        d = self._client.addEvent(self.myEventHref, vcalendar)
        return self._newOperation("create", d)
Beispiel #11
0
 def _makeSelfOrganizer(self):
     organizer = Property(
         name=u'ORGANIZER',
         value=self.email,
         params={
             'CN': self.record.commonName,
         },
     )
     return organizer
Beispiel #12
0
 def attachProperty(self):
     """
     Return an iCalendar ATTACH property for this attachment.
     """
     attach = Property("ATTACH", "", valuetype=Value.VALUETYPE_URI)
     location = (yield self.updateProperty(attach))
     returnValue((
         attach,
         location,
     ))
Beispiel #13
0
    def _transferVPOLLData(self, serverComponent, clientComponent):

        changed = False

        # Get the matching VVOTER component in each VPOLL
        serverVoter = serverComponent.voterComponentForVoter(self.attendee)
        clientVoter = clientComponent.voterComponentForVoter(self.attendee)

        # Now get a map of each response
        serverMap = serverVoter.voteMap()
        clientMap = clientVoter.voteMap()

        # Remove missing
        for poll_id in set(serverMap.keys()) - set(clientMap.keys()):
            serverVoter.removeComponent(serverMap[poll_id])
            changed = True

        # Add new ones
        for poll_id in set(clientMap.keys()) - set(serverMap.keys()):
            vote = clientMap[poll_id].duplicate()
            vote.replaceProperty(
                Property("LAST-MODIFIED", DateTime.getNowUTC()))
            serverVoter.addComponent(vote)
            changed = True

        # Look for response change
        for poll_id in set(serverMap.keys()) & set(clientMap.keys()):
            server_vote = serverMap[poll_id]
            client_vote = clientMap[poll_id]
            server_response = server_vote.propertyValue("RESPONSE")
            client_response = client_vote.propertyValue("RESPONSE")
            if server_response != client_response:
                if client_response is not None:
                    server_vote.replaceProperty(
                        Property("RESPONSE", client_response))
                else:
                    server_vote.removeProperty("RESPONSE")
                server_vote.replaceProperty(
                    Property("LAST-MODIFIED", DateTime.getNowUTC()))
                changed = True

        return changed
Beispiel #14
0
 def _makeSelfAttendee(self):
     attendee = Property(
         name=u'ATTENDEE',
         value=self.email,
         params={
             'CN': self.record.commonName,
             'CUTYPE': 'INDIVIDUAL',
             'PARTSTAT': 'ACCEPTED',
         },
     )
     return attendee
Beispiel #15
0
    def _defaultFilter(self, ical):
        """
        There is no per-user component. Instead apply default properties to the data for this user.

        @param ical: the iCalendar object to process
        @type ical: L{Component}
        """

        # TRANSP property behavior: if the event is all-day add TRANSP:TRANSPARENT, otherwise do nothing
        comp = ical.mainComponent()
        if comp.name() == "VEVENT" and comp.propertyValue("DTSTART").isDateOnly() and not comp.hasProperty("TRANSP"):
            ical.addPropertyToAllComponents(Property("TRANSP", "TRANSPARENT"))
Beispiel #16
0
    def _transferProperty(self, propName, serverComponent, clientComponent):

        changed = False
        serverProp = serverComponent.getProperty(propName)
        clientProp = clientComponent.getProperty(propName)
        if serverProp != clientProp:
            if clientProp:
                serverComponent.replaceProperty(Property(propName, clientProp.value()))
            else:
                serverComponent.removeProperty(serverProp)
            changed = True
        return changed
Beispiel #17
0
    def _addEvent(self):
        # Don't perform any operations until the client is up and running
        if not self._client.started:
            return succeed(None)

        calendar = self._getRandomCalendarOfType('VEVENT')

        if not calendar:
            # No VEVENT calendars, so no new event...
            return succeed(None)

        # Copy the template event and fill in some of its fields
        # to make a new event to create on the calendar.
        vcalendar = self._eventTemplate.duplicate()
        vevent = vcalendar.mainComponent()
        uid = str(uuid4())
        dtstart = self._eventStartDistribution.sample()
        dtend = dtstart + Duration(
            seconds=self._eventDurationDistribution.sample())
        vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTART", dtstart))
        vevent.replaceProperty(Property("DTEND", dtend))
        vevent.replaceProperty(Property("UID", uid))

        rrule = self._recurrenceDistribution.sample()
        if rrule is not None:
            vevent.addProperty(Property(None, None, None, pycalendar=rrule))

        href = '%s%s.ics' % (calendar.url, uid)
        d = self._client.addEvent(href, vcalendar)
        return self._newOperation("create", d)
Beispiel #18
0
    def _initEvent(self):
        if not self._client.started:
            return succeed(None)

        # If it already exists, don't re-create
        calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
        if calendar.events:
            events = [
                event for event in calendar.events.values()
                if event.url.endswith("event_to_update.ics")
            ]
            if events:
                return succeed(None)

        # Copy the template event and fill in some of its fields
        # to make a new event to create on the calendar.
        vcalendar = self._eventTemplate.duplicate()
        vevent = vcalendar.mainComponent()
        uid = str(uuid4())
        dtstart = self._eventStartDistribution.sample()
        dtend = dtstart + Duration(
            seconds=self._eventDurationDistribution.sample())
        vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
        vevent.replaceProperty(Property("DTSTART", dtstart))
        vevent.replaceProperty(Property("DTEND", dtend))
        vevent.replaceProperty(Property("UID", uid))

        rrule = self._recurrenceDistribution.sample()
        if rrule is not None:
            vevent.addProperty(Property(None, None, None, pycalendar=rrule))

        href = '%s%s' % (calendar.url, "event_to_update.ics")
        d = self._client.addEvent(href, vcalendar)
        return self._newOperation("create", d)
Beispiel #19
0
    def _addEvent(self):
        if not self._client.started:
            return succeed(None)

        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")

        while calendars:
            calendar = self.random.choice(calendars)
            calendars.remove(calendar)

            # Copy the template event and fill in some of its fields
            # to make a new event to create on the calendar.
            vcalendar = self._eventTemplate.duplicate()
            vevent = vcalendar.mainComponent()
            uid = str(uuid4())
            dtstart = self._eventStartDistribution.sample()
            dtend = dtstart + Duration(
                seconds=self._eventDurationDistribution.sample())
            vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
            vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
            vevent.replaceProperty(Property("DTSTART", dtstart))
            vevent.replaceProperty(Property("DTEND", dtend))
            vevent.replaceProperty(Property("UID", uid))

            rrule = self._recurrenceDistribution.sample()
            if rrule is not None:
                vevent.addProperty(Property(None, None, None,
                                            pycalendar=rrule))

            href = '%s%s.ics' % (calendar.url, uid)
            d = self._client.addEvent(href, vcalendar)
            return self._newOperation("create", d)
Beispiel #20
0
    def _defaultMerge(self, component):
        """
        Handle default property values during a merge.

        @param component: the iCalendar component to process
        @type component: L{Component}
        """

        # Special handling for TRANSP: if it is not present on an all-day event,
        # add TRANSP:OPAQUE so that the default is explicitly included, since otherwise
        # L_defaultFilter) will add TRANSP:TRANSPARENT
        if component.name() == "VEVENT" and component.propertyValue("DTSTART").isDateOnly() and not component.hasProperty("TRANSP"):
            component.addProperty(Property("TRANSP", "OPAQUE"))
Beispiel #21
0
    def _invite(self):
        """
        Try to add a new event, or perhaps remove an
        existing attendee from an event.

        @return: C{None} if there are no events to play with,
            otherwise a L{Deferred} which fires when the attendee
            change has been made.
        """

        if not self._client.started:
            return succeed(None)

        # Find calendars which are eligible for invites
        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")

        while calendars:
            # Pick one at random from which to try to create an event
            # to modify.
            calendar = self.random.choice(calendars)
            calendars.remove(calendar)

            # Copy the template event and fill in some of its fields
            # to make a new event to create on the calendar.
            vcalendar = self._eventTemplate.duplicate()
            vevent = vcalendar.mainComponent()
            uid = str(uuid4())
            dtstart = self._eventStartDistribution.sample()
            dtend = dtstart + Duration(
                seconds=self._eventDurationDistribution.sample())
            vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
            vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
            vevent.replaceProperty(Property("DTSTART", dtstart))
            vevent.replaceProperty(Property("DTEND", dtend))
            vevent.replaceProperty(Property("UID", uid))

            rrule = self._recurrenceDistribution.sample()
            if rrule is not None:
                vevent.addProperty(Property(None, None, None,
                                            pycalendar=rrule))

            vevent.addProperty(self._client._makeSelfOrganizer())
            vevent.addProperty(self._client._makeSelfAttendee())

            attendees = list(vevent.properties('ATTENDEE'))
            for _ignore in range(int(self._inviteeCountDistribution.sample())):
                try:
                    self._addAttendee(vevent, attendees)
                except CannotAddAttendee:
                    self._failedOperation("invite", "Cannot add attendee")
                    return succeed(None)

            href = '%s%s.ics' % (calendar.url, uid)
            d = self._client.addInvite(href, vcalendar)
            return self._newOperation("invite", d)
    def split(self, ical, rid=None, olderUID=None):
        """
        Split the specified iCalendar object. This assumes that L{willSplit} has already
        been called and returned C{True}. Splitting is done by carving out old instances
        into a new L{Component} and adjusting the specified component for the on-going
        set. A RELATED-TO property is added to link old to new.

        @param ical: the iCalendar object to split
        @type ical: L{Component}

        @param rid: recurrence-id where the split should occur, or C{None} to determine it here
        @type rid: L{DateTime} or C{None}

        @param olderUID: UID to use for the split off component, or C{None} to generate one here
        @type olderUID: C{str} or C{None}

        @return: iCalendar objects for the old and new "carved out" instances
        @rtype: C{tuple} of two L{Component}'s
        """

        # Find the instance RECURRENCE-ID where a split is going to happen
        rid = self.whereSplit(ical) if rid is None else rid

        # Create the old one with a new UID value (or the one passed in)
        icalOld = ical.duplicate()
        oldUID = icalOld.newUID(newUID=olderUID)
        icalOld.onlyPastInstances(rid)

        # Adjust the current one
        icalNew = ical.duplicate()
        icalNew.onlyFutureInstances(rid)

        # Relate them - add RELATED-TO;RELTYPE=RECURRENCE-SET if not already present
        if not icalOld.hasPropertyWithParameterMatch(
                "RELATED-TO", "RELTYPE", "X-CALENDARSERVER-RECURRENCE-SET"):
            property = Property(
                "RELATED-TO",
                str(uuid.uuid5(self.uuid_namespace, oldUID)),
                params={"RELTYPE": "X-CALENDARSERVER-RECURRENCE-SET"})
            icalOld.addPropertyToAllComponents(property)
            icalNew.addPropertyToAllComponents(property)

        return (
            icalOld,
            icalNew,
        )
Beispiel #23
0
    def processDSN(self, calBody, msgId):
        calendar = Component.fromString(calBody)
        # Extract the token (from organizer property)
        organizer = calendar.getOrganizer()
        token = self._extractToken(organizer)
        if not token:
            log.error("Mail gateway can't find token in DSN {msgid}",
                      msgid=msgId)
            return

        txn = self.store.newTransaction(label="MailReceiver.processDSN")
        records = (yield txn.imipLookupByToken(token))
        yield txn.commit()
        try:
            # Note the results are returned as utf-8 encoded strings
            record = records[0]
        except:
            # This isn't a token we recognize
            log.error(
                "Mail gateway found a token ({token}) but didn't recognize it in message {msgid}",
                token=token,
                msgid=msgId,
            )
            returnValue(self.UNKNOWN_TOKEN)

        calendar.removeAllButOneAttendee(record.attendee)
        calendar.getOrganizerProperty().setValue(organizer)
        for comp in calendar.subcomponents():
            if comp.name() == "VEVENT":
                comp.addProperty(
                    Property("REQUEST-STATUS", ["5.1", "Service unavailable"]))
                break
        else:
            # no VEVENT in the calendar body.
            # TODO: what to do in this case?
            pass

        log.warn("Mail gateway processing DSN {msgid}", msgid=msgId)
        txn = self.store.newTransaction(label="MailReceiver.processDSN")
        yield txn.enqueue(IMIPReplyWork,
                          organizer=record.organizer,
                          attendee=record.attendee,
                          icalendarText=str(calendar))
        yield txn.commit()
        returnValue(self.INJECTION_SUBMITTED)
Beispiel #24
0
    def action(self):
        # Don't perform any operations until the client is up and running
        if not self._client.started:
            returnValue(None)

        event = self._getRandomEventOfType('VEVENT', justOwned=True)
        if not event:
            returnValue(None)
        component = event.component
        vevent = component.mainComponent()

        label = yield self.modifyEvent(event.url, vevent)
        if label:
            vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))

            event.component = component
            yield self._newOperation(label,
                                     self._client.changeEvent(event.url))
Beispiel #25
0
def sanitizeCalendar(calendar):
    """
    Clean up specific issues seen in the wild from third party IMIP capable
    servers.

    @param calendar: the calendar Component to sanitize
    @type calendar: L{Component}
    """
    # Don't let a missing PRODID prevent the reply from being processed
    if not calendar.hasProperty("PRODID"):
        calendar.addProperty(Property("PRODID", "Unknown"))

    # For METHOD:REPLY we can remove STATUS properties
    methodProperty = calendar.getProperty("METHOD")
    if methodProperty is not None:
        # Uppercase any METHOD properties
        methodProperty.setValue(methodProperty.value().upper())
        if methodProperty.value() == "REPLY":
            calendar.removeAllPropertiesWithName("STATUS")
Beispiel #26
0
    def _addAttendee(self, event, attendees):
        """
        Create a new attendee to add to the list of attendees for the
        given event.
        """
        selfRecord = self._sim.getUserRecord(self._number)
        invitees = set([u'mailto:%s' % (selfRecord.email, )])
        for att in attendees:
            invitees.add(att.value())

        for _ignore_i in range(10):

            sample = self._inviteeDistribution.sample()
            if self._inviteeClumping:
                sample = self._number + sample
            invitee = max(0, sample)

            try:
                record = self._sim.getUserRecord(invitee)
            except IndexError:
                continue
            cuaddr = u'mailto:%s' % (record.email, )
            if cuaddr not in invitees:
                break
        else:
            raise CannotAddAttendee("Can't find uninvited user to invite.")

        attendee = Property(
            name=u'ATTENDEE',
            value=cuaddr.encode("utf-8"),
            params={
                'CN': record.commonName,
                'CUTYPE': 'INDIVIDUAL',
                'PARTSTAT': 'NEEDS-ACTION',
                'ROLE': 'REQ-PARTICIPANT',
                'RSVP': 'TRUE',
            },
        )

        event.addProperty(attendee)
        attendees.append(attendee)
Beispiel #27
0
    def updateAttendeeData(from_component, to_component):
        """
        Called when processing a REPLY only.

        Copy the PARTSTAT of the Attendee in the from_component to the matching ATTENDEE
        in the to_component. Ignore if no match found. Also update the private comments.

        For VPOLL we need to copy POLL-ITEM-ID response values into the actual matching
        polled sub-components as VOTER properties.

        @param from_component: component to copy from
        @type from_component: L{Component}
        @param to_component: component to copy to
        @type to_component: L{Component}
        """

        # Track what changed
        partstat_changed = False
        private_comment_changed = False

        # Get REQUEST-STATUS as we need to write that into the saved ATTENDEE property
        reqstatus = tuple(from_component.properties("REQUEST-STATUS"))
        if reqstatus:
            reqstatus = ",".join(status.value()[0] for status in reqstatus)
        else:
            reqstatus = "2.0"

        # Get attendee in from_component - there MUST be only one
        attendees = tuple(from_component.properties(from_component.recipientPropertyName()))
        if len(attendees) != 1:
            log.error("There must be one and only one ATTENDEE property in a REPLY\n%s" % (str(from_component),))
            return None, False, False

        attendee = attendees[0]
        partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")

        # Now find matching ATTENDEE in to_component
        existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
        if existing_attendee:
            oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
            existing_attendee.setParameter("PARTSTAT", partstat)
            existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
            partstat_changed = (oldpartstat != partstat)

            # Always delete RSVP on PARTSTAT change
            if partstat_changed:
                try:
                    existing_attendee.removeParameter("RSVP")
                except KeyError:
                    pass

            # Handle attendee comments
            if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
                # Look for X-CALENDARSERVER-PRIVATE-COMMENT property in iTIP component (State 1 in spec)
                attendee_comment = tuple(from_component.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
                attendee_comment = attendee_comment[0] if len(attendee_comment) else None

                # Look for matching X-CALENDARSERVER-ATTENDEE-COMMENT property in existing data (State 2 in spec)
                private_comments = tuple(to_component.properties("X-CALENDARSERVER-ATTENDEE-COMMENT"))
                for comment in private_comments:
                    attendeeref = comment.parameterValue("X-CALENDARSERVER-ATTENDEE-REF")
                    if attendeeref == attendee.value():
                        private_comment = comment
                        break
                else:
                    private_comment = None
            else:
                attendee_comment = None
                private_comment = None

            # Now do update logic
            if attendee_comment is None and private_comment is None:
                # Nothing to do
                pass

            elif attendee_comment is None and private_comment is not None:
                # We now remove the private comment on the organizer's side if the attendee removed it
                to_component.removeProperty(private_comment)

                private_comment_changed = True

            elif attendee_comment is not None and private_comment is None:

                # Add new property
                private_comment = Property(
                    "X-CALENDARSERVER-ATTENDEE-COMMENT",
                    attendee_comment.value(),
                    params={
                        "X-CALENDARSERVER-ATTENDEE-REF": attendee.value(),
                        "X-CALENDARSERVER-DTSTAMP": DateTime.getNowUTC().getText(),
                    }
                )
                to_component.addProperty(private_comment)

                private_comment_changed = True

            else:
                # Only change if different
                if private_comment.value() != attendee_comment.value():
                    # Remove all property parameters
                    private_comment.removeAllParameters()

                    # Add default parameters
                    private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
                    private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", DateTime.getNowUTC().getText())

                    # Set new value
                    private_comment.setValue(attendee_comment.value())

                    private_comment_changed = True

            # Do VPOLL transfer
            if from_component.name() == "VPOLL":
                # TODO: figure out how to report changes back
                iTipProcessing.updateVPOLLData(from_component, to_component, attendee)

        return attendee.value(), partstat_changed, private_comment_changed
Beispiel #28
0
    def updateAttendeeData(from_component, to_component):
        """
        Copy the PARTSTAT of the Attendee in the from_component to the matching ATTENDEE
        in the to_component. Ignore if no match found. Also update the private comments.

        @param from_component:
        @type from_component:
        @param to_component:
        @type to_component:
        """
        
        # Track what changed
        partstat_changed = False
        private_comment_changed = False

        # Get REQUEST-STATUS as we need to write that into the saved ATTENDEE property
        reqstatus = tuple(from_component.properties("REQUEST-STATUS"))
        if reqstatus:
            reqstatus = ",".join(status.value()[0] for status in reqstatus)
        else:
            reqstatus = "2.0"

        # Get attendee in from_component - there MUST be only one
        attendees = tuple(from_component.properties("ATTENDEE"))
        if len(attendees) != 1:
            log.error("There must be one and only one ATTENDEE property in a REPLY\n%s" % (str(from_component),))
            return None, False, False

        attendee = attendees[0]
        partstat = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
        
        # Now find matching ATTENDEE in to_component
        existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
        if existing_attendee:
            oldpartstat = existing_attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
            existing_attendee.params()["PARTSTAT"] = [partstat]
            existing_attendee.params()["SCHEDULE-STATUS"] = [reqstatus]
            partstat_changed = (oldpartstat != partstat)
            
            # Always delete RSVP on PARTSTAT change
            if partstat_changed:
                try:
                    del existing_attendee.params()["RSVP"]
                except KeyError:
                    pass

            # Handle attendee comments
            if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
                # Look for X-CALENDARSERVER-PRIVATE-COMMENT property in iTIP component (State 1 in spec)
                attendee_comment = tuple(from_component.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
                attendee_comment = attendee_comment[0] if len(attendee_comment) else None
                
                # Look for matching X-CALENDARSERVER-ATTENDEE-COMMENT property in existing data (State 2 in spec)
                private_comments = tuple(to_component.properties("X-CALENDARSERVER-ATTENDEE-COMMENT"))
                for comment in private_comments:
                    params = comment.params()["X-CALENDARSERVER-ATTENDEE-REF"]
                    if len(params) != 1:
                        log.error("Must be one and only one X-CALENDARSERVER-ATTENDEE-REF parameter in X-CALENDARSERVER-ATTENDEE-COMMENT")
                        params = (None,)
                    param = params[0]
                    if param == attendee.value():
                        private_comment = comment
                        break
                else:
                    private_comment = None
            else:
                attendee_comment = None
                private_comment = None
                
            # Now do update logic
            if attendee_comment is None and private_comment is None:
                # Nothing to do
                pass
 
            elif attendee_comment is None and private_comment is not None:
                # Remove all property parameters
                private_comment.params().clear()
                
                # Add default parameters
                private_comment.params()["X-CALENDARSERVER-ATTENDEE-REF"] = [attendee.value()]
                private_comment.params()["X-CALENDARSERVER-DTSTAMP"] = [dateTimeToString(datetime.datetime.now(tz=utc))]
                
                # Set value empty
                private_comment.setValue("")
                
                private_comment_changed = True
                
            elif attendee_comment is not None and private_comment is None:
                
                # Add new property
                private_comment = Property(
                    "X-CALENDARSERVER-ATTENDEE-COMMENT",
                    attendee_comment.value(),
                    params = {
                        "X-CALENDARSERVER-ATTENDEE-REF":     [attendee.value()],
                        "X-CALENDARSERVER-DTSTAMP": [dateTimeToString(datetime.datetime.now(tz=utc))],
                    }
                )
                to_component.addProperty(private_comment)
                
                private_comment_changed = True
            
            else:
                # Only change if different
                if private_comment.value() != attendee_comment.value():
                    # Remove all property parameters
                    private_comment.params().clear()
                    
                    # Add default parameters
                    private_comment.params()["X-CALENDARSERVER-ATTENDEE-REF"] = [attendee.value()]
                    private_comment.params()["X-CALENDARSERVER-DTSTAMP"] = [dateTimeToString(datetime.datetime.now(tz=utc))]
                    
                    # Set new value
                    private_comment.setValue(attendee_comment.value())
    
                    private_comment_changed = True

        return attendee.value(), partstat_changed, private_comment_changed
Beispiel #29
0
 def modifyEvent(self, _ignore_href, vevent):
     length = int(self._descriptionLength.sample())
     vevent.replaceProperty(Property("DESCRIPTION", "." * length))
     return succeed("update{description}")
Beispiel #30
0
 def modifyEvent(self, _ignore_href, vevent):
     length = max(5, int(self._titleLength.sample()))
     vevent.replaceProperty(
         Property("SUMMARY", "Event" + "." * (length - 5)))
     return succeed("update{title}")
Beispiel #31
0
    def buildFreeBusyResult(self, fbinfo, method=None):
        """
        Generate a VCALENDAR object containing a single VFREEBUSY that is the
        aggregate of the free busy info passed in.

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

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

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

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

        return fbcalendar
    def _splitPerUserData(self, ical):
        """
        Split the per-user data out of the "normal" iCalendar components into separate per-user
        components. Along the way keep the iCalendar representation in a "minimal" state by eliminating
        any components that are the same as the master derived component.

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

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

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

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

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

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

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

            if perinstance_id_different and perinstance_component:
                perinstance_components[rid] = perinstance_component

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

            if peruser_component_different:
                ical.addComponent(peruser_component)

            self._compactInstances(ical)
 def init_peruser_component():
     peruser = Component(PERUSER_COMPONENT)
     peruser.addProperty(Property("UID", ical.resourceUID()))
     peruser.addProperty(Property(PERUSER_UID, self.uid))
     return peruser