[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