[CalendarServer-changes] [2750] CalendarServer/branches/users/sagen/mailgateway-implicit-2745/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Tue Jul 22 11:45:06 PDT 2008


Revision: 2750
          http://trac.macosforge.org/projects/calendarserver/changeset/2750
Author:   sagen at apple.com
Date:     2008-07-22 11:45:06 -0700 (Tue, 22 Jul 2008)
Log Message:
-----------
Checkpoint -- still need to add token mapping

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py
    CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/static.py
    CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/tap.py

Modified: CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py	2008-07-22 00:30:34 UTC (rev 2749)
+++ CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py	2008-07-22 18:45:06 UTC (rev 2750)
@@ -20,6 +20,8 @@
 """
 
 from twisted.internet import protocol, defer, ssl
+from twisted.web.client import HTTPClientFactory
+from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue
 from twisted.protocols import basic
 from twisted.mail import pop3client, imap4
 from twisted.plugin import IPlugin
@@ -28,13 +30,14 @@
 from twisted.python.reflect import namedClass
 from twistedcaldav.log import LoggingMixIn
 from twistedcaldav import ical
+from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.scheduling.scheduler import IMIPScheduler
-from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
 from twistedcaldav.config import config, parseConfig, defaultConfig
 from zope.interface import Interface, implements
-import email
+import email, uuid
 
 __all__ = [
+    "IMIPInboxResource",
     "MailGatewayServiceMaker",
 ]
 
@@ -133,6 +136,195 @@
         self.parent['pidfile'] = None
 
 
+
+class IMIPInboxResource(CalDAVResource):
+    """
+    IMIP-delivery Inbox resource.
+
+    Extends L{DAVResource} to provide IMIP delivery functionality.
+    """
+
+    def __init__(self, parent):
+        """
+        @param parent: the parent resource of this one.
+        """
+        assert parent is not None
+
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+        self.parent = parent
+
+    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(),
+            ),
+        )
+
+    def resourceType(self):
+        return davxml.ResourceType.ischeduleinbox
+
+    def isCollection(self):
+        return False
+
+    def isCalendarCollection(self):
+        return False
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+    def render(self, request):
+        output = """<html>
+<head>
+<title>IMIP Delivery Resource</title>
+</head>
+<body>
+<h1>IMIP Delivery Resource.</h1>
+</body
+</html>"""
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response
+
+    @inlineCallbacks
+    def http_POST(self, request):
+        """
+        The IMIP delivery POST method.
+        """
+
+        # Check authentication and access controls
+        # yield self.authorize(request, (caldavxml.Schedule(),))
+
+        # Inject using the IMIPScheduler.
+        scheduler = IMIPScheduler(request, self)
+
+        # Do the POST processing treating this as a non-local schedule
+        response = (yield scheduler.doSchedulingViaPOST())
+        returnValue(response)
+
+
+
+def injectMessage(reactor, useSSL, host, port, path, originator, recipient,
+    calendar):
+
+    headers = {
+        'Content-Type' : 'text/calendar',
+        'Originator' : originator,
+        'Recipient' : recipient,
+    }
+
+    # TODO: use token to look up actual organizer for substitution
+    calendar.getOrganizerProperty().setValue("mailto:user01 at example.com")
+    data = str(calendar)
+
+    scheme = "https:" if useSSL else "http:"
+    url = "%s//%s:%d/%s/" % (scheme, host, port, path)
+
+    factory = HTTPClientFactory(url, method='POST', headers=headers,
+        postdata=data)
+    if useSSL:
+        reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
+    else:
+        reactor.connectTCP(host, port, factory)
+    return factory.deferred
+
+
+
+
+class MailGatewayTokensDatabase(AbstractSQLDatabase):
+    """
+    A database to maintain "plus-address" tokens for IMIP requests.
+
+    SCHEMA:
+
+    Token Database:
+
+    ROW: TOKEN, ORGANIZER
+
+    """
+
+    dbType = "MAILGATEWAYTOKENS"
+    dbFilename = "mailgatewaytokens.sqlite"
+    dbFormatVersion = "1"
+
+
+    def __init__(self, path):
+        path = os.path.join(path, MailGatewayTokensDatabase.dbFilename)
+        super(MailGatewayTokensDatabase, self).__init__(path, True)
+
+    def createToken(self, organizer):
+        token = uuid.uuid4()
+        self._db_execute(
+            """
+            insert into TOKENS (TOKEN, ORGANIZER)
+            values (:1, :2)
+            """, token, organizer
+        )
+
+    def deleteToken(self, token):
+        self._db_execute(
+            """
+            delete from TOKENS where TOKEN = :1
+            """, token
+        )
+
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return MailGatewayTokensDatabase.dbFormatVersion
+
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return MailGatewayTokensDatabase.dbType
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+
+        #
+        # TOKENS table
+        #
+        q.execute(
+            """
+            create table TOKENS (
+                TOKEN       text,
+                ORGANIZER   text
+            )
+            """
+        )
+        q.execute(
+            """
+            create index TOKENSINDEX on TOKENS (TOKEN)
+            """
+        )
+
+    def _db_upgrade_data_tables(self, q, old_version):
+        """
+        Upgrade the data from an older version of the DB.
+        @param q: a database cursor to use.
+        @param old_version: existing DB's version number
+        @type old_version: str
+        """
+        pass
+
+
+
+#
+# Service
+#
+
 class MailGatewayServiceMaker(object):
     implements(IPlugin, service.IServiceMaker)
 
@@ -250,6 +442,7 @@
         self.log_info("POP factory connection failed")
         self.retry(connector)
 
+    @inlineCallbacks
     def handleMessage(self, message):
         self.log_info("POP factory handle message")
         self.log_info(message)
@@ -258,12 +451,15 @@
         for part in parsedMessage.walk():
             if part.get_content_type() == "text/calendar":
                 calBody = part.get_payload(decode=True)
-                calComponent = ical.Component.fromString(calBody)
-                scheduler = IMIPScheduler(None, None)
-                organizer = LocalCalendarUser("mailto:user01 at example.com", None)
-                scheduler.doSchedulingViaPUT(None, (organizer,), calComponent)
+                self.log_info(calBody)
+                calendar = ical.Component.fromString(calBody)
+                yield injectMessage(self.reactor, False, "localhost", 8008,
+                    "email-inbox", "mailto:ORGANIZER at HOST.NAME",
+                    "mailto:user01 at example.com", calendar)
 
 
+
+
 #
 # IMAP4
 #

Modified: CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/scheduling/scheduler.py	2008-07-22 00:30:34 UTC (rev 2749)
+++ CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/scheduling/scheduler.py	2008-07-22 18:45:06 UTC (rev 2750)
@@ -720,15 +720,12 @@
 
 class IMIPScheduler(Scheduler):
 
+    # TODO: have iScheduleScheduler and IMIPScheduler share a common base
+    # class to share checkRecipients()
+
     def checkAuthorization(self):
         pass
 
-    def checkOriginator(self):
-        pass
-
-    def checkRecipients(self):
-        pass
-
     def checkOrganizer(self):
         pass
 
@@ -741,7 +738,60 @@
     def securityChecks(self):
         pass
 
+    @inlineCallbacks
+    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.
+        originatorPrincipal = self.resource.principalForCalendarUserAddress(self.originator)
+        localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
+        if originatorPrincipal or localUser:
+            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 = RemoteCalendarUser(self.originator)
+
+
+    @inlineCallbacks
+    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)
+            
+            # 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:
+                localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(recipient))
+                if localUser:
+                    log.err("No principal for calendar user address: %s" % (recipient,))
+                else:
+                    log.err("Unknown calendar user address: %s" % (recipient,))
+                results.append(InvalidCalendarUser(recipient))
+            else:
+                # Map recipient to their inbox
+                inbox = None
+                inboxURL = principal.scheduleInboxURL()
+                if inboxURL:
+                    inbox = (yield self.request.locateResource(inboxURL))
+
+                if inbox:
+                    results.append(LocalCalendarUser(recipient, principal, inbox, inboxURL))
+                else:
+                    log.err("No schedule inbox for principal: %s" % (principal,))
+                    results.append(InvalidCalendarUser(recipient))
+        
+        self.recipients = results
+
+
 class ScheduleResponseResponse (Response):
     """
     ScheduleResponse L{Response} object.

