[CalendarServer-changes] [5895] CalendarServer/branches/new-store
source_changes at macosforge.org
source_changes at macosforge.org
Wed Jul 14 04:50:42 PDT 2010
Revision: 5895
http://trac.macosforge.org/projects/calendarserver/changeset/5895
Author: glyph at apple.com
Date: 2010-07-14 04:50:41 -0700 (Wed, 14 Jul 2010)
Log Message:
-----------
detect X-APPLE-DROPBOX property and enforce permissions based on it; also simplify permission checking logic.
Modified Paths:
--------------
CalendarServer/branches/new-store/twistedcaldav/storebridge.py
CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
Modified: CalendarServer/branches/new-store/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/storebridge.py 2010-07-14 11:44:03 UTC (rev 5894)
+++ CalendarServer/branches/new-store/twistedcaldav/storebridge.py 2010-07-14 11:50:41 UTC (rev 5895)
@@ -129,15 +129,28 @@
-def requiresPermissions(*permissions):
+def requiresPermissions(*permissions, **kw):
"""
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.
"""
+ fromParent = kw.get('fromParent')
+ # FIXME: direct unit tests
def wrap(thunk):
def authAndContinue(self, request):
- d = self.authorize(request, permissions)
+ if permissions:
+ d = self.authorize(request, permissions)
+ else:
+ d = succeed(None)
+ if fromParent:
+ d.addCallback(
+ lambda whatever:
+ request.locateResource(parentForURL(request.uri))
+ ).addCallback(
+ lambda parent:
+ parent.authorize(request, fromParent)
+ )
d.addCallback(lambda whatever: thunk(self, request))
return d
return authAndContinue
@@ -394,6 +407,7 @@
principalCollections=self.principalCollections())
else:
result = CalendarAttachment(
+ self._newStoreCalendarObject,
attachment, principalCollections=self.principalCollections())
self.propagateTransaction(result)
return result
@@ -455,6 +469,9 @@
"""
d = super(CalendarObjectDropbox, self).accessControlList(*a, **kw)
def moreACLs(originalACL):
+ othersCanWrite = (
+ self._newStoreCalendarObject.attendeesCanManageAttachments()
+ )
originalACEs = list(originalACL.children)
cuas = self._newStoreCalendarObject.component().getAttendees()
newACEs = []
@@ -463,9 +480,16 @@
calendarUserAddress
)
principalURL = principal.principalURL()
+ if othersCanWrite:
+ privileges = [davxml.Privilege(davxml.All())]
+ else:
+ privileges = [
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
+ ]
newACEs.append(davxml.ACE(
davxml.Principal(davxml.HRef(principalURL)),
- davxml.Grant(davxml.Privilege(davxml.Read())),
+ davxml.Grant(*privileges),
davxml.Protected(),
TwistedACLInheritable(),
))
@@ -475,7 +499,7 @@
-class ProtoCalendarAttachment(_GetChildHelper, CalDAVResource):
+class ProtoCalendarAttachment(_GetChildHelper):
def __init__(self, calendarObject, attachmentName, **kw):
super(ProtoCalendarAttachment, self).__init__(**kw)
@@ -491,6 +515,12 @@
return NO_CONTENT
+ # FIXME: Permissions should dictate a different response, sometimes.
+ def http_GET(self, request):
+ return NOT_FOUND
+
+
+ @requiresPermissions(fromParent=[davxml.Bind()])
def http_PUT(self, request):
# FIXME: MIME-Type from header
# FIXME: direct test
@@ -501,15 +531,16 @@
)
def done(ignored):
t.loseConnection()
- return NO_CONTENT
+ return CREATED
return readStream(request.stream, t.write).addCallback(done)
class CalendarAttachment(_GetChildHelper):
- def __init__(self, attachment, **kw):
+ def __init__(self, calendarObject, attachment, **kw):
super(CalendarAttachment, self).__init__(**kw)
+ self._newStoreCalendarObject = calendarObject
self._newStoreAttachment = attachment
@@ -529,7 +560,7 @@
return None
- # FIXME: @requiresPermissions(davxml.Write())
+ @requiresPermissions(davxml.WriteContent())
def http_PUT(self, request):
# FIXME: direct test
# FIXME: MIME-Type from header
@@ -554,9 +585,14 @@
return Response(OK, None, stream)
+ @requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
- # FIXME: implement
- raise NotImplementedError()
+ self._newStoreCalendarObject.removeAttachmentWithName(
+ self._newStoreAttachment.name()
+ )
+ del self._newStoreCalendarObject
+ self.__class__ = ProtoCalendarAttachment
+ return NO_CONTENT
def isCollection(self):
@@ -589,21 +625,13 @@
return True
- # FIXME: @requiresPermissions(fromParent=[Bind()])
+ @requiresPermissions(fromParent=[davxml.Unbind()])
@inlineCallbacks
def http_DELETE(self, request):
"""
Override http_DELETE to validate 'depth' header.
"""
- #
- # Check authentication and access controls
- #
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
-
- yield parent.authorize(request, (davxml.Unbind(),))
-
depth = request.headers.getHeader("depth", "infinity")
if depth != "infinity":
msg = "illegal depth header for DELETE on collection: %s" % (
@@ -719,6 +747,7 @@
return FORBIDDEN
+ # FIXME: access control
@inlineCallbacks
def http_MOVE(self, request):
"""
@@ -881,9 +910,11 @@
# FIXME: Tests
return True
+
def name(self):
return self._newStoreObject.name()
+
def etag(self):
# FIXME: far too slow to be used for real, but I needed something to
# placate the etag computation in the case where the file doesn't exist
@@ -919,24 +950,14 @@
return self._newStoreObject.iCalendarText()
- @inlineCallbacks
+ @requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
"""
Override http_DELETE to validate 'depth' header.
"""
+ return self.storeRemove(request, True, request.uri)
- #
- # Check authentication and access controls
- #
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
- yield parent.authorize(request, (davxml.Unbind(),))
-
- response = (yield self.storeRemove(request, True, request.uri))
- returnValue(response)
-
-
@inlineCallbacks
def storeStream(self, stream):
# FIXME: direct tests
@@ -1221,6 +1242,7 @@
def isCollection(self):
return True
+
def isAddressBookCollection(self):
"""
Yes, it is a calendar collection.
@@ -1228,20 +1250,12 @@
return True
+ @requiresPermissions(fromParent=[davxml.Unbind()])
@inlineCallbacks
def http_DELETE(self, request):
"""
Override http_DELETE to validate 'depth' header.
"""
-
- #
- # Check authentication and access controls
- #
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
-
- yield parent.authorize(request, (davxml.Unbind(),))
-
depth = request.headers.getHeader("depth", "infinity")
if depth != "infinity":
msg = "illegal depth header for DELETE on collection: %s" % (
@@ -1339,6 +1353,7 @@
return FORBIDDEN
+ # FIXME: access control
@inlineCallbacks
def http_MOVE(self, request):
"""
@@ -1362,6 +1377,7 @@
returnValue(NO_CONTENT)
+
class ProtoAddressBookCollectionFile(CalDAVFile):
"""
A resource representing an addressbook collection which hasn't yet been created.
@@ -1384,6 +1400,7 @@
def isCollection(self):
return True
+
def createSimilarFile(self, path):
# FIXME: this is necessary for
# twistedcaldav.test.test_mkcol.
@@ -1480,6 +1497,7 @@
def isCollection(self):
return True
+
def createSimilarFile(self, path):
# FIXME: this is necessary for
# twistedcaldav.test.test_mkcol.
@@ -1621,24 +1639,14 @@
return self._newStoreObject.vCardText()
- @inlineCallbacks
+ @requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
"""
Override http_DELETE to validate 'depth' header.
"""
+ return self.storeRemove(request, request.uri)
- #
- # Check authentication and access controls
- #
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
- yield parent.authorize(request, (davxml.Unbind(),))
-
- response = (yield self.storeRemove(request, request.uri))
- returnValue(response)
-
-
@inlineCallbacks
def storeStream(self, stream):
# FIXME: direct tests
@@ -1746,10 +1754,12 @@
"""
Initialize with a notification collection.
- @param notifications: the wrapped http://daboo.name/groups/daboofamily/wiki/c4c42/6585_Sale.html.
+ @param notifications: the wrapped notification collection backend
+ object.
@type notifications: L{txdav.common.inotification.INotificationCollection}
- @param home: the home through which the given notification collection was accessed.
+ @param home: the home through which the given notification collection
+ was accessed.
@type home: L{txdav.icommonstore.ICommonHome}
"""
self._newStoreNotifications = notifications
@@ -1812,7 +1822,8 @@
-class StoreNotificationCollectionFile(_NotificationChildHelper, NotificationCollectionFile):
+class StoreNotificationCollectionFile(_NotificationChildHelper,
+ NotificationCollectionFile):
"""
Wrapper around a L{txcaldav.icalendar.ICalendar}.
"""
@@ -1829,12 +1840,12 @@
def isCollection(self):
return True
+
@inlineCallbacks
def http_DELETE(self, request):
"""
Override http_DELETE to reject.
"""
-
raise HTTPError(StatusResponse(FORBIDDEN, "Cannot delete notification collections"))
@@ -1853,6 +1864,8 @@
"""
raise HTTPError(StatusResponse(FORBIDDEN, "Cannot move notification collections"))
+
+
class StoreNotificationObjectFile(NotificationFile):
"""
A resource wrapping a calendar object.
@@ -1913,23 +1926,14 @@
return self._newStoreObject.xmldata()
- @inlineCallbacks
+ @requiresPermissions(fromParent=[davxml.Unbind()])
def http_DELETE(self, request):
"""
Override http_DELETE to validate 'depth' header.
"""
+ return self.storeRemove(request, request.uri)
- #
- # Check authentication and access controls
- #
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
- yield parent.authorize(request, (davxml.Unbind(),))
-
- response = (yield self.storeRemove(request, request.uri))
- returnValue(response)
-
@inlineCallbacks
def storeRemove(self, request, where):
"""
@@ -1965,6 +1969,7 @@
returnValue(NO_CONTENT)
+
def _initializeWithObject(self, notificationObject):
self._newStoreObject = notificationObject
self._dead_properties = _NewStorePropertiesWrapper(
@@ -1985,9 +1990,11 @@
super(ProtoStoreNotificationObjectFile, self).__init__(*a, **kw)
self._newStoreParentNotifications = parentNotifications
+
def isCollection(self):
return False
+
def exists(self):
# FIXME: tests
return False
@@ -1997,3 +2004,5 @@
# FIXME: tests, workingness
return succeed(0)
+
+
Modified: CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/calendarstore/file.py 2010-07-14 11:44:03 UTC (rev 5894)
+++ CalendarServer/branches/new-store/txcaldav/calendarstore/file.py 2010-07-14 11:50:41 UTC (rev 5895)
@@ -336,6 +336,8 @@
"""
# FIXME: rollback, tests for rollback
self._dropboxPath().child(name).remove()
+ if name in self._attachments:
+ del self._attachments[name]
def attachmentWithName(self, name):
@@ -352,7 +354,6 @@
else:
# FIXME: test for non-existent attachment.
return None
- # But, ahem.
def _allSubcomponents(self, component):
@@ -362,17 +363,31 @@
yield deeper
- def dropboxID(self):
+ def _anyProperty(self, name):
component = self.component()
for subcomp in self._allSubcomponents(component):
- dropboxProperty = subcomp.getProperty("X-APPLE-DROPBOX")
+ dropboxProperty = subcomp.getProperty(name)
if dropboxProperty is not None:
- componentDropboxID = dropboxProperty.value().split("/")[-1]
- return componentDropboxID
+ return dropboxProperty.value()
+ return None
+
+
+ def attendeesCanManageAttachments(self):
+ return bool(self._anyProperty("X-APPLE-DROPBOX"))
+
+
+ def dropboxID(self):
# FIXME: direct tests
-
- # FIXME: read ATTACH properties as well as X-APPLE-DROPBOX properties,
- # since X-APPLE-DROPBOX only appears when others can write attachments.
+ dropboxProperty = self._anyProperty("X-APPLE-DROPBOX")
+ if dropboxProperty is not None:
+ componentDropboxID = dropboxProperty.split("/")[-1]
+ return componentDropboxID
+ attachProperty = self._anyProperty("ATTACH")
+ if attachProperty is not None:
+ # FIXME: more aggressive checking to see if this URI is really the
+ # 'right' URI. Maybe needs to happen in the front end.
+ attachPath = attachProperty.split("/")[-2]
+ return attachPath
return self.uid() + ".dropbox"
@@ -434,6 +449,7 @@
# TwistedGETContentMD5.fromString(md5)
+
contentTypeKey = PropertyName.fromString(GETContentType.sname())
# md5key = PropertyName.fromString(TwistedGETContentMD5.sname())
@@ -508,6 +524,7 @@
"""
Just enough resource to keep the calendar's sql DB classes going.
"""
+
def __init__(self, calendar):
self.calendar = calendar
self.fp = self.calendar._path
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100714/89e59c0a/attachment-0001.html>
More information about the calendarserver-changes
mailing list