[CalendarServer-changes] [1844] CalendarServer/branches/users/cdaboo/server2server-1842

source_changes at macosforge.org source_changes at macosforge.org
Thu Sep 6 09:07:15 PDT 2007


Revision: 1844
          http://trac.macosforge.org/projects/calendarserver/changeset/1844
Author:   cdaboo at apple.com
Date:     2007-09-06 09:07:14 -0700 (Thu, 06 Sep 2007)

Log Message:
-----------
Created a new top-level resource /inbox that will act as the server-to-server inbox. Refactored scheduling code to start pulling
out the core scheduling features into more manageable chunks so that the slightly different behaviors of CalDAV and Server-to-server
can be easily handled without code duplication. The server-to-server behavior is controlled via a config option.

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/customxml.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.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 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist	2007-09-06 16:07:14 UTC (rev 1844)
@@ -330,7 +330,11 @@
   <key>EnableNotifications</key>
   <true/>
 
+  <!-- Server to server protocol -->
+  <key>EnableServerToServer</key>
+  <true/>
 
+
   <!--
     Twisted
   -->

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist	2007-09-06 16:07:14 UTC (rev 1844)
@@ -264,6 +264,9 @@
   <key>EnableNotifications</key>
   <true/>
 
+  <!-- Server to server protocol -->
+  <key>EnableServerToServer</key>
+  <true/>
 
 </dict>
 </plist>

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -145,8 +145,9 @@
     #
     # Non-standard CalDAV extensions
     #
-    "EnableDropBox"      : False, # Calendar Drop Box
-    "EnableNotifications": False, # Drop Box Notifications
+    "EnableDropBox"       : False, # Calendar Drop Box
+    "EnableNotifications" : False, # Drop Box Notifications
+    "EnableServerToServer": False, # Server-to-server protocol
 
     #
     # Implementation details

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -254,6 +254,14 @@
 
         return found
 
+class ServerToServerInbox (davxml.WebDAVEmptyElement):
+    """
+    Denotes the resourcetype of a server-to_server Inbox.
+    (CalDAV-s2s-xx, section x.x.x)
+    """
+    namespace = calendarserver_namespace
+    name = "server-to-server-inbox"
+
 ##
 # Extensions to davxml.ResourceType
 ##
@@ -263,3 +271,4 @@
 davxml.ResourceType.notifications = davxml.ResourceType(davxml.Collection(), Notifications())
 davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
 davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
+davxml.ResourceType.servertoserverinbox = davxml.ResourceType(ServerToServerInbox())

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -25,32 +25,23 @@
     "ScheduleOutboxResource",
 ]
 
-from twisted.internet import reactor
-from twisted.internet.defer import deferredGenerator, maybeDeferred, succeed, waitForDeferred
-from twisted.python import log
-from twisted.python.failure import Failure
+from twisted.internet.defer import deferredGenerator, succeed, waitForDeferred
 from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, Response
-from twisted.web2.http_headers import MimeType
 from twisted.web2.dav import davxml
-from twisted.web2.dav.http import ErrorResponse, errorForFailure, messageForFailure, statusForFailure
-from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.util import joinURL
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http_headers import MimeType
 
 from twistedcaldav import caldavxml
-from twistedcaldav import itip
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.ical import Component
-from twistedcaldav.method import report_common
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.resource import isCalendarCollectionResource
+from twistedcaldav.schedule_common import doSchedulingViaPOST
 
-import md5
-import time
-
 class CalendarSchedulingCollectionResource (CalDAVResource):
     """
     CalDAV principal resource.
@@ -201,398 +192,80 @@
         yield x
         x.getResult()
 
-        # 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")))
-    
-        # 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]
-    
-        # 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")))
+        # Do the POST processing treating this as a local schedule
+        x = waitForDeferred(doSchedulingViaPOST(self, request, True))
+        yield x
+        yield x.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")))
-    
-        # 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")))
 
-        # 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")))
+class ScheduleServerToServerResource (CalDAVResource):
+    """
+    Server-to-server schedule Inbox resource.
 
