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

source_changes at macosforge.org source_changes at macosforge.org
Fri Oct 28 11:22:24 PDT 2011


Revision: 8235
          http://trac.macosforge.org/projects/calendarserver/changeset/8235
Author:   sagen at apple.com
Date:     2011-10-28 11:22:23 -0700 (Fri, 28 Oct 2011)
Log Message:
-----------
Add podding logic to iMIP reply handling.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
    CalendarServer/trunk/twistedcaldav/mail.py
    CalendarServer/trunk/twistedcaldav/test/test_mail.py

Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2011-10-27 22:32:39 UTC (rev 8234)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2011-10-28 18:22:23 UTC (rev 8235)
@@ -30,12 +30,14 @@
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
+    <server-id>00001</server-id>
   </record>
   <record>
     <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
+    <server-id>00002</server-id>
   </record>
   <record>
     <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2011-10-27 22:32:39 UTC (rev 8234)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2011-10-28 18:22:23 UTC (rev 8235)
@@ -24,6 +24,7 @@
 import datetime
 import email.utils
 import os
+import urlparse
 import uuid
 
 from cStringIO import StringIO
@@ -545,9 +546,35 @@
         returnValue(Response(code=responsecode.OK))
 
 
+def injectionSettingsFromURL(url, config):
+    """
+    Given a url returned from server podding info (or None if not podding),
+    generate the url that should be used to inject an iMIP reply.  If the
+    url is None, then compute the url from config.
+    """
+    path = "inbox"
+    if url is None:
+        # Didn't get url from server podding configuration, so use caldavd.plist
+        if config.Scheduling.iMIP.MailGatewayServer == "localhost":
+            hostname = "localhost"
+        else:
+            hostname = config.ServerHostName
+        if config.EnableSSL:
+            useSSL = True
+            port = config.SSLPort
+        else:
+            useSSL = False
+            port = config.HTTPPort
+        scheme = "https:" if useSSL else "http:"
+        url = "%s//%s:%d/%s/" % (scheme, hostname, port, path)
+    else:
+        url = "%s/%s/" % (url.rstrip("/"), path)
+    return url
 
-def injectMessage(organizer, attendee, calendar, msgId, reactor=None):
 
+
+def injectMessage(url, organizer, attendee, calendar, msgId, reactor=None):
+
     if reactor is None:
         reactor = _reactor
 
@@ -559,23 +586,9 @@
     }
 
     data = str(calendar)
+    url = injectionSettingsFromURL(url, config)
+    parsed = urlparse.urlparse(url)
 
