[CalendarServer-changes] [5183] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Feb 22 19:45:11 PST 2010


Revision: 5183
          http://trac.macosforge.org/projects/calendarserver/changeset/5183
Author:   sagen at apple.com
Date:     2010-02-22 19:45:07 -0800 (Mon, 22 Feb 2010)
Log Message:
-----------
Replies for attendees now handled by the imip gateway.

If the client receives an invitation out of band (e.g. imip from a third-party),
the client can PUT the event to calendar server with the appropriate PARTSTAT
and the implicit scheduler will send an imip reply to the organizer via the
mail gateway.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/mail.py
    CalendarServer/trunk/twistedcaldav/scheduling/imip.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/itip.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2010-02-22 23:56:15 UTC (rev 5182)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2010-02-23 03:45:07 UTC (rev 5183)
@@ -768,29 +768,15 @@
 
 
 
-    def outbound(self, organizer, attendee, calendar, language='en'):
+    def outbound(self, originator, recipient, calendar, language='en'):
         # create token, send email
 
         component = calendar.masterComponent()
         if component is None:
             component = calendar.mainComponent(True)
         icaluid = component.propertyValue("UID")
+        method = calendar.propertyValue("METHOD")
 
-        token = self.db.getToken(organizer, attendee, icaluid)
-        if token is None:
-            token = self.db.createToken(organizer, attendee, icaluid)
-            self.log_debug("Mail gateway created token %s for %s (organizer), %s (attendee) and %s (icaluid)" % (token, organizer, attendee, icaluid))
-            newInvitation = True
-        else:
-            self.log_debug("Mail gateway reusing token %s for %s (organizer), %s (attendee) and %s (icaluid)" % (token, organizer, attendee, icaluid))
-            newInvitation = False
-
-        settings = config.Scheduling['iMIP']['Sending']
-        fullServerAddress = settings['Address']
-        name, serverAddress = email.utils.parseaddr(fullServerAddress)
-        pre, post = serverAddress.split('@')
-        addressWithToken = "%s+%s@%s" % (pre, token, post)
-
         attendees = []
         for attendeeProp in calendar.getAllAttendeeProperties():
             params = attendeeProp.params()
@@ -808,39 +794,73 @@
                 if cn or mailto:
                     attendees.append( (cn, mailto) )
 
-        calendar.getOrganizerProperty().setValue("mailto:%s" %
-            (addressWithToken,))
+        toAddr = recipient
+        if not recipient.startswith("mailto:"):
+            raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (recipient,))
+        recipient = recipient[7:]
 
-        organizerAttendee = calendar.getAttendeeProperty([organizer])
-        if organizerAttendee is not None:
-            organizerAttendee.setValue("mailto:%s" % (addressWithToken,))
+        settings = config.Scheduling['iMIP']['Sending']
 
+        if method != "REPLY":
+            # Invites and cancellations:
 
-        # The email's From will include the organizer's real name email
-        # address if available.  Otherwise it will be the server's email
-        # address (without # + addressing)
-        if organizer.startswith("mailto:"):
-            orgEmail = fromAddr = organizer[7:]
-        else:
-            fromAddr = serverAddress
-            orgEmail = None
-        cn = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
-        if cn is None:
-            cn = 'Calendar Server'
-            orgCN = orgEmail
-        else:
-            orgCN = cn
-        formattedFrom = "%s <%s>" % (cn, fromAddr)
+            # Reuse or generate a token based on originator, recipient, and
+            # event uid
+            token = self.db.getToken(originator, recipient, icaluid)
+            if token is None:
+                token = self.db.createToken(originator, recipient, icaluid)
+                self.log_debug("Mail gateway created token %s for %s (originator), %s (recipient) and %s (icaluid)" % (token, originator, recipient, icaluid))
+                inviteState = "new"
+            else:
+                self.log_debug("Mail gateway reusing token %s for %s (originator), %s (recipient) and %s (icaluid)" % (token, originator, recipient, icaluid))
+                inviteState = "update"
 
-        # Reply-to address will be the server+token address
+            fullServerAddress = settings['Address']
+            name, serverAddress = email.utils.parseaddr(fullServerAddress)
+            pre, post = serverAddress.split('@')
+            addressWithToken = "%s+%s@%s" % (pre, token, post)
 
-        toAddr = attendee
-        if not attendee.startswith("mailto:"):
-            raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (attendee,))
-        attendee = attendee[7:]
+            calendar.getOrganizerProperty().setValue("mailto:%s" %
+                (addressWithToken,))
 
