def generateResponse(self, recipient, responses): # Hash the iCalendar data for use as the last path element of the URI path name = "{hash}-{r}.ics".format(hash=hashlib.md5(self.scheduler.calendar.resourceUID()).hexdigest(), r=str(uuid.uuid4())[:8],) # Do implicit scheduling message processing. try: processor = ImplicitProcessor() _ignore_processed, autoprocessed, store_inbox, changes = (yield processor.doImplicitProcessing( self.scheduler.txn, self.scheduler.calendar, self.scheduler.originator, recipient, noAttendeeRefresh=self.scheduler.noAttendeeRefresh, )) except ImplicitProcessorException as e: log.failure( "Could not store data in inbox {inbox}", inbox=recipient.inbox, level=LogLevel.debug ) log.error( "Could not store data in inbox {inbox}", inbox=recipient.inbox ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not store data in inbox", )) responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=e.msg) returnValue(False) except Exception as e: log.failure( "Could not process iTIP message", level=LogLevel.debug ) log.error( "Could not process iTIP message", ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not process iTIP message", )) responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.BAD_REQUEST) returnValue(False) if store_inbox: # Copy calendar to inbox try: child = yield recipient.inbox._createCalendarObjectWithNameInternal(name, self.scheduler.calendar, ComponentUpdateState.INBOX) except Exception as e: log.failure( "Could not store data in inbox {inbox}: {error}", inbox=recipient.inbox, error=e, level=LogLevel.debug ) log.error( "Could not store data in inbox {inbox}: {error}", inbox=recipient.inbox, error=e ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not store data in inbox", )) responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_AUTHORITY) returnValue(False) else: # Store CS:schedule-changes property if present if changes is not None: props = child.properties() props[PropertyName.fromElement(changes)] = changes responses.add(recipient.cuaddr, responsecode.OK, reqstatus=iTIPRequestStatus.MESSAGE_DELIVERED) if autoprocessed: if self.scheduler.logItems is not None: self.scheduler.logItems["itip.auto"] = self.scheduler.logItems.get("itip.auto", 0) + 1 returnValue(True)
def importCollectionComponent(store, component): """ Import a component representing a collection (e.g. VCALENDAR) into the store. The homeUID and collection resource name the component will be imported into is derived from the SOURCE property on the VCALENDAR (which must be present). The code assumes it will be a URI with slash-separated parts with the penultimate part specifying the homeUID and the last part specifying the calendar resource name. The NAME property will be used to set the DAV:display-name, while the COLOR property will be used to set calendar-color. Subcomponents (e.g. VEVENTs) are grouped into resources by UID. Objects which have a UID already in use within the home will be skipped. @param store: The db store to add the component to @type store: L{IDataStore} @param component: The component to store @type component: L{twistedcaldav.ical.Component} """ sourceURI = component.propertyValue("SOURCE") if not sourceURI: raise ImportException("Calendar is missing SOURCE property") ownerUID, collectionResourceName = sourceURI.strip("/").split("/")[-2:] dir = store.directoryService() ownerRecord = yield dir.recordWithUID(ownerUID) if not ownerRecord: raise ImportException("{} is not in the directory".format(ownerUID)) # Set properties on the collection txn = store.newTransaction() home = yield txn.calendarHomeWithUID(ownerUID, create=True) collection = yield home.childWithName(collectionResourceName) if not collection: print("Creating calendar: {}".format(collectionResourceName)) collection = yield home.createChildWithName(collectionResourceName) for propertyName, element in ( ("NAME", davxml.DisplayName), ("COLOR", customxml.CalendarColor), ): value = component.propertyValue(propertyName) if value is not None: setCollectionPropertyValue(collection, element, value) print( "Setting {name} to {value}".format(name=propertyName, value=value) ) yield txn.commit() # Populate the collection; NB we use a txn for each object, and we might # want to batch them? groupedComponents = Component.componentsFromComponent(component) for groupedComponent in groupedComponents: try: uid = list(groupedComponent.subcomponents())[0].propertyValue("UID") except: continue # If event is unscheduled or the organizer matches homeUID, store the # component print("Event UID: {}".format(uid)) storeDirectly = True organizer = groupedComponent.getOrganizer() if organizer is not None: organizerRecord = yield dir.recordWithCalendarUserAddress(organizer) if organizerRecord is None: # Organizer does not exist, so skip this event continue else: if ownerRecord.uid != organizerRecord.uid: # Owner is not the organizer storeDirectly = False if storeDirectly: resourceName = "{}.ics".format(str(uuid.uuid4())) try: yield storeComponentInHomeAndCalendar( store, groupedComponent, ownerUID, collectionResourceName, resourceName ) print("Imported: {}".format(uid)) except UIDExistsError: # That event is already in the home print("Skipping since UID already exists: {}".format(uid)) except Exception, e: print( "Failed to import due to: {error}\n{comp}".format( error=e, comp=groupedComponent ) ) else: # Owner is an attendee, not the organizer # Apply the PARTSTATs from the import and from the possibly # existing event (existing event takes precedence) to the # organizer's copy. # Put the attendee copy into the right calendar now otherwise it # could end up on the default calendar when the change to the # organizer's copy causes an attendee update resourceName = "{}.ics".format(str(uuid.uuid4())) try: yield storeComponentInHomeAndCalendar( store, groupedComponent, ownerUID, collectionResourceName, resourceName, asAttendee=True ) print("Imported: {}".format(uid)) except UIDExistsError: # No need since the event is already in the home pass # Now use the iTip reply processing to update the organizer's copy # with the PARTSTATs from the component we're restoring. attendeeCUA = ownerRecord.canonicalCalendarUserAddress() organizerCUA = organizerRecord.canonicalCalendarUserAddress() processor = ImplicitProcessor() newComponent = iTipGenerator.generateAttendeeReply(groupedComponent, attendeeCUA, method="X-RESTORE") if newComponent is not None: txn = store.newTransaction() yield processor.doImplicitProcessing( txn, newComponent, LocalCalendarUser(attendeeCUA, ownerRecord), LocalCalendarUser(organizerCUA, organizerRecord) ) yield txn.commit()
def generateResponse(self, recipient, responses): # Hash the iCalendar data for use as the last path element of the URI path name = "%s-%s.ics" % (hashlib.md5(self.scheduler.calendar.resourceUID()).hexdigest(), str(uuid.uuid4())[:8],) # Do implicit scheduling message processing. try: processor = ImplicitProcessor() _ignore_processed, autoprocessed, store_inbox, changes = (yield processor.doImplicitProcessing( self.scheduler.txn, self.scheduler.calendar, self.scheduler.originator, recipient, noAttendeeRefresh=self.scheduler.noAttendeeRefresh, )) except ImplicitProcessorException as e: log.failure( "Could not store data in inbox {inbox}", inbox=recipient.inbox, level=LogLevel.debug ) log.error( "Could not store data in inbox {inbox}", inbox=recipient.inbox ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not store data in inbox", )) responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=e.msg) returnValue(False) if store_inbox: # Copy calendar to inbox try: child = yield recipient.inbox._createCalendarObjectWithNameInternal(name, self.scheduler.calendar, ComponentUpdateState.INBOX) except Exception as e: log.failure( "Could not store data in inbox {inbox}: {error}", inbox=recipient.inbox, error=e, level=LogLevel.debug ) log.error( "Could not store data in inbox {inbox}: {error}", inbox=recipient.inbox, error=e ) err = HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions"), "Could not store data in inbox", )) responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_AUTHORITY) returnValue(False) else: # Store CS:schedule-changes property if present if changes is not None: props = child.properties() props[PropertyName.fromElement(changes)] = changes responses.add(recipient.cuaddr, responsecode.OK, reqstatus=iTIPRequestStatus.MESSAGE_DELIVERED) if autoprocessed: if self.scheduler.logItems is not None: self.scheduler.logItems["itip.auto"] = self.scheduler.logItems.get("itip.auto", 0) + 1 returnValue(True)
def importCollectionComponent(store, component): """ Import a component representing a collection (e.g. VCALENDAR) into the store. The homeUID and collection resource name the component will be imported into is derived from the SOURCE property on the VCALENDAR (which must be present). The code assumes it will be a URI with slash-separated parts with the penultimate part specifying the homeUID and the last part specifying the calendar resource name. The NAME property will be used to set the DAV:display-name, while the COLOR property will be used to set calendar-color. Subcomponents (e.g. VEVENTs) are grouped into resources by UID. Objects which have a UID already in use within the home will be skipped. @param store: The db store to add the component to @type store: L{IDataStore} @param component: The component to store @type component: L{twistedcaldav.ical.Component} """ sourceURI = component.propertyValue("SOURCE") if not sourceURI: raise ImportException("Calendar is missing SOURCE property") ownerUID, collectionResourceName = sourceURI.strip("/").split("/")[-2:] dir = store.directoryService() ownerRecord = yield dir.recordWithUID(ownerUID) if not ownerRecord: raise ImportException("{} is not in the directory".format(ownerUID)) # Set properties on the collection txn = store.newTransaction() home = yield txn.calendarHomeWithUID(ownerUID, create=True) collection = yield home.childWithName(collectionResourceName) if not collection: print("Creating calendar: {}".format(collectionResourceName)) collection = yield home.createChildWithName(collectionResourceName) for propertyName, element in ( ("NAME", davxml.DisplayName), ("COLOR", customxml.CalendarColor), ): value = component.propertyValue(propertyName) if value is not None: setCollectionPropertyValue(collection, element, value) print("Setting {name} to {value}".format(name=propertyName, value=value)) yield txn.commit() # Populate the collection; NB we use a txn for each object, and we might # want to batch them? groupedComponents = Component.componentsFromComponent(component) for groupedComponent in groupedComponents: try: uid = list( groupedComponent.subcomponents())[0].propertyValue("UID") except: continue # If event is unscheduled or the organizer matches homeUID, store the # component print("Event UID: {}".format(uid)) storeDirectly = True organizer = groupedComponent.getOrganizer() if organizer is not None: organizerRecord = yield dir.recordWithCalendarUserAddress( organizer) if organizerRecord is None: # Organizer does not exist, so skip this event continue else: if ownerRecord.uid != organizerRecord.uid: # Owner is not the organizer storeDirectly = False if storeDirectly: resourceName = "{}.ics".format(str(uuid.uuid4())) try: yield storeComponentInHomeAndCalendar(store, groupedComponent, ownerUID, collectionResourceName, resourceName) print("Imported: {}".format(uid)) except UIDExistsError: # That event is already in the home print("Skipping since UID already exists: {}".format(uid)) except Exception, e: print("Failed to import due to: {error}\n{comp}".format( error=e, comp=groupedComponent)) else: # Owner is an attendee, not the organizer # Apply the PARTSTATs from the import and from the possibly # existing event (existing event takes precedence) to the # organizer's copy. # Put the attendee copy into the right calendar now otherwise it # could end up on the default calendar when the change to the # organizer's copy causes an attendee update resourceName = "{}.ics".format(str(uuid.uuid4())) try: yield storeComponentInHomeAndCalendar(store, groupedComponent, ownerUID, collectionResourceName, resourceName, asAttendee=True) print("Imported: {}".format(uid)) except UIDExistsError: # No need since the event is already in the home pass # Now use the iTip reply processing to update the organizer's copy # with the PARTSTATs from the component we're restoring. attendeeCUA = ownerRecord.canonicalCalendarUserAddress() organizerCUA = organizerRecord.canonicalCalendarUserAddress() processor = ImplicitProcessor() newComponent = iTipGenerator.generateAttendeeReply( groupedComponent, attendeeCUA, method="X-RESTORE") txn = store.newTransaction() yield processor.doImplicitProcessing( txn, newComponent, LocalCalendarUser(attendeeCUA, ownerRecord), LocalCalendarUser(organizerCUA, organizerRecord)) yield txn.commit()