-    if config.EnableSSL:
-        useSSL = True
-        port = config.SSLPort
-    else:
-        useSSL = False
-        port = config.HTTPPort
-
-    # If we're running on same host as calendar server, inject via localhost
-    if config.Scheduling.iMIP.MailGatewayServer == 'localhost':
-        host = 'localhost'
-    else:
-        host = config.ServerHostName
-    path = "inbox"
-    scheme = "https:" if useSSL else "http:"
-    url = "%s//%s:%d/%s/" % (scheme, host, port, path)
-
     log.debug("Injecting to %s: %s %s" % (url, str(headers), data))
 
     factory = client.HTTPClientFactory(url, method='POST', headers=headers,
@@ -584,10 +597,11 @@
     factory.noisy = False
     factory.protocol = AuthorizedHTTPGetter
 
-    if useSSL:
-        reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
+    if parsed.scheme == "https":
+        reactor.connectSSL(parsed.hostname, parsed.port, factory,
+            ssl.ClientContextFactory())
     else:
-        reactor.connectTCP(host, port, factory)
+        reactor.connectTCP(parsed.hostname, parsed.port, factory)
 
     def _success(result, msgId):
         log.info("Mail gateway successfully injected message %s" % (msgId,))
@@ -601,8 +615,35 @@
     return factory.deferred
 
 
+def serverForOrganizer(directory, organizer):
+    """
+    Return the URL for the server hosting the organizer, or None if podding
+    is not enabled or organizer is hosted locally.
+    Raises ServerNotFound if we can't find the record for the organizer.
+    @param directory: service to look for organizer in
+    @type directory: L{DirectoryService}
+    @param organizer: CUA of organizer
+    @type organizer: C{str}
+    @return: string URL
+    """
+    record = directory.recordWithCalendarUserAddress(organizer)
+    if record is None:
+        log.warn("Can't find server for %s" % (organizer,))
+        raise ServerNotFound()
 
+    server = record.server() # None means hosted locally
+    if server is None:
+        return None
+    else:
+        return server.uri
 
+
+class ServerNotFound(Exception):
+    """
+    Can't determine which server is hosting a given user
+    """
+
+
 class MailGatewayTokensDatabase(AbstractSQLDatabase, LoggingMixIn):
     """
     A database to maintain "plus-address" tokens for IMIP requests.
@@ -957,9 +998,17 @@
             # TODO: what to do in this case?
             pass
 
-        self.log_warn("Mail gateway processing DSN %s" % (msgId,))
-        return fn(organizer, attendee, calendar, msgId)
+        try:
+            hostname = serverForOrganizer(self.directory, organizer)
+        except ServerNotFound:
+            # We can't determine which server hosts the organizer
+            self.log_error("Unable to determine which server hosts organizer %s"
+                % (organizer,))
+            return succeed(None)
 
+        self.log_warn("Mail gateway processing DSN %s to server %s" % (msgId, hostname))
+        return fn(hostname, organizer, attendee, calendar, msgId)
+
     def processReply(self, msg, injectFunction, testMode=False):
         # extract the token from the To header
         name, addr = email.utils.parseaddr(msg['To'])
@@ -1076,9 +1125,18 @@
             # the appropriate ATTENDEE.  This will require a new localizable
             # email template for the message.
 
-        return injectFunction(organizer, attendee, calendar, msg['Message-ID'])
+        try:
+            hostname = serverForOrganizer(self.directory, organizer)
+        except ServerNotFound:
+            # We can't determine which server hosts the organizer
+            self.log_error("Unable to determine which server hosts organizer %s"
+                % (organizer,))
+            return succeed(None)
 
+        return injectFunction(hostname, organizer, attendee, calendar,
+            msg['Message-ID'])
 
+
     def inbound(self, message, fn=injectMessage):
         try:
             msg = email.message_from_string(message)

Modified: CalendarServer/trunk/twistedcaldav/test/test_mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mail.py	2011-10-27 22:32:39 UTC (rev 8234)
+++ CalendarServer/trunk/twistedcaldav/test/test_mail.py	2011-10-28 18:22:23 UTC (rev 8235)
@@ -16,28 +16,27 @@
 
 
 from cStringIO import StringIO
-import datetime
-import email
-
 from twisted.internet.defer import inlineCallbacks
-
+from twisted.python.filepath import FilePath
+from twisted.python.modules import getModule
 from twisted.web.template import Element, renderer, flattenString
-from twisted.python.modules import getModule
-from twisted.python.filepath import FilePath
-
-from twistedcaldav.test.util import TestCase
-
+from twistedcaldav.config import config, ConfigDict
+from twistedcaldav.directory import augment
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.ical import Component
-from twistedcaldav.config import config
-from twistedcaldav.scheduling.itip import iTIPRequestStatus
-
+from twistedcaldav.mail import injectionSettingsFromURL
+from twistedcaldav.mail import MailGatewayTokensDatabase
 from twistedcaldav.mail import MailHandler
 from twistedcaldav.mail import StringFormatTemplateLoader
-from twistedcaldav.mail import MailGatewayTokensDatabase
+from twistedcaldav.mail import serverForOrganizer
+from twistedcaldav.scheduling.itip import iTIPRequestStatus
+from twistedcaldav.servers import Servers
+from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import xmlFile, augmentsFile
+import datetime
+import email
 
-from twistedcaldav.directory.directory import DirectoryRecord
 
-
 def echo(*args):
     return args
 
@@ -67,12 +66,31 @@
 class MailHandlerTests(TestCase):
 
     def setUp(self):
-        TestCase.setUp(self)
-        self.handler = MailHandler(dataRoot=":memory:")
+        super(MailHandlerTests, self).setUp()
+
+        self._setupServers(serverData)
+        self.directory = XMLDirectoryService(
+            {
+                'xmlFile' : xmlFile,
+                'augmentService' :
+                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
+            }
+        )
+        self.handler = MailHandler(dataRoot=":memory:", directory=self.directory)
         module = getModule(__name__)
         self.dataPath = module.filePath.sibling("data").child("mail")
 
 
+    def _setupServers(self, data):
+        self.patch(config, "ServerHostName", "caldav1.example.com")
+        self.patch(config, "HTTPPort", 8008)
+        self.patch(config.Servers, "Enabled", True)
+
+        xmlFile = StringIO(data)
+        servers = Servers
+        servers.load(xmlFile, ignoreIPLookupFailures=True)
+
+
     def dataFile(self, name):
         """
         Get the contents of a given data file from the 'data/mail' test
@@ -80,7 +98,23 @@
         """
         return self.dataPath.child(name).getContent()
 
+    def test_serverDetection(self):
+        wsanchez = self.directory.recordWithShortName("users",
+            "wsanchez")
+        cdaboo = self.directory.recordWithShortName("users",
+            "cdaboo")
+        server = wsanchez.server()
+        self.assertEquals(server.uri, "http://caldav1.example.com:8008")
+        server = cdaboo.server()
+        self.assertEquals(server.uri, "https://caldav2.example.com:8843")
 
+        url = serverForOrganizer(self.directory,
+            "mailto:wsanchez at example.com")
+        self.assertEquals(url, "http://caldav1.example.com:8008")
+        url = serverForOrganizer(self.directory,
+            "mailto:cdaboo at example.com")
+        self.assertEquals(url, "https://caldav2.example.com:8843")
+
     def test_purge_and_lowercase(self):
         """
         Ensure that purge( ) cleans out old tokens, and that lowercase( )
@@ -263,12 +297,13 @@
            None)
 
         # Make sure a known token *is* processed
