[CalendarServer-changes] [2090]
CalendarServer/branches/users/cdaboo/private_events-2081/
twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Fri Jan 4 14:38:25 PST 2008
Revision: 2090
http://trac.macosforge.org/projects/calendarserver/changeset/2090
Author: cdaboo at apple.com
Date: 2008-01-04 14:38:23 -0800 (Fri, 04 Jan 2008)
Log Message:
-----------
Handle X-CALENDARSERVER-ACCESS on PUT. This includes setting up ACLs for PRIVATE to prevent access by anyone
except DAV:owner. Still have to do data filtering for CONFIDENTIAL and RESTRICTED.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/__init__.py
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/customxml.py
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/ical.py
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/method/put_common.py
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/resource.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/fileops.py
Modified: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/__init__.py 2008-01-02 22:24:22 UTC (rev 2089)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/__init__.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -31,6 +31,7 @@
"directory",
"dropbox",
"extensions",
+ "fileops",
"ical",
"icaldav",
"index",
Modified: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/customxml.py 2008-01-02 22:24:22 UTC (rev 2089)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/customxml.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -60,6 +60,17 @@
def getValue(self):
return str(self)
+class TwistedCalendarAccessProperty (davxml.WebDAVTextElement):
+ """
+ Contains the calendar access level (private events) for the resource.
+ """
+ namespace = twisted_dav_namespace
+ name = "calendar-access"
+ hidden = True
+
+ def getValue(self):
+ return str(self)
+
class CalendarProxyRead (davxml.WebDAVEmptyElement):
"""
A read-only calendar user proxy principal resource.
Added: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/fileops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/fileops.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/fileops.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -0,0 +1,111 @@
+##
+# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twisted.internet.defer import deferredGenerator
+from twisted.web2.dav.fileop import put
+from twisted.internet.defer import waitForDeferred
+from twisted.web2.dav.fileop import copy
+
+"""
+Various file utilities.
+"""
+
+from twisted.web2.dav.xattrprops import xattrPropertyStore
+
+# This class simulates a DAVFile with enough information for use with xattrPropertyStore.
+class FakeXAttrResource(object):
+
+ def __init__(self, fp):
+ self.fp = fp
+
+ at deferredGenerator
+def putWithXAttrs(stream, filepath):
+ """
+ Write a file to a possibly existing path and preserve any xattrs at that path.
+
+ @param stream: the stream to write to the destination.
+ @type stream: C{file}
+ @param filepath: the destination file.
+ @type filepath: L{FilePath}
+ """
+
+ # Preserve existings xattrs
+ props = []
+ if filepath.exists():
+ xold = xattrPropertyStore(FakeXAttrResource(filepath))
+ for item in xold.list():
+ props.append((xold.get(item)))
+ xold = None
+
+ # First do the actual file copy
+ d = waitForDeferred(put(stream, filepath))
+ yield d
+ response = d.getResult()
+
+ # Restore original xattrs.
+ if props:
+ xnew = xattrPropertyStore(FakeXAttrResource(filepath))
+ for prop in props:
+ xnew.set(prop)
+ xnew = None
+
+ yield response
+
+ at deferredGenerator
+def copyWithXAttrs(source_filepath, destination_filepath, destination_uri):
+ """
+ Copy a file from one path to another and also copy xattrs we care about.
+
+ @param source_filepath: the file to copy from
+ @type source_filepath: L{FilePath}
+ @param destination_filepath: the file to copy to
+ @type destination_filepath: L{FilePath}
+ @param destination_uri: the URI of the destination resource
+ @type destination_uri: C{str}
+ """
+
+ # First do the actual file copy
+ d = waitForDeferred(copy(source_filepath, destination_filepath, destination_uri, "0"))
+ yield d
+ response = d.getResult()
+
+ # Now copy over xattrs.
+ copyXAttrs(source_filepath, destination_filepath)
+
+ yield response
+
+def copyToWithXAttrs(from_fp, to_fp):
+ """
+ Copy a file from one path to another and also copy xattrs we care about.
+
+ @param from_fp: file being copied
+ @type from_fp: L{FilePath}
+ @param to_fp: file to copy to
+ @type to_fp: L{FilePath}
+ """
+
+ # First do the actual file copy.
+ from_fp.copyTo(to_fp)
+
+ # Now copy over xattrs.
+ copyXAttrs(from_fp, to_fp)
+
+def copyXAttrs(from_fp, to_fp):
+ # Create xattr stores for each file and copy over all xattrs.
+ xfrom = xattrPropertyStore(FakeXAttrResource(from_fp))
+ xto = xattrPropertyStore(FakeXAttrResource(to_fp))
+
+ for item in xfrom.list():
+ xto.set(xfrom.get(item))
Modified: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/ical.py 2008-01-02 22:24:22 UTC (rev 2089)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/ical.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -379,7 +379,7 @@
return None
- def accessLevel(self):
+ def accessLevel(self, default=ACCESS_PUBLIC):
"""
Return the access level for this component.
@return: the access level for the calendar data.
@@ -389,7 +389,7 @@
access = self.propertyValue(Component.ACCESS_PROPERTY)
if access:
access = access.upper()
- return Component.accessMap.get(access, Component.ACCESS_PUBLIC)
+ return Component.accessMap.get(access, default)
def duplicate(self):
"""
Modified: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/method/put_common.py 2008-01-02 22:24:22 UTC (rev 2089)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/method/put_common.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -31,7 +31,7 @@
from twisted.web2.dav import davxml
from twisted.web2.dav.element.base import dav_namespace
from twisted.web2.dav.element.base import PCDATAElement
-from twisted.web2.dav.fileop import copy, delete, put
+from twisted.web2.dav.fileop import delete
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.resource import TwistedGETContentMD5
from twisted.web2.dav.stream import MD5StreamWrapper
@@ -46,10 +46,16 @@
from twistedcaldav.caldavxml import NoUIDConflict
from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.ical import Component
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.fileops import copyToWithXAttrs
+from twistedcaldav.fileops import putWithXAttrs
+from twistedcaldav.fileops import copyWithXAttrs
+from twistedcaldav.ical import Component, Property
from twistedcaldav.index import ReservationError
from twistedcaldav.instance import TooManyInstancesError
+ at deferredGenerator
def storeCalendarObjectResource(
request,
sourcecal, destinationcal,
@@ -151,7 +157,7 @@
if self.source_index_deleted:
doSourceIndexRecover()
self.destination_index_deleted = False
- logging.debug("Rollback: soyurce re-indexed %s" % (source.fp.path,), system="Store Resource")
+ logging.debug("Rollback: source re-indexed %s" % (source.fp.path,), system="Store Resource")
except:
log.err("Rollback: exception caught and not handled: %s" % failure.Failure())
@@ -258,6 +264,39 @@
return result, message
+ @deferredGenerator
+ def validAccess():
+ """
+ Make sure that the X-CALENDARSERVER-ACCESS property is properly dealt with.
+ """
+
+ if calendar.hasProperty(Component.ACCESS_PROPERTY):
+
+ # Must be a value we know about
+ access = calendar.accessLevel(default=None)
+ if access is None:
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (calendarserver_namespace, "valid-access-restriction")))
+
+ # Only DAV:owner is able to set the property to other than PUBLIC
+ d = waitForDeferred(destinationparent.owner(request))
+ yield d
+ parent_owner = d.getResult()
+
+ authz = destinationparent.currentPrincipal(request)
+ if davxml.Principal(parent_owner) != authz and access != Component.ACCESS_PUBLIC:
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (calendarserver_namespace, "valid-access-restriction-change")))
+
+ yield access, calendardata
+ else:
+ # Check whether an access property was present before and write that into the calendar data
+ newcalendardata = calendardata
+ if not source and destination.exists() and destination.hasDeadProperty(TwistedCalendarAccessProperty):
+ old_access = str(destination.readDeadProperty(TwistedCalendarAccessProperty))
+ calendar.addProperty(Property(name=Component.ACCESS_PROPERTY, value=old_access))
+ newcalendardata = str(calendar)
+
+ yield None, newcalendardata
+
def noUIDConflict(uid):
"""
Check that the UID of the new calendar object conforms to the requirements of
@@ -305,6 +344,7 @@
Handle validation operations here.
"""
reserved = False
+ access = None
if destinationcal:
# Valid resource name check
result, message = validResourceName()
@@ -361,6 +401,12 @@
log.err(message)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "max-resource-size")))
+ # Check access
+ if destinationcal and config.EnablePrivateEvents:
+ d = waitForDeferred(validAccess())
+ yield d
+ access, calendardata = d.getResult()
+
# Reserve UID
destination_index = destinationparent.index()
@@ -437,7 +483,7 @@
if overwrite:
rollback.destination_copy = FilePath(destination.fp.path)
rollback.destination_copy.path += ".rollback"
- destination.fp.copyTo(rollback.destination_copy)
+ copyToWithXAttrs(destination.fp, rollback.destination_copy)
logging.debug("Rollback: backing up destination %s to %s" % (destination.fp.path, rollback.destination_copy.path), system="Store Resource")
else:
rollback.destination_created = True
@@ -446,7 +492,7 @@
if deletesource:
rollback.source_copy = FilePath(source.fp.path)
rollback.source_copy.path += ".rollback"
- source.fp.copyTo(rollback.source_copy)
+ copyToWithXAttrs(source.fp, rollback.source_copy)
logging.debug("Rollback: backing up source %s to %s" % (source.fp.path, rollback.source_copy.path), system="Store Resource")
"""
@@ -465,10 +511,10 @@
# Do put or copy based on whether source exists
if source is not None:
- response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
+ response = maybeDeferred(copyWithXAttrs, source.fp, destination.fp, destination_uri)
else:
md5 = MD5StreamWrapper(MemoryStream(calendardata))
- response = maybeDeferred(put, md5, destination.fp)
+ response = maybeDeferred(putWithXAttrs, md5, destination.fp)
response = waitForDeferred(response)
yield response
response = response.getResult()
@@ -485,8 +531,18 @@
md5 = md5.getMD5()
destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+ # Update calendar-access property value on the resource
+ if access:
+ destination.writeDeadProperty(TwistedCalendarAccessProperty(access))
+
+ # Do not remove the property if access was not specified and we are storing in a calendar.
+ # This ensure that clients that do not preserve the iCalendar property do not cause access
+ # restrictions to be lost.
+ elif not destinationcal:
+ destination.removeDeadProperty(TwistedCalendarAccessProperty)
+
response = IResponse(response)
-
+
def doDestinationIndex(caltoindex):
"""
Do destination resource indexing, replacing any index previous stored.
@@ -616,5 +672,3 @@
# if the rollback has already ocurred or changes already committed.
rollback.Rollback()
raise
-
-storeCalendarObjectResource = deferredGenerator(storeCalendarObjectResource)
Modified: CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/resource.py 2008-01-02 22:24:22 UTC (rev 2089)
+++ CalendarServer/branches/users/cdaboo/private_events-2081/twistedcaldav/resource.py 2008-01-04 22:38:23 UTC (rev 2090)
@@ -50,7 +50,9 @@
import twistedcaldav
from twistedcaldav import caldavxml, customxml
from twistedcaldav.config import config
+from twistedcaldav.customxml import TwistedCalendarAccessProperty
from twistedcaldav.extensions import DAVResource, DAVPrincipalResource
+from twistedcaldav.ical import Component
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.customxml import calendarserver_namespace
@@ -238,12 +240,55 @@
return self.hasDeadProperty(AccessDisabled)
# FIXME: Perhaps this is better done in authorize() instead.
- def accessControlList(self, *args, **kwargs):
+ @deferredGenerator
+ def accessControlList(self, request, *args, **kwargs):
if self.isDisabled():
- return succeed(None)
+ yield None
+ return
- return super(CalDAVResource, self).accessControlList(*args, **kwargs)
+ d = waitForDeferred(super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
+ yield d
+ acls = d.getResult()
+ # Look for private events access classification
+ if self.hasDeadProperty(TwistedCalendarAccessProperty):
+ access = self.readDeadProperty(TwistedCalendarAccessProperty)
+ if access.getValue() in (Component.ACCESS_PRIVATE, Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED,):
+ # Need to insert ACE to prevent non-owner principals from seeing this resource
+ d = waitForDeferred(self.owner(request))
+ yield d
+ owner = d.getResult()
+ if access.getValue() == Component.ACCESS_PRIVATE:
+ ace = davxml.ACE(
+ davxml.Invert(
+ davxml.Principal(owner),
+ ),
+ davxml.Deny(
+ davxml.Privilege(
+ davxml.Read(),
+ ),
+ davxml.Privilege(
+ davxml.Write(),
+ ),
+ ),
+ davxml.Protected(),
+ )
+ else:
+ ace = davxml.ACE(
+ davxml.Invert(
+ davxml.Principal(owner),
+ ),
+ davxml.Deny(
+ davxml.Privilege(
+ davxml.Write(),
+ ),
+ ),
+ davxml.Protected(),
+ )
+
+ acls = davxml.ACL(ace, *acls.children)
+ yield acls
+
@deferredGenerator
def owner(self, request):
"""
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080104/ddf5182b/attachment-0001.html
More information about the calendarserver-changes
mailing list