[CalendarServer-changes] [5872] CalendarServer/branches/new-store/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 12 08:54:48 PDT 2010


Revision: 5872
          http://trac.macosforge.org/projects/calendarserver/changeset/5872
Author:   glyph at apple.com
Date:     2010-07-12 08:54:47 -0700 (Mon, 12 Jul 2010)
Log Message:
-----------
Synthesize inheritable 'read' ACLs for attachments based on attendee list.

Also, simplify / linearize parts of the inheritance hierarchy to be friendlier to sharing resourceType implementation.

Modified Paths:
--------------
    CalendarServer/branches/new-store/twistedcaldav/extensions.py
    CalendarServer/branches/new-store/twistedcaldav/storebridge.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py

Modified: CalendarServer/branches/new-store/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/extensions.py	2010-07-12 15:52:35 UTC (rev 5871)
+++ CalendarServer/branches/new-store/twistedcaldav/extensions.py	2010-07-12 15:54:47 UTC (rev 5872)
@@ -443,17 +443,8 @@
         returnValue(MultiStatusResponse(responses))
 
 
-class DAVResource (DirectoryPrincipalPropertySearchMixIn, SudoersMixin, SuperDAVResource, LoggingMixIn):
-    """
-    Extended L{twext.web2.dav.resource.DAVResource} implementation.
-    """
-    http_REPORT = http_REPORT
+class DirectoryRenderingMixIn(object):
 
-    def render(self, request):
-        if self.isCollection():
-            return self.renderDirectory(request)
-        return super(DAVResource, self).render(request)
-
     def directoryStyleSheet(self):
         return (
             "th, .even td, .odd td { padding-right: 0.5em; font-family: monospace}"
@@ -687,8 +678,32 @@
 
 
 
-class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn, SuperDAVPrincipalResource, LoggingMixIn):
+class DAVResource (DirectoryPrincipalPropertySearchMixIn,
+                   SudoersMixin, SuperDAVResource, LoggingMixIn,
+                   DirectoryRenderingMixIn):
     """
+    Extended L{twext.web2.dav.resource.DAVResource} implementation.
+    """
+    http_REPORT = http_REPORT
+
+    def render(self, request):
+        if self.isCollection():
+            return self.renderDirectory(request)
+        return super(DAVResource, self).render(request)
+
+
+    def resourceType(self, request):
+        # Allow live property to be overridden by dead property
+        if self.deadProperties().contains((dav_namespace, "resourcetype")):
+            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
+        return succeed(davxml.ResourceType())
+
+
+
+class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn,
+                            SuperDAVPrincipalResource, LoggingMixIn,
+                            DirectoryRenderingMixIn):
+    """
     Extended L{twext.web2.dav.static.DAVFile} implementation.
     """
 
@@ -764,7 +779,9 @@
             return succeed(davxml.ResourceType(davxml.Principal()))
 
 
-class DAVFile (SudoersMixin, SuperDAVFile, LoggingMixIn):
+
+class DAVFile (SudoersMixin, SuperDAVFile, LoggingMixIn,
+               DirectoryRenderingMixIn):
     """
     Extended L{twext.web2.dav.static.DAVFile} implementation.
     """
@@ -828,8 +845,6 @@
 
 
 
-
-
 class ReadOnlyWritePropertiesResourceMixIn (object):
     """
     Read only that will allow writing of properties resource.

Modified: CalendarServer/branches/new-store/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/storebridge.py	2010-07-12 15:52:35 UTC (rev 5871)
+++ CalendarServer/branches/new-store/twistedcaldav/storebridge.py	2010-07-12 15:54:47 UTC (rev 5872)
@@ -38,7 +38,7 @@
     FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
     BAD_REQUEST, OK, NOT_IMPLEMENTED, NOT_ALLOWED)
 from twext.web2.dav import davxml
-from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.resource import TwistedGETContentMD5, TwistedACLInheritable
 from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL
 from twext.web2.http import HTTPError, StatusResponse, Response
 from twext.web2.stream import ProducerStream, readStream
@@ -126,6 +126,23 @@
                 self._newPropertyStore.keys()]
 
 
+
+def requiresPermissions(*permissions):
+    """
+    A decorator to wrap http_ methods in, to indicate that they should not be
+    run until the current user principal has been authorized for the given
+    permission set.
+    """
+    def wrap(thunk):
+        def authAndContinue(self, request):
+            d = self.authorize(request, permissions)
+            d.addCallback(lambda whatever: thunk(self, request))
+            return d
+        return authAndContinue
+    return wrap
+
+
+
 class _CalendarChildHelper(object):
     """
     Methods for things which are like calendars.
@@ -234,6 +251,7 @@
     def isCollection(self):
         return True
 
+
     def provisionFile(self):
         pass
 
@@ -242,7 +260,8 @@
         pass
 
 