-        # 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)
+    Extends L{DAVResource} to provide Server-to-server functionality.
+    """
 
-        timerange = TimeRange(start="20000101", end="20000102")
-        recipients_state = {"OK":0, "BAD":0}
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
 
-        # 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")))
- 
-        # 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")))
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
 
-        # 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")))
-        
-        # 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")))
-    
-        # 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")))
+        self.parent = parent
 
-        # 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")))
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule for all principals (includes anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.All()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(caldavxml.Schedule()),
+                ),
+                davxml.Protected(),
+            ),
+        )
 
-        # 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()
-        
-            # 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")))
+    def resourceType(self):
+        return davxml.ResourceType.servertoserverinbox
 
-        # 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
+    def isCollection(self):
+        return False
 
-            # Look for maksed UID
-            excludeuid = calendar.getMaskUID()
+    def isCalendarCollection(self):
+        return False
 
-            # Do free busy operation
-            freebusy = True
-        else:
-            # Do regular invite (fan-out)
-            freebusy = False
+    def isPseudoCalendarCollection(self):
+        return False
 
-        # Prepare for multiple responses
-        responses = ScheduleResponseQueue("POST", responsecode.OK)
-    
-        # Extract the ORGANIZER property and UID value from the calendar data for use later
-        organizerProp = calendar.getOrganizerProperty()
-        uid = calendar.resourceUID()
+    def render(self, request):
+        output = """<html>
+<head>
+<title>Server To Server Inbox Resource</title>
+</head>
+<body>
+<h1>Server To Server Inbox Resource.</h1>
+</body
+</html>"""
 
-        # Loop over each recipient and do appropriate action.
-        autoresponses = []
-        for recipient in recipients:
-            # Get the principal resource for this recipient
-            principal = self.principalForCalendarUserAddress(recipient)
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
 
-            # Map recipient to their inbox
-            inbox = None
-            if principal is None:
-                log.err("No principal for calendar user address: %s" % (recipient,))
-            else:
-                inboxURL = principal.scheduleInboxURL()
-                if inboxURL:
-                    inbox = waitForDeferred(request.locateResource(inboxURL))
-                    yield inbox
-                    inbox = inbox.getResult()
-                else:
-                    log.err("No schedule inbox for principal: %s" % (principal,))
-
-            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
-            
-                # Process next recipient
-                continue
-            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
-    
-                # 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)
-            
-                    # 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 = ([], [], [])
-                
-                    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)
-
-                        # 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()
-
-                        # 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)
-
-                        responses.add(recipient, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
-                        recipients_state["OK"] += 1
-                
-                    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
-                
-                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()
-            
-                    # 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
-
-        # 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 = []
-            
-        # 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
-
-        # Return with final response if we are done
-        yield responses.response()
-
-class ScheduleResponseResponse (Response):
-    """
-    ScheduleResponse L{Response} object.
-    Renders itself as a CalDAV:schedule-response XML document.
-    """
-    def __init__(self, xml_responses, location=None):
+    @deferredGenerator
+    def http_POST(self, request):
         """
