[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