-class _GetChildHelper(object):
+
+class _GetChildHelper(CalDAVResource):
     def locateChild(self, request, segments):
         if segments[0] == '':
             return self, segments[1:]
@@ -268,14 +287,20 @@
         return ("1", "access-control")
 
 
+    @requiresPermissions(davxml.Read())
+    def http_GET(self, request):
+        return super(_GetChildHelper, self).http_GET(request)
+
+
     http_PROPFIND = http_PROPFIND
 
 
 
-class DropboxCollection(_GetChildHelper, CalDAVResource):
+class DropboxCollection(_GetChildHelper):
     """
-    A wrapper around a calendar object which serves that calendar object's
-    attachments as a DAV collection.
+    A collection of all dropboxes (containers for attachments), presented as a
+    resource under the user's calendar home, where a dropbox is a
+    L{CalendarObjectDropbox}.
     """
     # FIXME: no direct tests for this class at all.
 
@@ -319,8 +344,10 @@
 
 
 
-class NoDropboxHere(_GetChildHelper, CalDAVResource):
 
+
+class NoDropboxHere(_GetChildHelper):
+
     def isCollection(self):
         return False
 
@@ -338,7 +365,11 @@
 
 
 
-class CalendarObjectDropbox(_GetChildHelper, CalDAVResource):
+class CalendarObjectDropbox(_GetChildHelper):
+    """
+    A wrapper around a calendar object which serves that calendar object's
+    attachments as a DAV collection.
+    """
 
     def __init__(self, calendarObject, *a, **kw):
         super(CalendarObjectDropbox, self).__init__(*a, **kw)
@@ -361,7 +392,8 @@
                 name,
                 principalCollections=self.principalCollections())
         else:
-            result = CalendarAttachment(attachment)
+            result = CalendarAttachment(
+                attachment, principalCollections=self.principalCollections())
         self.propagateTransaction(result)
         return result
 
@@ -382,7 +414,32 @@
         return l
 
 
+    def accessControlList(self, *a, **kw):
+        """
+        All principals identified as ATTENDEEs on the event for this dropbox
+        may read all its children.
+        """
+        d = super(CalendarObjectDropbox, self).accessControlList(*a, **kw)
+        def moreACLs(originalACL):
+            originalACEs = list(originalACL.children)
+            cuas = self._newStoreCalendarObject.component().getAttendees()
+            newACEs = []
+            for calendarUserAddress in cuas:
+                principal = self.principalForCalendarUserAddress(
+                    calendarUserAddress
+                )
+                principalURL = principal.principalURL()
+                newACEs.append(davxml.ACE(
+                    davxml.Principal(davxml.HRef(principalURL)),
+                    davxml.Grant(davxml.Privilege(davxml.Read())),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+                ))
+            return davxml.ACL(*tuple(newACEs + originalACEs))
+        d.addCallback(moreACLs)
+        return d
 
+
 class ProtoCalendarAttachment(_GetChildHelper, CalDAVResource):
 
 
@@ -411,10 +468,10 @@
 
 
 
-class CalendarAttachment(_GetChildHelper, CalDAVResource):
+class CalendarAttachment(_GetChildHelper):
 
-    def __init__(self, attachment):
-        super(CalendarAttachment, self).__init__()
+    def __init__(self, attachment, **kw):
+        super(CalendarAttachment, self).__init__(**kw)
         self._newStoreAttachment = attachment
 
 
@@ -422,10 +479,12 @@
         return None
 
 
+    # FIXME: @requiresPermissions(davxml.Write())
     def http_PUT(self, request):
         # FIXME: direct test
         # FIXME: MIME-Type
         # FIXME: refactor with ProtoCalendarAttachment.http_PUT
+        # FIXME: CDT test to make sure that permissions are enforced.
         t = self._newStoreAttachment.store("application/octet-stream")
         def done(ignored):
             t.loseConnection()
@@ -433,6 +492,7 @@
         return readStream(request.stream, t.write).addCallback(done)
 
 
+    @requiresPermissions(davxml.Read())
     def http_GET(self, request):
         stream = ProducerStream()
         class StreamProtocol(Protocol):
@@ -471,6 +531,7 @@
     def isCollection(self):
         return True
 