-        @param xml_responses: an interable of davxml.Response objects.
-        @param location:      the value of the location header to return in the response,
-                              or None.
+        The server-to-server POST method.
         """
 
-        Response.__init__(self, code=responsecode.OK,
-                          stream=caldavxml.ScheduleResponse(*xml_responses).toxml())
+        # Check authentication and access controls
+        x = waitForDeferred(self.authorize(request, (caldavxml.Schedule(),)))
+        yield x
+        x.getResult()
 
-        self.headers.setHeader("content-type", MimeType("text", "xml"))
-    
-        if location is not None:
-            self.headers.setHeader("location", location)
-
-class ScheduleResponseQueue (object):
-    """
-    Stores a list of (typically error) responses for use in a
-    L{ScheduleResponse}.
-    """
-    def __init__(self, method, success_response):
-        """
-        @param method: the name of the method generating the queue.
-        @param success_response: the response to return in lieu of a
-            L{ScheduleResponse} if no responses are added to this queue.
-        """
-        self.responses         = []
-        self.method            = method
-        self.success_response  = success_response
-        self.location          = None
-
-    def setLocation(self, location):
-        """
-        @param location:      the value of the location header to return in the response,
-                              or None.
-        """
-        self.location = location
-
-    def add(self, recipient, what, reqstatus=None, calendar=None):
-        """
-        Add a response.
-        @param recipient: the recipient for this response.
-        @param what: a status code or a L{Failure} for the given recipient.
-        @param status: the iTIP request-status for the given recipient.
-        @param calendar: the calendar data for the given recipient response.
-        """
-        if type(what) is int:
-            code    = what
-            error   = None
-            message = responsecode.RESPONSES[code]
-        elif isinstance(what, Failure):
-            code    = statusForFailure(what)
-            error   = errorForFailure(what)
-            message = messageForFailure(what)
-        else:
-            raise AssertionError("Unknown data type: %r" % (what,))
-
-        if code > 400: # Error codes only
-            log.err("Error during %s for %s: %s" % (self.method, recipient, message))
-
-        children = []
-        children.append(caldavxml.Recipient(davxml.HRef.fromString(recipient)))
-        children.append(caldavxml.RequestStatus(reqstatus))
-        if calendar is not None:
-            children.append(caldavxml.CalendarData.fromCalendar(calendar))
-        if error is not None:
-            children.append(error)
-        if message is not None:
-            children.append(davxml.ResponseDescription(message))
-        self.responses.append(caldavxml.Response(*children))
-
-    def response(self):
-        """
-        Generate a L{ScheduleResponseResponse} with the responses contained in the
-        queue or, if no such responses, return the C{success_response} provided
-        to L{__init__}.
-        @return: the response.
-        """
-        if self.responses:
-            return ScheduleResponseResponse(self.responses, self.location)
-        else:
-            return self.success_response
+        # Do the POST processing treating this as a non-local schedule
+        x = waitForDeferred(doSchedulingViaPOST(self, request, False))
+        yield x
+        yield x.getResult()

Added: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -0,0 +1,456 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+CalDAV/Server-to-Server scheduling behavior.
+"""
+
+__all__ = [
+    "doSchedulingViaPOST",
+]
+
+from twisted.internet import reactor
+from twisted.internet.defer import deferredGenerator, maybeDeferred, waitForDeferred
+from twisted.python import log
+from twisted.python.failure import Failure
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, Response
+from twisted.web2.http_headers import MimeType
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse, errorForFailure, messageForFailure, statusForFailure
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.dav.util import joinURL
+
+from twistedcaldav import caldavxml
+from twistedcaldav import itip
+from twistedcaldav.caldavxml import caldav_namespace, TimeRange
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import Component
+from twistedcaldav.method import report_common
+from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.resource import isCalendarCollectionResource
+
+import md5
+import time
+
+ at deferredGenerator
+def doSchedulingViaPOST(self, request, local):
+    """
+    The CalDAV POST method.
+
+    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.
+    """
+
+    # 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")))
+
+    # 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]
+
+    # 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")))
+
+    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")))
+
+    # 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")))
+
+    # 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")))
+
+    # 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)
+
+    timerange = TimeRange(start="20000101", end="20000102")
+    recipients_state = {"OK":0, "BAD":0}
+
+    # 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")))
+ 
+        # 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")))
+
+    # 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")))
+    
+    # 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")))
+
+    # 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")))
+
+    # 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")))
+
+    # 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()
+    
+        # 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")))
+
+    # 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
+
+        # Look for maksed UID
+        excludeuid = calendar.getMaskUID()
+
+        # Do free busy operation
+        freebusy = True
+    else:
+        # Do regular invite (fan-out)
+        freebusy = False
+
+    # Prepare for multiple responses
+    responses = ScheduleResponseQueue("POST", responsecode.OK)
+
+    # Extract the ORGANIZER property and UID value from the calendar data for use later
+    organizerProp = calendar.getOrganizerProperty()
+    uid = calendar.resourceUID()
+
+    # Loop over each recipient and do appropriate action.
+    autoresponses = []
+    for recipient in recipients:
+        # Get the principal resource for this recipient
+        principal = self.principalForCalendarUserAddress(recipient)
+
+        # Map recipient to their inbox
+        inbox = None
+        if principal is None:
+            log.err("No principal for calendar user address: %s" % (recipient,))
+        else:
+            inboxURL = principal.scheduleInboxURL()
+            if inboxURL:
+                inbox = waitForDeferred(request.locateResource(inboxURL))
+                yield inbox
+                inbox = inbox.getResult()
+            else:
+                log.err("No schedule inbox for principal: %s" % (principal,))
+
+        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
+        
+            # Process next recipient
+            continue
+        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
+
+            # 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)
+        
+                # 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 = ([], [], [])
+            
+                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)
+
+                    # 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()
+
+                    # 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)
+
+                    responses.add(recipient, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
+                    recipients_state["OK"] += 1
+            
+                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
+            
+            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()
+        
+                # 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
+
+    # 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 = []
+        
+    # 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
+
+    # Return with final response if we are done
+    yield responses.response()
+
+class ScheduleResponseResponse (Response):
+    """
+    ScheduleResponse L{Response} object.
+    Renders itself as a CalDAV:schedule-response XML document.
+    """
+    def __init__(self, xml_responses, location=None):
+        """
+        @param xml_responses: an interable of davxml.Response objects.
+        @param location:      the value of the location header to return in the response,
+                              or None.
+        """
+
+        Response.__init__(self, code=responsecode.OK,
+                          stream=caldavxml.ScheduleResponse(*xml_responses).toxml())
+
+        self.headers.setHeader("content-type", MimeType("text", "xml"))
+    
+        if location is not None:
+            self.headers.setHeader("location", location)
+
+class ScheduleResponseQueue (object):
+    """
+    Stores a list of (typically error) responses for use in a
+    L{ScheduleResponse}.
+    """
+    def __init__(self, method, success_response):
+        """
+        @param method: the name of the method generating the queue.
+        @param success_response: the response to return in lieu of a
+            L{ScheduleResponse} if no responses are added to this queue.
+        """
+        self.responses         = []
+        self.method            = method
+        self.success_response  = success_response
+        self.location          = None
+
+    def setLocation(self, location):
+        """
+        @param location:      the value of the location header to return in the response,
+                              or None.
+        """
+        self.location = location
+
+    def add(self, recipient, what, reqstatus=None, calendar=None):
+        """
+        Add a response.
+        @param recipient: the recipient for this response.
+        @param what: a status code or a L{Failure} for the given recipient.
+        @param status: the iTIP request-status for the given recipient.
+        @param calendar: the calendar data for the given recipient response.
+        """
+        if type(what) is int:
+            code    = what
+            error   = None
+            message = responsecode.RESPONSES[code]
+        elif isinstance(what, Failure):
+            code    = statusForFailure(what)
+            error   = errorForFailure(what)
+            message = messageForFailure(what)
+        else:
+            raise AssertionError("Unknown data type: %r" % (what,))
+
+        if code > 400: # Error codes only
+            log.err("Error during %s for %s: %s" % (self.method, recipient, message))
+
+        children = []
+        children.append(caldavxml.Recipient(davxml.HRef.fromString(recipient)))
+        children.append(caldavxml.RequestStatus(reqstatus))
+        if calendar is not None:
+            children.append(caldavxml.CalendarData.fromCalendar(calendar))
+        if error is not None:
+            children.append(error)
+        if message is not None:
+            children.append(davxml.ResponseDescription(message))
+        self.responses.append(caldavxml.Response(*children))
+
+    def response(self):
+        """
+        Generate a L{ScheduleResponseResponse} with the responses contained in the
+        queue or, if no such responses, return the C{success_response} provided
+        to L{__init__}.
+        @return: the response.
+        """
+        if self.responses:
+            return ScheduleResponseResponse(self.responses, self.location)
+        else:
+            return self.success_response

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -63,7 +63,7 @@
 from twistedcaldav.index import Index, IndexSchedule
 from twistedcaldav.notifications import NotificationsCollectionResource, NotificationResource
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
-from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
+from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, ScheduleServerToServerResource
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource, DropBoxChildResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeTypeProvisioningResource
@@ -602,6 +602,48 @@
     def __repr__(self):
         return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
 
+class ServerToServerInboxFile (ScheduleServerToServerResource, CalDAVFile):
+    """
+    Server-to-server scheduling inbox resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, parent)
+        ScheduleServerToServerResource.__init__(self, parent)
+        
+        self.fp.open("w").close()
+        self.fp.restat(False)
+
+    def __repr__(self):
+        return "<%s (server-to-server inbox resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+    def isCollection(self):
+        return False
+
+    def createSimilarFile(self, path):
+        if path == self.fp.path:
+            return self
+        else:
+            return CalDAVFile(path, principalCollections=self.principalCollections())
+
+    def http_PUT        (self, request): return responsecode.FORBIDDEN
+    def http_COPY       (self, request): return responsecode.FORBIDDEN
+    def http_MOVE       (self, request): return responsecode.FORBIDDEN
+    def http_DELETE     (self, request): return responsecode.FORBIDDEN
+    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(schedulePrivilegeSet)
+
 class DropBoxHomeFile (AutoProvisioningFileMixIn, DropBoxHomeResource, CalDAVFile):
     def __init__(self, path, parent):
         DropBoxHomeResource.__init__(self)

Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py	2007-09-06 01:30:57 UTC (rev 1843)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/tap.py	2007-09-06 16:07:14 UTC (rev 1844)
@@ -52,6 +52,7 @@
 
 from twistedcaldav import pdmonster
 from twistedcaldav.static import CalendarHomeProvisioningFile
+from twistedcaldav.static import ServerToServerInboxFile
 
 try:
     from twistedcaldav.authkerb import NegotiateCredentialFactory
@@ -239,7 +240,7 @@
     def checkDirectory(self, dirpath, description, access=None, fail=False, permissions=None, uname=None, gname=None, create=None):
         if not os.path.exists(dirpath):
             if create is not None:
-            	# create is a tuple of (mode, username, groupname)
+                # create is a tuple of (mode, username, groupname)
                 try:
                     os.mkdir(dirpath)
                     os.chmod(dirpath, create[0])
@@ -341,9 +342,10 @@
     # default resource classes
     #
 
-    rootResourceClass      = RootResource
-    principalResourceClass = DirectoryPrincipalProvisioningResource
-    calendarResourceClass  = CalendarHomeProvisioningFile
+    rootResourceClass           = RootResource
+    principalResourceClass      = DirectoryPrincipalProvisioningResource
+    calendarResourceClass       = CalendarHomeProvisioningFile
+    servertoserverResourceClass = ServerToServerInboxFile
 
     def makeService_Slave(self, options):
         #
@@ -413,6 +415,15 @@
         root.putChild('principals', principalCollection)
         root.putChild('calendars', calendarCollection)
 
+        if config.EnableServerToServer:
+            log.msg("Setting up server-to-server resource: %r" % (self.servertoserverResourceClass,))
+    
+            servertoserver = self.servertoserverResourceClass(
+                os.path.join(config.DocumentRoot, 'inbox'),
+                root,
+            )
+            root.putChild('inbox', servertoserver)
+
         # Configure default ACLs on the root resource
 
         log.msg("Setting up default ACEs on root resource")

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070906/6dcc3d40/attachment.html


More information about the calendarserver-changes mailing list