-        msgId, message = self.generateEmail(newInvitation, calendar, orgEmail,
-            orgCN, attendees, formattedFrom, addressWithToken, attendee,
+            originatorAttendee = calendar.getAttendeeProperty([originator])
+            if originatorAttendee is not None:
+                originatorAttendee.setValue("mailto:%s" % (addressWithToken,))
+
+            # The email's From will include the originator's real name email
+            # address if available.  Otherwise it will be the server's email
+            # address (without # + addressing)
+            if originator.startswith("mailto:"):
+                orgEmail = fromAddr = originator[7:]
+            else:
+                fromAddr = serverAddress
+                orgEmail = None
+            cn = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
+            if cn is None:
+                cn = 'Calendar Server'
+                orgCN = orgEmail
+            else:
+                orgCN = cn
+            formattedFrom = "%s <%s>" % (cn, fromAddr)
+
+            # Reply-to address will be the server+token address
+
+        else: # REPLY
+            inviteState = "reply"
+            if not originator.startswith("mailto:"):
+                raise ValueError("Originator address '%s' must be mailto: for REPLY." % (originator,))
+            formattedFrom = fromAddr = originator = originator[7:]
+
+            organizerMailto = str(calendar.getOrganizer())
+            if not organizerMailto.startswith("mailto:"):
+                raise ValueError("ORGANIZER address '%s' must be mailto: for REPLY." % (organizerMailto,))
+            orgEmail = organizerMailto[7:]
+
+            orgCN = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
+            addressWithToken = formattedFrom
+
+        msgId, message = self.generateEmail(inviteState, calendar, orgEmail,
+            orgCN, attendees, formattedFrom, addressWithToken, recipient,
             language=language)
 
         self.log_debug("Sending: %s" % (message,))
@@ -899,7 +919,7 @@
             return iconPath
 
 
-    def generateEmail(self, newInvitation, calendar, orgEmail, orgCN,
+    def generateEmail(self, inviteState, calendar, orgEmail, orgCN,
         attendees, fromAddress, replyToAddress, toAddress, language='en'):
 
         details = self.getEventDetails(calendar, language=language)
@@ -917,10 +937,12 @@
 
             if canceled:
                 formatString = _("Event canceled: %(summary)s")
-            elif newInvitation:
+            elif inviteState == "new":
                 formatString = _("Event invitation: %(summary)s")
+            elif inviteState == "update":
+                formatString = _("Event update: %(summary)s")
             else:
-                formatString = _("Event update: %(summary)s")
+                formatString = _("Event reply: %(summary)s")
 
             details['subject'] = msg['Subject'] = formatString % {
                 'summary' : details['summary']
@@ -933,10 +955,12 @@
             if canceled:
                 details['inviteLabel'] = _("Event Canceled")
             else:
-                if newInvitation:
+                if inviteState == "new":
                     details['inviteLabel'] = _("Event Invitation")
+                if inviteState == "update":
+                    details['inviteLabel'] = _("Event Update")
                 else:
-                    details['inviteLabel'] = _("Event Update")
+                    details['inviteLabel'] = _("Event Reply")
 
             details['dateLabel'] = _("Date")
             details['timeLabel'] = _("Time")

Modified: CalendarServer/trunk/twistedcaldav/scheduling/imip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/imip.py	2010-02-22 23:56:15 UTC (rev 5182)
+++ CalendarServer/trunk/twistedcaldav/scheduling/imip.py	2010-02-23 03:45:07 UTC (rev 5183)
@@ -59,6 +59,7 @@
             if method not in (
                 "PUBLISH",
                 "REQUEST",
+                "REPLY",
                 "ADD",
                 "CANCEL",
                 "DECLINE_COUNTER",
@@ -77,7 +78,7 @@
                     if not toAddr.startswith("mailto:"):
                         raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (toAddr,))
 
-                    fromAddr = str(self.scheduler.calendar.getOrganizer())
+                    fromAddr = self.scheduler.originator.cuaddr
 
                     log.debug("POSTing iMIP message to gateway...  To: '%s', From :'%s'\n%s" % (toAddr, fromAddr, caldata,))
                     yield self.postToGateway(fromAddr, toAddr, caldata)

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-02-22 23:56:15 UTC (rev 5182)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2010-02-23 03:45:07 UTC (rev 5183)
@@ -37,6 +37,7 @@
 from twistedcaldav.scheduling.itip import iTipGenerator, iTIPRequestStatus
 from twistedcaldav.scheduling.scheduler import CalDAVScheduler
 from twistedcaldav.scheduling.utils import getCalendarObjectForPrincipals
+from twistedcaldav.config import config
 
 __all__ = [
     "ImplicitScheduler",
@@ -854,6 +855,9 @@
         is_server = self.calendar.getOrganizerScheduleAgent()
         local_organizer = isinstance(self.organizerAddress, LocalCalendarUser)
 
+        if config.Scheduling.iMIP.Enabled and self.organizerAddress.cuaddr.startswith("mailto:"):
+            return True
+
         if local_organizer and not is_server:
             log.error("Attendee '%s' is not allowed to change SCHEDULE-AGENT on organizer: UID:%s" % (self.attendeePrincipal, self.uid,))
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-attendee-change")))

Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2010-02-22 23:56:15 UTC (rev 5182)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2010-02-23 03:45:07 UTC (rev 5183)
@@ -641,6 +641,9 @@
             "ORGANIZER",
             "ATTENDEE",
             "X-CALENDARSERVER-PRIVATE-COMMENT",
+            "SUMMARY",
+            "LOCATION",
+            "DESCRIPTION",
         ))
         
         # Now set each ATTENDEE's PARTSTAT to DECLINED

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py	2010-02-22 23:56:15 UTC (rev 5182)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_imip.py	2010-02-23 03:45:07 UTC (rev 5183)
@@ -34,34 +34,6 @@
             self.calendar = calendar
 
     @inlineCallbacks
-    def test_no_reply(self):
-        
-        data = """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REPLY
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
-
-        scheduler = iMIPProcessing.FakeSchedule(Component.fromString(data))
-        recipients = (RemoteCalendarUser("mailto:user1 at example.com"),)
-        responses = ScheduleResponseQueue("REPLY", responsecode.OK)
-
-        delivery = ScheduleViaIMip(scheduler, recipients, responses, False)
-        yield delivery.generateSchedulingResponses()
-        
-        self.assertEqual(len(responses.responses), 1)
-        self.assertEqual(str(responses.responses[0].children[1]), iTIPRequestStatus.NO_USER_SUPPORT)
-
-    @inlineCallbacks
     def test_no_freebusy(self):
         
         data = """BEGIN:VCALENDAR
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100222/6b73c75e/attachment-0001.html>


More information about the calendarserver-changes mailing list