+
     def isCalendarCollection(self):
         """
         Yes, it is a calendar collection.
@@ -752,7 +813,7 @@
         calendarName = self._newStoreObject._calendar.name()
         homeUID = self._newStoreObject._calendar._calendarHome.uid()
         store = self._newStoreObject._transaction.store()
-        txn = store.newTransaction("new transaction for "+self._newStoreObject.name())
+        txn = store.newTransaction("new transaction for " + self._newStoreObject.name())
         newObject = (txn.calendarHomeWithUID(homeUID)
                         .calendarWithName(calendarName)
                         .calendarObjectWithName(objectName))
@@ -1441,7 +1502,7 @@
         Name = self._newStoreObject._addressbook.name()
         homeUID = self._newStoreObject._addressbook._addressbookHome.uid()
         store = self._newStoreObject._transaction.store()
-        txn = store.newTransaction("new AB transaction for "+self._newStoreObject.name())
+        txn = store.newTransaction("new AB transaction for " + self._newStoreObject.name())
         newObject = (txn.HomeWithUID(homeUID)
                         .addressbookWithName(Name)
                         .addressbookObjectWithName(objectName))

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	2010-07-12 15:52:35 UTC (rev 5871)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py	2010-07-12 15:54:47 UTC (rev 5872)
@@ -18,13 +18,17 @@
 Tests for the interaction between model-level and protocol-level logic.
 """
 
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
+from twistedcaldav.config import config
+
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twistedcaldav.ical import Component as VComponent
 from twistedcaldav.vcard import Component as VCComponent
 
-from twistedcaldav.storebridge import ProtoCalendarCollectionFile,\
-    ProtoAddressBookCollectionFile
+from twistedcaldav.storebridge import ProtoCalendarCollectionFile, \
+    ProtoAddressBookCollectionFile, DropboxCollection
 
 from twistedcaldav.test.util import TestCase
 
@@ -74,9 +78,6 @@
         txn = self.calendarCollection._newStore.newTransaction()
         home = txn.calendarHomeWithUID(uid, True)
         cal = home.calendarWithName("calendar")
-        if cal is None:
-            home.createCalendarWithName("calendar")
-            cal = home.calendarWithName("calendar")
         cal.createCalendarObjectWithName(objectName, VComponent.fromString(objectText))
         txn.commit()
 
@@ -147,9 +148,9 @@
     @inlineCallbacks
     def test_lookupCalendarHome(self):
         """
-        When a L{CalDAVFile} representing an existing calendar home is looked up
-        in a CalendarHomeFile, it will create a corresponding L{CalendarHome}
-        via C{newTransaction().calendarHomeWithUID}.
+        When a L{CalDAVFile} representing an existing calendar home is looked
+        up in a CalendarHomeFile, it will create a corresponding
+        L{CalendarHome} via C{newTransaction().calendarHomeWithUID}.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/")
         self.commit()
@@ -158,15 +159,35 @@
 
 
     @inlineCallbacks
+    def test_lookupDropboxHome(self):
+        """
+        When dropboxes are enabled, the 'dropbox' child of the user's calendar
+        home should be a L{DropboxCollection} wrapper around the user's
+        calendar home, with the dropbox-home resource type.
+        """
+        self.patch(config, "EnableDropBox", True)
+        dropBoxResource = yield self.getResource(
+            "calendars/users/wsanchez/dropbox"
+        )
+        self.commit()
+        self.assertIsInstance(dropBoxResource, DropboxCollection)
+        self.assertEquals((yield dropBoxResource.resourceType(None)),
+                          davxml.ResourceType.dropboxhome)
+
+
+    @inlineCallbacks
     def test_lookupExistingCalendar(self):
         """
         When a L{CalDAVFile} representing an existing calendar collection is
-        looked up in a L{CalendarHomeFile} representing a calendar home, it will
-        create a corresponding L{Calendar} via C{CalendarHome.calendarWithName}.
+        looked up in a L{CalendarHomeFile} representing a calendar home, it
+        will create a corresponding L{Calendar} via
+        C{CalendarHome.calendarWithName}.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/calendar")
         self.commit()
         self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendar._path)
+        self.assertEquals((yield calDavFile.resourceType(None)),
+                          davxml.ResourceType.calendar)
 
 
     @inlineCallbacks
@@ -214,7 +235,7 @@
             "calendars/users/wsanchez/calendar/1.ics"
         )
         self.commit()
-        self.assertEquals(calDavFileCalendar._newStoreObject._path, 
+        self.assertEquals(calDavFileCalendar._newStoreObject._path,
                           calDavFileCalendar.fp)
         self.assertEquals(calDavFileCalendar._principalCollections,
                           frozenset([self.principalsResource]))
@@ -234,6 +255,7 @@
         self.assertEquals(calDavFileCalendar._principalCollections,
                           frozenset([self.principalsResource]))
 
+
     def test_createAddressBookStore(self):
         """
         Creating a AddressBookHomeProvisioningFile will create a paired
@@ -297,7 +319,7 @@
             "addressbooks/users/wsanchez/addressbook/1.vcf"
         )
         self.commit()
-        self.assertEquals(calDavFileAddressBook._newStoreObject._path, 
+        self.assertEquals(calDavFileAddressBook._newStoreObject._path,
                           calDavFileAddressBook.fp)
         self.assertEquals(calDavFileAddressBook._principalCollections,
                           frozenset([self.principalsResource]))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100712/bbde297a/attachment-0001.html>


More information about the calendarserver-changes mailing list