Modified: CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/static.py	2008-07-22 00:30:34 UTC (rev 2749)
+++ CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/static.py	2008-07-22 18:45:06 UTC (rev 2750)
@@ -24,6 +24,7 @@
     "CalendarHomeProvisioningFile",
     "CalendarHomeUIDProvisioningFile",
     "CalendarHomeFile",
+    "IMIPInboxFile",
     "ScheduleFile",
     "ScheduleInboxFile",
     "ScheduleOutboxFile",
@@ -62,6 +63,7 @@
 from twistedcaldav.index import Index, IndexSchedule
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, IScheduleInboxResource
+from twistedcaldav.mail import IMIPInboxResource
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
 from twistedcaldav.directory.calendar import uidsResourceName
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
@@ -725,6 +727,41 @@
             (caldav_namespace, "calendar-collection-location-ok")
         )
 
+class IMIPInboxFile (IMIPInboxResource, CalDAVFile):
+    """
+    Mail gateway IMIP-delivery resource.
+    """
+    def __init__(self, path, parent):
+        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+        IMIPInboxResource.__init__(self, parent)
+        
+        self.fp.open("w").close()
+        self.fp.restat(False)
+
+    def __repr__(self):
+        return "<%s (IMIP delivery 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 responsecode.NOT_FOUND
+
+    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")
+        )
+
 class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
     """
     Free-busy URL resource.

Modified: CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/tap.py	2008-07-22 00:30:34 UTC (rev 2749)
+++ CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/tap.py	2008-07-22 18:45:06 UTC (rev 2750)
@@ -53,6 +53,7 @@
 from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.static import IScheduleInboxFile
 from twistedcaldav.static import TimezoneServiceFile
+from twistedcaldav.static import IMIPInboxFile
 from twistedcaldav.timezones import TimezoneCache
 from twistedcaldav import pdmonster
 from twistedcaldav import memcachepool
@@ -436,6 +437,7 @@
     principalResourceClass       = DirectoryPrincipalProvisioningResource
     calendarResourceClass        = CalendarHomeProvisioningFile
     iScheduleResourceClass       = IScheduleInboxFile
+    imipResourceClass            = IMIPInboxFile
     timezoneServiceResourceClass = TimezoneServiceFile
 
     def makeService_Slave(self, options):
@@ -537,6 +539,15 @@
             root.putChild('inbox', ischedule)
 
         #
+        # IMIP delivery resource
+        #
+        imipInbox = self.imipResourceClass(
+            os.path.join(config.DocumentRoot, 'imip-inbox'),
+            root,
+        )
+        root.putChild('email-inbox', imipInbox)
+
+        #
         # Configure ancillary data
         #
         log.info("Setting up Timezone Cache")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080722/11ecc415/attachment-0001.html 


More information about the calendarserver-changes mailing list