[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