-        token = self.handler.db.createToken("mailto:user01 at example.com",
+        token = self.handler.db.createToken(
+            "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500",
             "mailto:user02 at example.com", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C")
         calBody = template % token
-        organizer, attendee, calendar, msgId = self.handler.processDSN(calBody,
+        url, organizer, attendee, calendar, msgId = self.handler.processDSN(calBody,
             "xyzzy", echo)
-        self.assertEquals(organizer, 'mailto:user01 at example.com')
+        self.assertEquals(organizer, 'urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500')
         self.assertEquals(attendee, 'mailto:user02 at example.com')
         self.assertEquals(str(calendar), """BEGIN:VCALENDAR
 VERSION:2.0
@@ -298,7 +333,7 @@
 DTEND;TZID=US/Pacific:20080812T104500
 CREATED:20080812T191857Z
 DTSTAMP:20080812T191932Z
-ORGANIZER;CN=User 01:mailto:user01 at example.com
+ORGANIZER;CN=User 01:urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500
 REQUEST-STATUS:5.1;Service unavailable
 SEQUENCE:2
 SUMMARY:New Event
@@ -318,35 +353,85 @@
 
         # Make sure a known token *is* processed
         self.handler.db.createToken(
-            "urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66",
+            "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500",
             "mailto:xyzzy at example.com",
             icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C",
             token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f"
         )
-        organizer, attendee, calendar, msgId = self.handler.processReply(msg,
-            echo)
+        url, organizer, attendee, calendar, msgId = self.handler.processReply(msg, echo)
+        self.assertEquals(url, "https://caldav2.example.com:8843")
         self.assertEquals(organizer,
-                          'urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66')
+                          'urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500')
         self.assertEquals(attendee, 'mailto:xyzzy at example.com')
         self.assertEquals(msgId,
                           '<1983F777-BE86-4B98-881E-06D938E60920 at example.com>')
 
+    def test_injectionSettingsFromURL(self):
+        testData = (
+            (
+                None,
+                {
+                    "Scheduling": {
+                        "iMIP" : {
+                            "MailGatewayServer" : "localhost",
+                        },
+                    },
+                    "EnableSSL" : True,
+                    "ServerHostName" : "calendar.example.com",
+                    "HTTPPort" : 1111,
+                    "SSLPort" : 2222,
+                },
+                "https://localhost:2222/inbox/",
+            ),
+            (
+                None,
+                {
+                    "Scheduling": {
+                        "iMIP" : {
+                            "MailGatewayServer" : "mailgateway.example.com",
+                        },
+                    },
+                    "EnableSSL" : False,
+                    "ServerHostName" : "calendar.example.com",
+                    "HTTPPort" : 1111,
+                    "SSLPort" : 2222,
+                },
+                "http://calendar.example.com:1111/inbox/",
+            ),
+            (
+                "https://calendar.example.com:1234/",
+                { },
+                "https://calendar.example.com:1234/inbox/",
+            ),
+            (
+                "https://calendar.example.com:1234",
+                { },
+                "https://calendar.example.com:1234/inbox/",
+            ),
+        )
+
+        for url, configData, expected in testData:
+            self.assertEquals(
+                expected,
+                injectionSettingsFromURL(url, ConfigDict(mapping=configData))
+            )
+
     def test_processReplyMissingOrganizer(self):
         msg = email.message_from_string(self.dataFile('reply_missing_organizer'))
         # stick the token in the database first
         self.handler.db.createToken(
-            "urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66",
+            "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500",
             "mailto:xyzzy at example.com",
             icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C",
             token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f"
         )
 
-        organizer, attendee, calendar, msgId = self.handler.processReply(msg,
-            echo)
+        url, organizer, attendee, calendar, msgId = self.handler.processReply(
+            msg, echo)
         organizerProp = calendar.mainComponent().getOrganizerProperty()
         self.assertTrue(organizerProp is not None)
         self.assertEquals(organizer,
-                          "urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66")
+                          "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500")
 
 
     def test_processReplyMissingAttendee(self):
@@ -354,14 +439,14 @@
 
         # stick the token in the database first
         self.handler.db.createToken(
-            "urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66",
+            "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500",
             "mailto:xyzzy at example.com",
             icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C",
             token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f"
         )
 
-        organizer, attendee, calendar, msgId = self.handler.processReply(msg,
-            echo)
+        url, organizer, attendee, calendar, msgId = self.handler.processReply(
+            msg, echo)
 
         # Since the expected attendee was missing, the reply processor should
         # have added an attendee back in with a "5.1;Service unavailable"
@@ -372,20 +457,12 @@
 
     def test_processReplyMissingAttachment(self):
 
-        # Fake a directory record
-        record = DirectoryRecord(self.handler.directory, "users",
-            "9DC04A70-E6DD-11DF-9492-0800200C9A66", shortNames=("user01",),
-            emailAddresses=("user01 at example.com",))
-        record.enabled = True
-        self.handler.directory._tmpRecords[
-            "guids"]["9DC04A70-E6DD-11DF-9492-0800200C9A66"] = record
-
         msg = email.message_from_string(
             self.dataFile('reply_missing_attachment')
         )
         # stick the token in the database first
         self.handler.db.createToken(
-            "urn:uuid:9DC04A70-E6DD-11DF-9492-0800200C9A66",
+            "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500",
             "mailto:xyzzy at example.com",
             icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C",
             token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f"
@@ -393,7 +470,7 @@
 
         self.assertEquals(
             self.handler.processReply(msg, echo, testMode=True),
-            ("user01 at example.com", "xyzzy at example.com")
+            ("cdaboo at example.com", "xyzzy at example.com")
         )
 
 
@@ -759,3 +836,29 @@
         self.assertEquals(self.db.lookupByToken(token), None)
 
 
+serverData = """<?xml version="1.0" encoding="utf-8"?>
+<servers>
+  <server>
+    <id>00001</id>
+    <uri>http://caldav1.example.com:8008</uri>
+    <allowed-from>127.0.0.1</allowed-from>
+    <shared-secret>foobar</shared-secret>
+  </server>
+  <server>
+    <id>00002</id>
+    <uri>https://caldav2.example.com:8843</uri>
+    <partitions>
+        <partition>
+            <id>A</id>
+            <uri>https://machine1.example.com:8443</uri>
+        </partition>
+        <partition>
+            <id>B</id>
+            <uri>https://machine2.example.com:8443</uri>
+        </partition>
+    </partitions>
+  </server>
+</servers>
+"""
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111028/091e3af4/attachment-0001.html>


More information about the calendarserver-changes mailing list