def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code in (responsecode.OK, ): data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError( "Incorrect cross-pod response status code: {}".format( response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code in (responsecode.OK,): data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError("Incorrect cross-pod response status code: {}".format(response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def doAccounting(self): # # Accounting # # Note that we associate logging with the organizer, not the # originator, which is good for looking for why something # shows up in a given principal's calendars, rather than # tracking the activities of a specific user. # if isinstance(self.organizer, LocalCalendarUser): accountingType = "iTIP-VFREEBUSY" if self.calendar.mainType( ) == "VFREEBUSY" else "iTIP" if accountingEnabled(accountingType, self.organizer.record): emitAccounting( accountingType, self.organizer.record, "Originator: {o}\nRecipients:\n{r}Method:{method}\n\n{cal}" .format( o=str(self.originator), r=str("".join([ " {}\n".format(recipient, ) for recipient in self.recipients ])), method=str(self.method), cal=str(self.calendar), ))
def finish(self): super(HTTPLoggingChannelRequest, self).finish() if self.logData is not None: doneTime = time.time() self.logData.response.append("\r\n\r\n<<<< Response complete at: %.3f (elapsed: %.1f ms)\r\n" % (doneTime, 1000 * (doneTime - self.startTime),)) accounting.emitAccounting("HTTP", "", "".join(self.logData.request) + "".join(self.logData.response), self.command)
def test_file_instead_of_directory(self): """ Test permissions when creating accounting """ # Make log root a file config.AccountingLogRoot = "other" open(config.AccountingLogRoot, "w").close() emitAccounting("iTIP", self._Record("1234-5678"), "bogus")
def test_permissions_makedirs(self): """ Test permissions when creating accounting """ # Make log root non-writeable os.chmod(config.AccountingLogRoot, stat.S_IRUSR) emitAccounting("iTIP", self._Record("1234-5678"), "bogus")
def test_file_instead_of_directory(self): """ Test permissions when creating accounting """ # Make log root a file config.AccountingLogRoot = os.path.abspath(self.mktemp()) open(config.AccountingLogRoot, "w").close() emitAccounting("iTIP", self._Principal("1234-5678"), "bogus")
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code == responsecode.OK: if self.writeStream is None: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: yield readStream(response.stream, self.writeStream.write) content_type = response.headers.getHeader("content-type") if content_type is None: content_type = MimeType("application", "octet-stream") content_disposition = response.headers.getHeader( "content-disposition") if content_disposition is None or "filename" not in content_disposition.params: filename = "" else: filename = content_disposition.params["filename"] self.writeStream.resetDetails(content_type, filename) yield self.writeStream.loseConnection() data = { "result": "ok", "content-type": content_type, "name": filename, } elif response.code == responsecode.BAD_REQUEST: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError( "Incorrect cross-pod response status code: {}".format( response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def _logDiffError(self, title): strcal1 = str(self.oldcalendar) strcal2 = str(self.newcalendar) strdiff = "\n".join(unified_diff( strcal1.split("\n"), strcal2.split("\n"), fromfile='Existing Calendar Object', tofile='New Calendar Object', )) logstr = """%s ------ Existing Calendar Data ------ %s ------ New Calendar Data ------ %s ------ Diff ------ %s """ % (title, strcal1, strcal2, strdiff,) loggedUID = self.oldcalendar.resourceUID() if loggedUID: loggedUID = loggedUID.encode("base64")[:-1] else: loggedUID = "Unknown" loggedName = accounting.emitAccounting("Implicit Errors", loggedUID, logstr) if loggedName: log.error("Generating Implicit Error accounting at path: %s" % (loggedName,))
def finish(self): super(HTTPLoggingChannelRequest, self).finish() if self.logData is not None: doneTime = time.time() self.logData.response.append( "\r\n\r\n<<<< Response complete at: %.3f (elapsed: %.1f ms)\r\n" % ( doneTime, 1000 * (doneTime - self.startTime), )) accounting.emitAccounting( "HTTP", "", "".join(self.logData.request) + "".join(self.logData.response), self.command)
def _logDiffError(self, title): strcal1 = str(self.oldcalendar) strcal2 = str(self.newcalendar) strdiff = "\n".join(unified_diff( strcal1.split("\n"), strcal2.split("\n"), fromfile='Existing Calendar Object', tofile='New Calendar Object', )) logstr = """%s ------ Existing Calendar Data ------ %s ------ New Calendar Data ------ %s ------ Diff ------ %s """ % (title, strcal1, strcal2, strdiff,) loggedUID = self.oldcalendar.resourceUID() if loggedUID: loggedUID = loggedUID.encode("base64")[:-1] else: loggedUID = "Unknown" loggedName = accounting.emitAccounting("Implicit Errors", loggedUID, logstr) if loggedName: log.error("Generating Implicit Error accounting at path: {name}", name=loggedName)
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code == responsecode.OK: if self.writeStream is None: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: yield readStream(response.stream, self.writeStream.write) content_type = response.headers.getHeader("content-type") if content_type is None: content_type = MimeType("application", "octet-stream") content_disposition = response.headers.getHeader("content-disposition") if content_disposition is None or "filename" not in content_disposition.params: filename = "" else: filename = content_disposition.params["filename"] self.writeStream.resetDetails(content_type, filename) yield self.writeStream.loseConnection() data = { "result": "ok", "content-type": content_type, "name": filename, } elif response.code == responsecode.BAD_REQUEST: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError("Incorrect cross-pod response status code: {}".format(response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def doAccounting(self): # # Accounting # # Note that we associate logging with the organizer, not the # originator, which is good for looking for why something # shows up in a given principal's calendars, rather than # tracking the activities of a specific user. # if isinstance(self.organizer, LocalCalendarUser): accountingType = "iTIP-VFREEBUSY" if self.calendar.mainType() == "VFREEBUSY" else "iTIP" if accountingEnabled(accountingType, self.organizer.record): emitAccounting( accountingType, self.organizer.record, "Originator: {o}\nRecipients:\n{r}Method:{method}\n\n{cal}".format( o=str(self.originator), r=str("".join([" {}\n".format(recipient,) for recipient in self.recipients])), method=str(self.method), cal=str(self.calendar), ) )
def doAccounting(self): # # Accounting # # Note that we associate logging with the organizer, not the # originator, which is good for looking for why something # shows up in a given principal's calendars, rather than # tracking the activities of a specific user. # if isinstance(self.organizer, LocalCalendarUser): accountingType = "iTIP-VFREEBUSY" if self.calendar.mainType() == "VFREEBUSY" else "iTIP" if accountingEnabled(accountingType, self.organizer.principal): emitAccounting( accountingType, self.organizer.principal, "Originator: %s\nRecipients:\n%sServer Instance:%s\nMethod:%s\n\n%s" % ( str(self.originator), str("".join([" %s\n" % (recipient,) for recipient in self.recipients])), str(self.request.serverInstance), str(self.method), str(self.calendar), ) )
def doImplicitAttendeeCancel(self): """ An iTIP CANCEL message has been sent to an attendee. If there is no existing resource, we will simply ignore the message. If there is an existing resource we need to reconcile the changes between it and the iTIP message. @return: C{tuple} of (processed, auto-processed, store inbox item, changes) """ # If there is no existing copy, then ignore if self.recipient_calendar is None: log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:CANCEL, UID: '%s' - attendee has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) result = (True, True, True, None) else: # Need to check for auto-respond attendees. These need to suppress the inbox message # if the cancel is processed. However, if the principal is a user we always force the # inbox item on them even if auto-schedule is true so that they get a notification # of the cancel. organizer = normalizeCUAddr(self.message.getOrganizer()) autoprocessed = yield self.recipient.record.canAutoSchedule(organizer=organizer) store_inbox = not autoprocessed or self.recipient.record.getCUType() == "INDIVIDUAL" # Check to see if this is a cancel of the entire event processed_message, delete_original, rids = iTipProcessing.processCancel(self.message, self.recipient_calendar, autoprocessing=autoprocessed) if processed_message: if autoprocessed and accountingEnabled("AutoScheduling", self.recipient.record): accounting = { "action": "cancel", "when": DateTime.getNowUTC().getText(), "deleting": delete_original, } emitAccounting( "AutoScheduling", self.recipient.record, json.dumps(accounting) + "\r\n", filename=self.uid.encode("base64")[:-1] + ".txt" ) if delete_original: # Delete the attendee's copy of the event log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - deleting entire event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) yield self.deleteCalendarResource(self.recipient_calendar_resource) # Build the schedule-changes XML element changes = customxml.ScheduleChanges( customxml.DTStamp(), customxml.Action( customxml.Cancel(), ), ) result = (True, autoprocessed, store_inbox, changes,) else: # Update the attendee's copy of the event log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) yield self.writeCalendarResource(None, self.recipient_calendar_resource, self.recipient_calendar) # Build the schedule-changes XML element if rids: action = customxml.Cancel( *[customxml.Recurrence(customxml.RecurrenceID.fromString(rid.getText())) for rid in sorted(rids)] ) else: action = customxml.Cancel() changes = customxml.ScheduleChanges( customxml.DTStamp(), customxml.Action(action), ) result = (True, autoprocessed, store_inbox, changes) else: log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - ignoring" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) result = (True, True, False, None) returnValue(result)
def doImplicitAttendeeRequest(self): """ An iTIP REQUEST message has been sent to an attendee. If there is no existing resource, we will simply create a new one. If there is an existing resource we need to reconcile the changes between it and the iTIP message. @return: C{tuple} of (processed, auto-processed, store inbox item, changes) """ # If there is no existing copy, then look for default calendar and copy it here if self.new_resource: # Check if the incoming data has the recipient declined in all instances. In that case we will not create # a new resource as chances are the recipient previously deleted the resource and we want to keep it deleted. attendees = self.message.getAttendeeProperties((self.recipient.cuaddr,)) if all([attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED" for attendee in attendees]): log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring all declined" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) returnValue((True, False, False, None,)) # Check for default calendar default = (yield self.recipient.inbox.viewerHome().defaultCalendar(self.message.mainType())) if default is None: log.error("No default calendar for recipient: '%s'." % (self.recipient.cuaddr,)) raise ImplicitProcessorException(iTIPRequestStatus.NO_USER_SUPPORT) log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, creating=True) # Handle auto-reply behavior organizer = normalizeCUAddr(self.message.getOrganizer()) if (yield self.recipient.record.canAutoSchedule(organizer=organizer)): # auto schedule mode can depend on who the organizer is mode = yield self.recipient.record.getAutoScheduleMode(organizer=organizer) send_reply, store_inbox, partstat, accounting = (yield self.checkAttendeeAutoReply(new_calendar, mode)) if accounting is not None: accounting["action"] = "create" emitAccounting( "AutoScheduling", self.recipient.record, json.dumps(accounting) + "\r\n", filename=self.uid.encode("base64")[:-1] + ".txt" ) # Only store inbox item when reply is not sent or always for users store_inbox = store_inbox or self.recipient.record.getCUType() == "INDIVIDUAL" else: send_reply = False store_inbox = True new_resource = (yield self.writeCalendarResource(default, None, new_calendar)) if send_reply: # Track outstanding auto-reply processing log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,)) ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat) # Build the schedule-changes XML element changes = customxml.ScheduleChanges( customxml.DTStamp(), customxml.Action( customxml.Create(), ), ) result = (True, send_reply, store_inbox, changes,) else: # Processing update to existing event new_calendar, rids = iTipProcessing.processRequest(self.message, self.recipient_calendar, self.recipient.cuaddr) if new_calendar: # Handle auto-reply behavior organizer = normalizeCUAddr(self.message.getOrganizer()) if (yield self.recipient.record.canAutoSchedule(organizer=organizer)) and not hasattr(self.txn, "doing_attendee_refresh"): # auto schedule mode can depend on who the organizer is mode = yield self.recipient.record.getAutoScheduleMode(organizer=organizer) send_reply, store_inbox, partstat, accounting = (yield self.checkAttendeeAutoReply(new_calendar, mode)) if accounting is not None: accounting["action"] = "modify" emitAccounting( "AutoScheduling", self.recipient.record, json.dumps(accounting) + "\r\n", filename=self.uid.encode("base64")[:-1] + ".txt" ) # Only store inbox item when reply is not sent or always for users store_inbox = store_inbox or self.recipient.record.getCUType() == "INDIVIDUAL" else: send_reply = False store_inbox = True # Let the store know that no time-range info has changed for a refresh (assuming that # no auto-accept changes were made) if hasattr(self.txn, "doing_attendee_refresh"): new_calendar.noInstanceIndexing = not send_reply # Update the attendee's copy of the event log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) new_resource = (yield self.writeCalendarResource(None, self.recipient_calendar_resource, new_calendar)) if send_reply: # Track outstanding auto-reply processing log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,)) ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat) # Build the schedule-changes XML element update_details = [] for rid, props_changed in sorted(rids.iteritems(), key=lambda x: x[0]): recurrence = [] if rid is None: recurrence.append(customxml.Master()) else: recurrence.append(customxml.RecurrenceID.fromString(rid.getText())) changes = [] for propName, paramNames in sorted(props_changed.iteritems(), key=lambda x: x[0]): params = tuple([customxml.ChangedParameter(name=param) for param in paramNames]) changes.append(customxml.ChangedProperty(*params, **{"name": propName})) recurrence.append(customxml.Changes(*changes)) update_details += (customxml.Recurrence(*recurrence),) changes = customxml.ScheduleChanges( customxml.DTStamp(), customxml.Action( customxml.Update(*update_details), ), ) # Refresh from another Attendee should not have Inbox item if hasattr(self.txn, "doing_attendee_refresh"): store_inbox = False result = (True, send_reply, store_inbox, changes,) else: # Request needs to be ignored log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid)) result = (True, True, False, None,) returnValue(result)