[CalendarServer-changes] [1846]
CalendarServer/branches/users/cdaboo/server2server-1842
source_changes at macosforge.org
source_changes at macosforge.org
Sun Sep 9 08:58:18 PDT 2007
Revision: 1846
http://trac.macosforge.org/projects/calendarserver/changeset/1846
Author: cdaboo at apple.com
Date: 2007-09-09 08:58:17 -0700 (Sun, 09 Sep 2007)
Log Message:
-----------
Refactored scheduling again to use classes so that base behavior can be defined, and then caldav
and server-to-server variants derived for clearer logic. Incoming server-to-server requests are
now supported with one caveat about authorization which is currently hard-coded to allow any
remote request to be authorized - this needs to be fixed.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/method/report_common.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist 2007-09-09 15:58:17 UTC (rev 1846)
@@ -331,8 +331,15 @@
<true/>
<!-- Server to server protocol -->
- <key>EnableServerToServer</key>
- <true/>
+ <key>ServerToServer</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Email Domain</key>
+ <string>example.com</string>
+ <key>HTTP Domain</key>
+ <string>example.com</string>
+ </dict>
<!--
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist 2007-09-09 15:58:17 UTC (rev 1846)
@@ -265,8 +265,15 @@
<true/>
<!-- Server to server protocol -->
- <key>EnableServerToServer</key>
- <true/>
+ <key>ServerToServer</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Email Domain</key>
+ <string></string>
+ <key>HTTP Domain</key>
+ <string></string>
+ </dict>
</dict>
</plist>
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py 2007-09-09 15:58:17 UTC (rev 1846)
@@ -147,7 +147,12 @@
#
"EnableDropBox" : False, # Calendar Drop Box
"EnableNotifications" : False, # Drop Box Notifications
- "EnableServerToServer": False, # Server-to-server protocol
+
+ "ServerToServer": {
+ "Enabled" : False, # Server-to-server protocol
+ "Email Domain" : "", # Domain for mailto calendar user addresses on this server
+ "HTTP Domain" : "", # Domain for http calendar user addresses on this server
+ },
#
# Implementation details
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/method/report_common.py 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/method/report_common.py 2007-09-09 15:58:17 UTC (rev 1846)
@@ -264,7 +264,8 @@
_namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)
def generateFreeBusyInfo(request, calresource, fbinfo, timerange, matchtotal,
- excludeuid=None, organizer=None, same_calendar_user=False):
+ excludeuid=None, organizer=None, same_calendar_user=False,
+ servertoserver=False):
"""
Run a free busy report on the specified calendar collection
accumulating the free busy info for later processing.
@@ -279,16 +280,19 @@
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 tyhe 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.
"""
# First check the privilege on this collection
- try:
- d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),)))
- yield d
- d.getResult()
- except AccessDeniedError:
- yield matchtotal
- return
+ # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+ if not servertoserver:
+ try:
+ d = waitForDeferred(calresource.checkPrivileges(request, (caldavxml.ReadFreeBusy(),)))
+ yield d
+ d.getResult()
+ except AccessDeniedError:
+ yield matchtotal
+ return
#
# What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
@@ -333,12 +337,14 @@
yield child
child = child.getResult()
- try:
- d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces))
- yield d
- d.getResult()
- except AccessDeniedError:
- continue
+ # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
+ if not servertoserver:
+ try:
+ d = waitForDeferred(child.checkPrivileges(request, (caldavxml.ReadFreeBusy(),), inherited_aces=filteredaces))
+ yield d
+ d.getResult()
+ except AccessDeniedError:
+ continue
calendar = calresource.iCalendar(name)
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py 2007-09-09 15:58:17 UTC (rev 1846)
@@ -40,7 +40,8 @@
from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.resource import isCalendarCollectionResource
-from twistedcaldav.schedule_common import doSchedulingViaPOST
+from twistedcaldav.schedule_common import CalDAVScheduler
+from twistedcaldav.schedule_common import ServerToServerScheduler
class CalendarSchedulingCollectionResource (CalDAVResource):
"""
@@ -192,8 +193,11 @@
yield x
x.getResult()
- # Do the POST processing treating this as a local schedule
- x = waitForDeferred(doSchedulingViaPOST(self, request, True))
+ # This is a local CALDAV scheduling operation.
+ scheduler = CalDAVScheduler(request, self)
+
+ # Do the POST processing treating
+ x = waitForDeferred(scheduler.doSchedulingViaPOST())
yield x
yield x.getResult()
@@ -265,7 +269,10 @@
yield x
x.getResult()
+ # This is a server-to-server scheduling operation.
+ scheduler = ServerToServerScheduler(request, self)
+
# Do the POST processing treating this as a non-local schedule
- x = waitForDeferred(doSchedulingViaPOST(self, request, False))
+ x = waitForDeferred(scheduler.doSchedulingViaPOST())
yield x
yield x.getResult()
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py 2007-09-09 15:58:17 UTC (rev 1846)
@@ -39,6 +39,7 @@
from twistedcaldav import caldavxml
from twistedcaldav import itip
from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.config import config
from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.ical import Component
from twistedcaldav.method import report_common
@@ -48,325 +49,698 @@
import md5
import time
- at deferredGenerator
-def doSchedulingViaPOST(self, request, local):
- """
- The CalDAV POST method.
+class Scheduler(object):
+
+ class CalendarUser(object):
+ def __init__(self, cuaddr):
+ self.cuaddr = cuaddr
+
+ class LocalCalendarUser(CalendarUser):
+ def __init__(self, cuaddr, principal, inbox=None, inboxURL=None):
+ self.cuaddr = cuaddr
+ self.principal = principal
+ self.inbox = inbox
+ self.inboxURL = inboxURL
- This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
- This allows for code that follows a 'linear' execution pattern rather than having to use nested
- L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
- issues which the other approach would have with large numbers of recipients.
- """
+ class RemoteCalendarUser(CalendarUser):
+ def __init__(self, cuaddr):
+ self.cuaddr = cuaddr
+ self.extractDomain()
+
+ def extractDomain(self):
+ if self.cuaddr.startswith("mailto:"):
+ splits = self.cuaddr[7:].split("?")
+ self.domain = splits[0].split("@")[1]
+ elif self.cuaddr.startswith("http://") or self.cuaddr.startswith("https://"):
+ splits = self.cuaddr.split(":")[0][2:].split("?")
+ self.domain = splits[0]
+ else:
+ self.domain = ""
- # Must be content-type text/calendar
- content_type = request.headers.getHeader("content-type")
- if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
- log.err("MIME type %s not allowed in calendar collection" % (content_type,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
+ class InvalidCalendarUser(CalendarUser):
+ pass
- # Must have Originator header
- originator = request.headers.getRawHeaders("originator")
- if originator is None or (len(originator) != 1):
- log.err("POST request must have Originator header")
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-specified")))
- else:
- originator = originator[0]
+
+ def __init__(self, request, resource):
+ self.request = request
+ self.resource = resource
+ self.originator = None
+ self.recipients = None
+ self.calendar = None
+ self.organizer = None
+
+ @deferredGenerator
+ def doSchedulingViaPOST(self):
+ """
+ The Scheduling POST operation.
+ """
+
+ # Do some extra authorization checks
+ self.checkAuthorization()
- # Verify that Originator is a valid calendar user (has an INBOX)
- oprincipal = self.principalForCalendarUserAddress(originator)
- if oprincipal is None:
- log.err("Could not find principal for originator: %s" % (originator,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ # Load various useful bits doing some basic checks on those
+ self.loadOriginator()
+ self.loadRecipients()
+ d = waitForDeferred(self.loadCalendar())
+ yield d
+ d.getResult()
- inboxURL = oprincipal.scheduleInboxURL()
- if inboxURL is None:
- log.err("Could not find inbox for originator: %s" % (originator,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ # Check validity of Originator header.
+ self.checkOriginator()
+
+ # Get recipient details.
+ d = waitForDeferred(self.checkRecipients())
+ yield d
+ d.getResult()
+
+ # Check calendar data.
+ self.checkCalendarData()
+
+ # Check validity of ORGANIZER
+ self.checkOrganizer()
+
+ # Do security checks (e.g. spoofing)
+ self.securityChecks()
+
+ # Do scheduling tasks
+ d = waitForDeferred(self.generateSchedulingResponse())
+ yield d
+ yield d.getResult()
- # Verify that Originator matches the authenticated user
- if davxml.Principal(davxml.HRef(oprincipal.principalURL())) != self.currentPrincipal(request):
- log.err("Originator: %s does not match authorized user: %s" % (originator, self.currentPrincipal(request).children[0],))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ def loadOriginator(self):
+ # Must have Originator header
+ originator = self.request.headers.getRawHeaders("originator")
+ if originator is None or (len(originator) != 1):
+ log.err("POST request must have Originator header")
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-specified")))
+ else:
+ self.originator = originator[0]
+
+ def loadRecipients(self):
+ # Get list of Recipient headers
+ rawrecipients = self.request.headers.getRawHeaders("recipient")
+ if rawrecipients is None or (len(rawrecipients) == 0):
+ log.err("POST request must have at least one Recipient header")
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-specified")))
+
+ # Recipient header may be comma separated list
+ self.recipients = []
+ for rawrecipient in rawrecipients:
+ for r in rawrecipient.split(","):
+ r = r.strip()
+ if len(r):
+ self.recipients.append(r)
+
+ @deferredGenerator
+ def loadCalendar(self):
+ # Must be content-type text/calendar
+ content_type = self.request.headers.getHeader("content-type")
+ if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
+ log.err("MIME type %s not allowed in calendar collection" % (content_type,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
+
+ # Parse the calendar object from the HTTP request stream
+ try:
+ d = waitForDeferred(Component.fromIStream(self.request.stream))
+ yield d
+ self.calendar = d.getResult()
+ except:
+ log.err("Error while handling POST: %s" % (Failure(),))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- # Get list of Recipient headers
- rawrecipients = request.headers.getRawHeaders("recipient")
- if rawrecipients is None or (len(rawrecipients) == 0):
- log.err("POST request must have at least one Recipient header")
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-specified")))
+ def checkAuthorization(self):
+ raise NotImplementedError
- # Recipient header may be comma separated list
- recipients = []
- for rawrecipient in rawrecipients:
- for r in rawrecipient.split(","):
- r = r.strip()
- if len(r):
- recipients.append(r)
+ def checkOriginator(self):
+ raise NotImplementedError
- timerange = TimeRange(start="20000101", end="20000102")
- recipients_state = {"OK":0, "BAD":0}
+ def checkRecipient(self):
+ raise NotImplementedError
- # Parse the calendar object from the HTTP request stream
- try:
- d = waitForDeferred(Component.fromIStream(request.stream))
- yield d
- calendar = d.getResult()
- except:
- log.err("Error while handling POST: %s" % (Failure(),))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-
+ def checkOrganizer(self):
+ raise NotImplementedError
+
+ def checkOrganizerAsOriginator(self):
+ raise NotImplementedError
+
+ def checkAttendeeAsOriginator(self):
+ raise NotImplementedError
+
+ def checkCalendarData(self):
# Must be a valid calendar
- try:
- calendar.validCalendarForCalDAV()
- except ValueError:
- log.err("POST request calendar component is not valid: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ try:
+ self.calendar.validCalendarForCalDAV()
+ except ValueError:
+ log.err("POST request calendar component is not valid: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+ # Must have a METHOD
+ if not self.calendar.isValidMethod():
+ log.err("POST request must have valid METHOD property in calendar component: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+ # Verify iTIP behaviour
+ if not self.calendar.isValidITIP():
+ log.err("POST request must have a calendar component that satisfies iTIP requirements: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- # Must have a METHOD
- if not calendar.isValidMethod():
- log.err("POST request must have valid METHOD property in calendar component: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ def checkForFreeBusy(self):
+ if (self.calendar.propertyValue("METHOD") == "REQUEST") and (self.calendar.mainType() == "VFREEBUSY"):
+ # Extract time range from VFREEBUSY object
+ vfreebusies = [v for v in self.calendar.subcomponents() if v.name() == "VFREEBUSY"]
+ if len(vfreebusies) != 1:
+ log.err("iTIP data is not valid for a VFREEBUSY request: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ dtstart = vfreebusies[0].getStartDateUTC()
+ dtend = vfreebusies[0].getEndDateUTC()
+ if dtstart is None or dtend is None:
+ log.err("VFREEBUSY start/end not valid: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ self.timerange = TimeRange(start="20000101T000000Z", end="20070102T000000Z")
+ self.timerange.start = dtstart
+ self.timerange.end = dtend
- # Verify iTIP behaviour
- if not calendar.isValidITIP():
- log.err("POST request must have a calendar component that satisfies iTIP requirements: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ # Look for maksed UID
+ self.excludeuid = self.calendar.getMaskUID()
+
+ # Do free busy operation
+ return True
+ else:
+ # Do regular invite (fan-out)
+ return False
+
+ def securityChecks(self):
+ raise NotImplementedError
- # Verify that the ORGANIZER's cu address maps to the request.uri
- outboxURL = None
- organizer = calendar.getOrganizer()
- if organizer is not None:
- oprincipal = self.principalForCalendarUserAddress(organizer)
- if oprincipal is not None:
- outboxURL = oprincipal.scheduleOutboxURL()
- if outboxURL is None:
- log.err("ORGANIZER in calendar data is not valid: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ @staticmethod
+ def isCalendarUserAddressInMyDomain(cuaddr):
+ """
+ Check whether the supplied calendar user address corresponds to one that ought to be within
+ this server's domain.
+
+ For now we will try to match email and http domains against ones in our config.
+
+ @param cuaddr: the calendar user address to check.
+ @type cuaddr: C{str}
+
+ @return: C{True} if the address is within the server's domain,
+ C{False} otherwise.
+ """
+
+ if cuaddr.startswith("mailto:"):
+ splits = cuaddr[7:].split("?")
+ domain = config.ServerToServer["Email Domain"]
+ if not domain:
+ domain = config.ServerHostName
+ return splits[0].endswith(domain)
+ elif cuaddr.startswith("http://") or cuaddr.startswith("https://"):
+ splits = cuaddr.split(":")[0][2:].split("?")
+ domain = config.ServerToServer["HTTP Domain"]
+ if not domain:
+ domain = config.ServerHostName
+ return splits[0].endswith(domain)
+ else:
+ return False
+
+ @deferredGenerator
+ def generateSchedulingResponse(self):
- # Prevent spoofing of ORGANIZER with specific METHODs
- if (calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER")) and (outboxURL != request.uri):
- log.err("ORGANIZER in calendar data does not match owner of Outbox: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ # For free-busy do immediate determination of iTIP result rather than fan-out
+ freebusy = self.checkForFreeBusy()
+
+ # Prepare for multiple responses
+ responses = ScheduleResponseQueue("POST", responsecode.OK)
+
+ # Extract the ORGANIZER property and UID value from the calendar data for use later
+ organizerProp = self.calendar.getOrganizerProperty()
+ uid = self.calendar.resourceUID()
+
+ # Loop over each recipient and do appropriate action.
+ autoresponses = []
+ for recipient in self.recipients:
+
+ if isinstance(recipient, Scheduler.InvalidCalendarUser):
+ err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-exists")))
+ responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.7;Invalid Calendar User")
+
+ # Process next recipient
+ continue
+ elif isinstance(recipient, Scheduler.RemoteCalendarUser):
+ # TODO: support remote recipients when server-to-server is enabled
+ err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-exists")))
+ responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.7;Invalid Calendar User")
+
+ # Process next recipient
+ continue
+ elif isinstance(recipient, Scheduler.LocalCalendarUser):
+ #
+ # Check access controls
+ #
+ if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+ try:
+ d = waitForDeferred(recipient.inbox.checkPrivileges(self.request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef(self.organizer.principal.principalURL()))))
+ yield d
+ d.getResult()
+ except AccessDeniedError:
+ log.err("Could not access Inbox for recipient: %s" % (recipient.cuaddr,))
+ err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-permisions")))
+ responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+
+ # Process next recipient
+ continue
+ else:
+ # TODO: need to figure out how best to do server-to-server authorization.
+ # First thing would be to checkk for DAV:unauthenticated privilege.
+ # Next would be to allow the calendar user address of the organizer/originator to be used
+ # as a principal.
+ pass
+
+ # Different behaviour for free-busy vs regular invite
+ if freebusy:
+ d = waitForDeferred(self.generateLocalFreeBusyResponse(recipient, responses, organizerProp, uid))
+ else:
+ d = waitForDeferred(self.generateLocalResponse(recipient, responses, autoresponses))
+ yield d
+ d.getResult()
+
+ # Now we have to do auto-respond
+ if len(autoresponses) != 0:
+ # First check that we have a method that we can auto-respond to
+ if not itip.canAutoRespond(self.calendar):
+ autoresponses = []
+
+ # Now do the actual auto response
+ for principal, inbox, child in autoresponses:
+ # Add delayed reactor task to handle iTIP responses
+ reactor.callLater(0.0, itip.handleRequest, *(self.request, principal, inbox, self.calendar.duplicate(), child)) #@UndefinedVariable
+
+ # Return with final response if we are done
+ yield responses.response()
+
+ @deferredGenerator
+ def generateLocalResponse(self, recipient, responses, autoresponses):
+ # Hash the iCalendar data for use as the last path element of the URI path
+ calendar_str = str(self.calendar)
+ name = md5.new(calendar_str + str(time.time()) + recipient.inbox.fp.path).hexdigest() + ".ics"
+
+ # Get a resource for the new item
+ childURL = joinURL(recipient.inboxURL, name)
+ child = waitForDeferred(self.request.locateResource(childURL))
+ yield child
+ child = child.getResult()
- # Prevent spoofing when doing reply-like METHODs
- if calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
- # Verify that there is a single ATTENDEE property and that the Originator has permission
- # to send on behalf of that ATTENDEE
- attendees = calendar.getAttendees()
+ # Copy calendar to inbox (doing fan-out)
+ try:
+ d = waitForDeferred(
+ maybeDeferred(
+ storeCalendarObjectResource,
+ request=self.request,
+ sourcecal = False,
+ destination = child,
+ destination_uri = childURL,
+ calendardata = calendar_str,
+ destinationparent = recipient.inbox,
+ destinationcal = True,
+ isiTIP = True
+ )
+ )
+ yield d
+ d.getResult()
+ responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
+
+ # Store CALDAV:originator property
+ child.writeDeadProperty(caldavxml.Originator(davxml.HRef(self.originator.cuaddr)))
+
+ # Store CALDAV:recipient property
+ child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient.cuaddr)))
+
+ # Look for auto-schedule option
+ if recipient.principal.autoSchedule():
+ autoresponses.append((recipient.principal, recipient.inbox, child))
+
+ yield True
+ except:
+ log.err("Could not store data in Inbox : %s" % (recipient.inbox,))
+ err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
+ responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+ yield False
- # Must have only one
- if len(attendees) != 1:
- log.err("ATTENDEE list in calendar data is wrong: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
-
- # Attendee's Outbox MUST be the request URI
- aoutboxURL = None
- aprincipal = self.principalForCalendarUserAddress(attendees[0])
- if aprincipal is not None:
- aoutboxURL = aprincipal.scheduleOutboxURL()
- if aoutboxURL is None or aoutboxURL != request.uri:
- log.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ @deferredGenerator
+ def generateLocalFreeBusyResponse(self, recipient, responses, organizerProp, uid):
- # For free-busy do immediate determination of iTIP result rather than fan-out
- if (calendar.propertyValue("METHOD") == "REQUEST") and (calendar.mainType() == "VFREEBUSY"):
- # Extract time range from VFREEBUSY object
- vfreebusies = [v for v in calendar.subcomponents() if v.name() == "VFREEBUSY"]
- if len(vfreebusies) != 1:
- log.err("iTIP data is not valid for a VFREEBUSY request: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- dtstart = vfreebusies[0].getStartDateUTC()
- dtend = vfreebusies[0].getEndDateUTC()
- if dtstart is None or dtend is None:
- log.err("VFREEBUSY start/end not valid: %s" % (calendar,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- timerange.start = dtstart
- timerange.end = dtend
+ # Extract the ATTENDEE property matching current recipient from the calendar data
+ cuas = recipient.principal.calendarUserAddresses()
+ attendeeProp = self.calendar.getAttendeeProperty(cuas)
- # Look for maksed UID
- excludeuid = calendar.getMaskUID()
+ remote = isinstance(self.organizer, Scheduler.RemoteCalendarUser)
- # Do free busy operation
- freebusy = True
- else:
- # Do regular invite (fan-out)
- freebusy = False
+ # Find the current recipients calendar-free-busy-set
+ fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
+ yield fbset
+ fbset = fbset.getResult()
- # Prepare for multiple responses
- responses = ScheduleResponseQueue("POST", responsecode.OK)
+ # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+ fbinfo = ([], [], [])
+
+ try:
+ # Process the availability property from the Inbox.
+ has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
+ yield has_prop
+ has_prop = has_prop.getResult()
+ if has_prop:
+ availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
+ yield availability
+ availability = availability.getResult()
+ availability = availability.calendar()
+ report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
- # Extract the ORGANIZER property and UID value from the calendar data for use later
- organizerProp = calendar.getOrganizerProperty()
- uid = calendar.resourceUID()
+ # Check to see if the recipient is the same calendar user as the organizer.
+ # Needed for masked UID stuff.
+ if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+ same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
+ else:
+ same_calendar_user = False
- # Loop over each recipient and do appropriate action.
- autoresponses = []
- for recipient in recipients:
- # Get the principal resource for this recipient
- principal = self.principalForCalendarUserAddress(recipient)
+ # Now process free-busy set calendars
+ matchtotal = 0
+ for calURL in fbset:
+ cal = waitForDeferred(self.request.locateResource(calURL))
+ yield cal
+ cal = cal.getResult()
+ if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+ # We will ignore missing calendars. If the recipient has failed to
+ # properly manage the free busy set that should not prevent us from working.
+ continue
+
+ matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
+ self.request,
+ cal,
+ fbinfo,
+ self.timerange,
+ matchtotal,
+ excludeuid=self.excludeuid,
+ organizer=self.organizer.cuaddr,
+ same_calendar_user=same_calendar_user,
+ servertoserver=remote))
+ yield matchtotal
+ matchtotal = matchtotal.getResult()
+
+ # Build VFREEBUSY iTIP reply for this recipient
+ fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
- # Map recipient to their inbox
- inbox = None
- if principal is None:
- log.err("No principal for calendar user address: %s" % (recipient,))
+ responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
+
+ yield True
+ except:
+ log.err("Could not determine free busy information: %s" % (recipient.cuaddr,))
+ err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
+ responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+
+ yield False
+
+ def generateRemoteResponse(self):
+ raise NotImplementedError
+
+ def generateRemoteFreeBusyResponse(self):
+ raise NotImplementedError
+
+class CalDAVScheduler(Scheduler):
+
+ def checkAuthorization(self):
+ # Must have an authenticated user
+ if self.resource.currentPrincipal(self.request) == davxml.Principal(davxml.Unauthenticated()):
+ log.err("Unauthenticated originators not allowed: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+ def checkOriginator(self):
+ """
+ Check the validity of the Originator header. Extract the corresponding principal.
+ """
+
+ # Verify that Originator is a valid calendar user
+ originator_principal = self.resource.principalForCalendarUserAddress(self.originator)
+ if originator_principal is None:
+ # Local requests MUST have a principal.
+ log.err("Could not find principal for originator: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
else:
- inboxURL = principal.scheduleInboxURL()
- if inboxURL:
- inbox = waitForDeferred(request.locateResource(inboxURL))
- yield inbox
- inbox = inbox.getResult()
+ # Must have a valid Inbox.
+ inboxURL = originator_principal.scheduleInboxURL()
+ if inboxURL is None:
+ log.err("Could not find inbox for originator: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+ # Verify that Originator matches the authenticated user.
+ authn_principal = self.resource.currentPrincipal(self.request)
+ if davxml.Principal(davxml.HRef(originator_principal.principalURL())) != authn_principal:
+ log.err("Originator: %s does not match authorized user: %s" % (self.originator, authn_principal.children[0],))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+
+ self.originator = Scheduler.LocalCalendarUser(self.originator, originator_principal)
+
+ @deferredGenerator
+ def checkRecipients(self):
+ """
+ Check the validity of the Recipient header values. Map these into local or
+ remote CalendarUsers.
+ """
+
+ results = []
+ for recipient in self.recipients:
+ # Get the principal resource for this recipient
+ principal = self.resource.principalForCalendarUserAddress(recipient)
+
+ # If no principal we may have a remote recipient but we should check whether
+ # the address is one that ought to be on our server and treat that as a missing
+ # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+ if principal is None:
+ if self.isCalendarUserAddressInMyDomain(recipient):
+ log.err("No principal for calendar user address: %s" % (recipient,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
+ elif not config.ServerToServer["Enabled"]:
+ log.err("Unknown calendar user address: %s" % (recipient,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
+ else:
+ results.append(Scheduler.RemoteCalendarUser(recipient))
else:
- log.err("No schedule inbox for principal: %s" % (principal,))
+ # Map recipient to their inbox
+ inbox = None
+ inboxURL = principal.scheduleInboxURL()
+ if inboxURL:
+ inbox = waitForDeferred(self.request.locateResource(inboxURL))
+ yield inbox
+ inbox = inbox.getResult()
- if inbox is None:
- err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-exists")))
- responses.add(recipient, Failure(exc_value=err), reqstatus="3.7;Invalid Calendar User")
- recipients_state["BAD"] += 1
+ if inbox:
+ results.append(Scheduler.LocalCalendarUser(recipient, principal, inbox, inboxURL))
+ else:
+ log.err("No schedule inbox for principal: %s" % (principal,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
- # Process next recipient
- continue
+ self.recipients = results
+
+ def checkOrganizer(self):
+ """
+ Check the validity of the ORGANIZER value. ORGANIZER must be local.
+ """
+
+ # Verify that the ORGANIZER's cu address maps to a valid user
+ organizer = self.calendar.getOrganizer()
+ if organizer:
+ orgprincipal = self.resource.principalForCalendarUserAddress(organizer)
+ if orgprincipal:
+ outboxURL = orgprincipal.scheduleOutboxURL()
+ if outboxURL:
+ self.organizer = Scheduler.LocalCalendarUser(organizer, orgprincipal)
+ else:
+ log.err("No outbox for ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ else:
+ log.err("No principal for ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
else:
- #
- # Check access controls
- #
- try:
- d = waitForDeferred(inbox.checkPrivileges(request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef(oprincipal.principalURL()))))
- yield d
- d.getResult()
- except AccessDeniedError:
- log.err("Could not access Inbox for recipient: %s" % (recipient,))
- err = HTTPError(ErrorResponse(responsecode.NOT_FOUND, (caldav_namespace, "recipient-permisions")))
- responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
- recipients_state["BAD"] += 1
-
- # Process next recipient
- continue
+ log.err("ORGANIZER missing in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
- # Different behaviour for free-busy vs regular invite
- if freebusy:
- # Extract the ATTENDEE property matching current recipient from the calendar data
- cuas = principal.calendarUserAddresses()
- attendeeProp = calendar.getAttendeeProperty(cuas)
+ def checkOrganizerAsOriginator(self):
+ # Make sure that the ORGANIZER's Outbox is the request URI
+ if self.organizer.principal.scheduleOutboxURL() != self.request.uri:
+ log.err("Wrong outbox for ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+
+ def checkAttendeeAsOriginator(self):
+ """
+ Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
+ Only local attendees are allowed for message originating from this server.
+ """
- # Find the current recipients calendar-free-busy-set
- fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
- yield fbset
- fbset = fbset.getResult()
-
- # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
- fbinfo = ([], [], [])
+ # Verify that there is a single ATTENDEE property
+ attendees = self.calendar.getAttendees()
+
+ # Must have only one
+ if len(attendees) != 1:
+ log.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ attendee = attendees[0]
+
+ # Attendee's Outbox MUST be the request URI
+ aprincipal = self.resource.principalForCalendarUserAddress(attendee)
+ if aprincipal:
+ aoutboxURL = aprincipal.scheduleOutboxURL()
+ if aoutboxURL is None or aoutboxURL != self.request.uri:
+ log.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ else:
+ log.err("Unkown ATTENDEE in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+
+ def securityChecks(self):
+ """
+ Check that the orginator has the appropriate rights to send this type of iTIP message.
+ """
+
+ # Prevent spoofing of ORGANIZER with specific METHODs when local
+ if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+ self.checkOrganizerAsOriginator()
+
+ # Prevent spoofing when doing reply-like METHODs
+ elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+ self.checkAttendeeAsOriginator()
- try:
- # Process the availability property from the Inbox.
- has_prop = waitForDeferred(inbox.hasProperty((calendarserver_namespace, "calendar-availability"), request))
- yield has_prop
- has_prop = has_prop.getResult()
- if has_prop:
- availability = waitForDeferred(inbox.readProperty((calendarserver_namespace, "calendar-availability"), request))
- yield availability
- availability = availability.getResult()
- availability = availability.calendar()
- report_common.processAvailabilityFreeBusy(availability, fbinfo, timerange)
+ else:
+ log.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- # Check to see if the recipient is the same calendar user as the organizer.
- # Needed for masked UID stuff.
- same_calendar_user = oprincipal.principalURL() == principal.principalURL()
+class ServerToServerScheduler(Scheduler):
- # Now process free-busy set calendars
- matchtotal = 0
- for calURL in fbset:
- cal = waitForDeferred(request.locateResource(calURL))
- yield cal
- cal = cal.getResult()
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
- # We will ignore missing calendars. If the recipient has failed to
- # properly manage the free busy set that should not prevent us from working.
- continue
-
- matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
- request,
- cal,
- fbinfo,
- timerange,
- matchtotal,
- excludeuid=excludeuid,
- organizer=organizer,
- same_calendar_user=same_calendar_user))
- yield matchtotal
- matchtotal = matchtotal.getResult()
-
- # Build VFREEBUSY iTIP reply for this recipient
- fbresult = report_common.buildFreeBusyResult(fbinfo, timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
+ def checkAuthorization(self):
+ # Must have an unauthenticated user
+ if self.resource.currentPrincipal(self.request) != davxml.Principal(davxml.Unauthenticated()):
+ log.err("Authenticated originators not allowed: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
- responses.add(recipient, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
- recipients_state["OK"] += 1
+ def checkOriginator(self):
+ """
+ Check the validity of the Originator header.
+ """
+
+ # For remote requests we do not allow the originator to be a local user or one within our domain
+ originator_principal = self.resource.principalForCalendarUserAddress(self.originator)
+ if originator_principal or self.isCalendarUserAddressInMyDomain(self.originator):
+ log.err("Cannot use originator that is on this server: %s" % (self.originator,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+ else:
+ self.originator = Scheduler.RemoteCalendarUser(self.originator)
+
+ @deferredGenerator
+ def checkRecipients(self):
+ """
+ Check the validity of the Recipient header values. These must all be local as there
+ is no concept of server-to-server relaying.
+ """
+
+ results = []
+ for recipient in self.recipients:
+ # Get the principal resource for this recipient
+ principal = self.resource.principalForCalendarUserAddress(recipient)
- except:
- log.err("Could not determine free busy information: %s" % (recipient,))
- err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
- responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
- recipients_state["BAD"] += 1
-
+ # If no principal we may have a remote recipient but we should check whether
+ # the address is one that ought to be on our server and treat that as a missing
+ # user. Also if server-to-server is not enabled then remote addresses are not allowed.
+ if principal is None:
+ if self.isCalendarUserAddressInMyDomain(recipient):
+ log.err("No principal for calendar user address: %s" % (recipient,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
+ else:
+ log.err("Unknown calendar user address: %s" % (recipient,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
else:
- # Hash the iCalendar data for use as the last path element of the URI path
- name = md5.new(str(calendar) + str(time.time()) + inbox.fp.path).hexdigest() + ".ics"
-
- # Get a resource for the new item
- childURL = joinURL(inboxURL, name)
- child = waitForDeferred(request.locateResource(childURL))
- yield child
- child = child.getResult()
+ # Map recipient to their inbox
+ inbox = None
+ inboxURL = principal.scheduleInboxURL()
+ if inboxURL:
+ inbox = waitForDeferred(self.request.locateResource(inboxURL))
+ yield inbox
+ inbox = inbox.getResult()
+
+ if inbox:
+ results.append(Scheduler.LocalCalendarUser(recipient, principal, inbox, inboxURL))
+ else:
+ log.err("No schedule inbox for principal: %s" % (principal,))
+ results.append(Scheduler.InvalidCalendarUser(recipient))
- # Copy calendar to inbox (doing fan-out)
- d = waitForDeferred(
- maybeDeferred(
- storeCalendarObjectResource,
- request=request,
- sourcecal = False,
- destination = child,
- destination_uri = childURL,
- calendardata = str(calendar),
- destinationparent = inbox,
- destinationcal = True,
- isiTIP = True
- )
- )
- yield d
- try:
- d.getResult()
- responses.add(recipient, responsecode.OK, reqstatus="2.0;Success")
- recipients_state["OK"] += 1
-
- # Store CALDAV:originator property
- child.writeDeadProperty(caldavxml.Originator(davxml.HRef(originator)))
-
- # Store CALDAV:recipient property
- child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient)))
-
- # Look for auto-schedule option
- if principal.autoSchedule():
- autoresponses.append((principal, inbox, child))
- except:
- log.err("Could not store data in Inbox : %s" % (inbox,))
- err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
- responses.add(recipient, Failure(exc_value=err), reqstatus="3.8;No authority")
- recipients_state["BAD"] += 1
+ self.recipients = results
- # Now we have to do auto-respond
- if len(autoresponses) != 0:
- # First check that we have a method that we can auto-respond to
- if not itip.canAutoRespond(calendar):
- autoresponses = []
+ def checkOrganizer(self):
+ """
+ Delay ORGANIZER check until we know what their role is.
+ """
+ pass
+
+ def checkOrganizerAsOriginator(self):
+ """
+ Check the validity of the ORGANIZER value. ORGANIZER must not be local.
+ """
- # Now do the actual auto response
- for principal, inbox, child in autoresponses:
- # Add delayed reactor task to handle iTIP responses
- reactor.callLater(0.0, itip.handleRequest, *(request, principal, inbox, calendar.duplicate(), child)) #@UndefinedVariable
- #reactor.callInThread(itip.handleRequest, *(request, principal, inbox, calendar.duplicate(), child)) #@UndefinedVariable
+ # Verify that the ORGANIZER's cu address does not map to a valid user
+ organizer = self.calendar.getOrganizer()
+ if organizer:
+ orgprincipal = self.resource.principalForCalendarUserAddress(organizer)
+ if orgprincipal:
+ log.err("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ elif self.isCalendarUserAddressInMyDomain(organizer):
+ log.err("Unsupported ORGANIZER in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
+ else:
+ self.organizer = Scheduler.RemoteCalendarUser(organizer)
+ else:
+ log.err("ORGANIZER missing in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "organizer-allowed")))
- # Return with final response if we are done
- yield responses.response()
+ def checkAttendeeAsOriginator(self):
+ """
+ Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
+ Only local attendees are allowed for message originating from this server.
+ """
+
+ # Verify that there is a single ATTENDEE property
+ attendees = self.calendar.getAttendees()
+
+ # Must have only one
+ if len(attendees) != 1:
+ log.err("Wrong number of ATTENDEEs in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ attendee = attendees[0]
+
+ # Attendee cannot be local.
+ aprincipal = self.resource.principalForCalendarUserAddress(attendee)
+ if aprincipal:
+ log.err("Invalid ATTENDEE in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+ elif self.isCalendarUserAddressInMyDomain(attendee):
+ log.err("Unkown ATTENDEE in calendar data: %s" % (self.calendar,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
+
+ # TODO: in this case we should check that the ORGANIZER is the sole recipient.
+ def securityChecks(self):
+ """
+ Check that the orginator has the appropriate rights to send this type of iTIP message.
+ """
+
+ # Prevent spoofing of ORGANIZER with specific METHODs when local
+ if self.calendar.propertyValue("METHOD") in ("PUBLISH", "REQUEST", "ADD", "CANCEL", "DECLINECOUNTER"):
+ self.checkOrganizerAsOriginator()
+
+ # Prevent spoofing when doing reply-like METHODs
+ elif self.calendar.propertyValue("METHOD") in ("REPLY", "COUNTER", "REFRESH"):
+ self.checkAttendeeAsOriginator()
+
+ else:
+ log.err("Unknown iTIP METHOD for security checks: %s" % (self.calendar.propertyValue("METHOD"),))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
class ScheduleResponseResponse (Response):
"""
ScheduleResponse L{Response} object.
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py 2007-09-06 18:50:24 UTC (rev 1845)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py 2007-09-09 15:58:17 UTC (rev 1846)
@@ -415,7 +415,7 @@
root.putChild('principals', principalCollection)
root.putChild('calendars', calendarCollection)
- if config.EnableServerToServer:
+ if config.ServerToServer["Enabled"]:
log.msg("Setting up server-to-server resource: %r" % (self.servertoserverResourceClass,))
servertoserver = self.servertoserverResourceClass(
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070909/0ce53457/attachment.html
More information about the calendarserver-changes
mailing list