[CalendarServer-changes] [2163] CalendarServer/branches/release/CalendarServer-1.2-dev

source_changes at macosforge.org source_changes at macosforge.org
Tue Feb 19 11:20:48 PST 2008


Revision: 2163
          http://trac.macosforge.org/projects/calendarserver/changeset/2163
Author:   wsanchez at apple.com
Date:     2008-02-19 11:20:48 -0800 (Tue, 19 Feb 2008)

Log Message:
-----------
Pull up r2151 r2161 (private events)

Modified Paths:
--------------
    CalendarServer/branches/release/CalendarServer-1.2-dev/conf/caldavd-test.plist
    CalendarServer/branches/release/CalendarServer-1.2-dev/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/__init__.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/caldavxml.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/config.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/customxml.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendar.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/ical.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/__init__.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/put_common.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_calquery.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_common.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_multiget.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/query/calendarquery.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/resource.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/schedule.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/static.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/test/test_xml.py

Added Paths:
-----------
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/fileops.py
    CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/get.py

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/conf/caldavd-test.plist	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/conf/caldavd-test.plist	2008-02-19 19:20:48 UTC (rev 2163)
@@ -330,7 +330,11 @@
   <key>EnableNotifications</key>
   <true/>
 
+  <!-- Private Events -->
+  <key>EnablePrivateEvents</key>
+  <true/>
 
+
   <!--
     Twisted
   -->

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch	2008-02-19 19:20:48 UTC (rev 2163)
@@ -11,7 +11,7 @@
  
  import random
  
-@@ -37,8 +39,14 @@
+@@ -37,8 +39,13 @@
  from twisted.web2.dav.test.util import serialize
  import twisted.web2.dav.test.util
  
@@ -22,8 +22,48 @@
 +    (dav_namespace, "quota-used-bytes"          ),
 +)
  
-+live_properties = [lookupElement(qname)() for qname in DAVResource.liveProperties if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
 +
  #
  # See whether dead properties are available
  #
+@@ -49,6 +56,10 @@
+     """
+     PROPFIND, PROPPATCH requests
+     """
++
++    def liveProperties(self):
++        return [lookupElement(qname)() for qname in self.resource_class.liveProperties if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
++
+     def test_PROPFIND_basic(self):
+         """
+         PROPFIND request
+@@ -85,7 +96,7 @@
+                             self.fail("PROPFIND failed (status %s) to locate live properties: %s"
+                                       % (status.code, properties))
+ 
+-                        properties_to_find = [p.qname() for p in live_properties]
++                        properties_to_find = [p.qname() for p in self.liveProperties()]
+ 
+                         for property in properties:
+                             qname = property.qname()
+@@ -102,7 +113,7 @@
+             else:
+                 self.fail("No response for URI /")
+ 
+-        query = davxml.PropertyFind(davxml.PropertyContainer(*live_properties))
++        query = davxml.PropertyFind(davxml.PropertyContainer(*self.liveProperties()))
+ 
+         request = SimpleRequest(self.site, "PROPFIND", "/")
+ 
+@@ -146,9 +157,9 @@
+                               % (status.code, properties))
+ 
+                 if which.name == "allprop":
+-                    properties_to_find = [p.qname() for p in live_properties if not p.hidden]
++                    properties_to_find = [p.qname() for p in self.liveProperties() if not p.hidden]
+                 else:
+-                    properties_to_find = [p.qname() for p in live_properties]
++                    properties_to_find = [p.qname() for p in self.liveProperties()]
+ 
+                 for property in properties:
+                     qname = property.qname()

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/__init__.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/__init__.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -33,6 +33,7 @@
     "directory",
     "dropbox",
     "extensions",
+    "fileops",
     "ical",
     "icaldav",
     "index",

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/caldavxml.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/caldavxml.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -193,7 +193,7 @@
             self.filter_name = self.filter_name.encode("utf-8")
         self.defined     = not self.qualifier or (self.qualifier.qname() != (caldav_namespace, "is-not-defined"))
 
-    def match(self, item):
+    def match(self, item, access=None):
         """
         Returns True if the given calendar item (either a component, property or parameter value)
         matches this filter, False otherwise.
@@ -203,11 +203,11 @@
         # be negated by the caller
         if not self.defined: return True
 
-        if self.qualifier and not self.qualifier.match(item): return False
+        if self.qualifier and not self.qualifier.match(item, access): return False
 
         if len(self.filters) > 0:
             for filter in self.filters:
-                if filter._match(item):
+                if filter._match(item, access):
                     return True
             return False
         else:
@@ -477,12 +477,7 @@
         @param resource: the resource whose calendar data is to be returned.
         @return: an L{CalendarData} with the (filtered) calendar data.
         """
-        # Check for filtering or not
-        if self.children:
-            filtered = self.getFromICalendar(resource.iCalendar())
-            return CalendarData.fromCalendar(filtered)
-        else:
-            return resource.iCalendarXML()
+        return self.elementFromCalendar(resource.iCalendar())
 
     def elementFromCalendar(self, calendar):
         """
@@ -496,6 +491,138 @@
         filtered = self.getFromICalendar(calendar)
         return CalendarData.fromCalendar(filtered)
 
+    def elementFromResourceWithAccessRestrictions(self, resource, access):
+        """
+        Return a new CalendarData element comprised of the possibly filtered
+        calendar data from the specified resource. If no filter is being applied
+        read the data directly from the resource without parsing it. If a filter
+        is required, parse the iCal data and filter using this CalendarData.
+        
+        Also, apply appropriate access restriction filtering to the data.
+
+        @param resource: the resource whose calendar data is to be returned.
+        @param access: private event access restriction level.
+        @return: an L{CalendarData} with the (filtered) calendar data.
+        """
+        return self.elementFromCalendarWithAccessRestrictions(resource.iCalendar(), access)
+
+    def elementFromCalendarWithAccessRestrictions(self, calendar, access):
+        """
+        Return a new CalendarData element comprised of the possibly filtered
+        calendar.
+        
+        Also, apply appropriate access restriction filtering to the data.
+
+        @param calendar: the calendar that is to be filtered and returned.
+        @param access: private event access restriction level.
+        @return: an L{CalendarData} with the (filtered) calendar data.
+        """
+        
+        # Do normal filtering first
+        filtered_calendar = self.getFromICalendar(calendar)
+        
+        if access in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
+            # Create a CALDAV:calendar-data element with the appropriate iCalendar Component/Property
+            # filter in place for the access restriction in use
+            
+            extra_access = ()
+            if access == iComponent.ACCESS_RESTRICTED:
+                extra_access = (
+                    Property(name="SUMMARY"),
+                    Property(name="LOCATION"),
+                )
+
+            filter = CalendarData(
+                CalendarComponent(
+                    
+                    # VCALENDAR proeprties
+                    Property(name="PRODID"),
+                    Property(name="VERSION"),
+                    Property(name="CALSCALE"),
+                    Property(name=iComponent.ACCESS_PROPERTY),
+
+                    # VEVENT
+                    CalendarComponent(
+                        Property(name="UID"),
+                        Property(name="RECURRENCE-ID"),
+                        Property(name="SEQUENCE"),
+                        Property(name="DTSTAMP"),
+                        Property(name="STATUS"),
+                        Property(name="TRANSP"),
+                        Property(name="DTSTART"),
+                        Property(name="DTEND"),
+                        Property(name="DURATION"),
+                        Property(name="RRULE"),
+                        Property(name="RDATE"),
+                        Property(name="EXRULE"),
+                        Property(name="EXDATE"),
+                        *extra_access,
+                        **{"name":"VEVENT"}
+                    ),
+                    
+                    # VTODO
+                    CalendarComponent(
+                        Property(name="UID"),
+                        Property(name="RECURRENCE-ID"),
+                        Property(name="SEQUENCE"),
+                        Property(name="DTSTAMP"),
+                        Property(name="STATUS"),
+                        Property(name="DTSTART"),
+                        Property(name="COMPLETED"),
+                        Property(name="DUE"),
+                        Property(name="DURATION"),
+                        Property(name="RRULE"),
+                        Property(name="RDATE"),
+                        Property(name="EXRULE"),
+                        Property(name="EXDATE"),
+                        *extra_access,
+                        **{"name":"VTODO"}
+                    ),
+                    
+                    # VJOURNAL
+                    CalendarComponent(
+                        Property(name="UID"),
+                        Property(name="RECURRENCE-ID"),
+                        Property(name="SEQUENCE"),
+                        Property(name="DTSTAMP"),
+                        Property(name="STATUS"),
+                        Property(name="TRANSP"),
+                        Property(name="DTSTART"),
+                        Property(name="RRULE"),
+                        Property(name="RDATE"),
+                        Property(name="EXRULE"),
+                        Property(name="EXDATE"),
+                        *extra_access,
+                        **{"name":"VJOURNAL"}
+                    ),
+                    
+                    # VFREEBUSY
+                    CalendarComponent(
+                        Property(name="UID"),
+                        Property(name="DTSTAMP"),
+                        Property(name="DTSTART"),
+                        Property(name="DTEND"),
+                        Property(name="DURATION"),
+                        Property(name="FREEBUSY"),
+                        *extra_access,
+                        **{"name":"VFREEBUSY"}
+                    ),
+                    
+                    # VTIMEZONE
+                    CalendarComponent(
+                        AllProperties(),
+                        AllComponents(),
+                        name="VTIMEZONE",
+                    ),
+                    name="VCALENDAR",
+                ),
+            )
+
+            # Now "filter" the resource calendar data through the CALDAV:calendar-data element
+            return filter.elementFromCalendar(filtered_calendar)
+        else:
+            return CalendarData.fromCalendar(filtered_calendar)
+
     def getFromICalendar(self, calendar):
         """
         Returns a calendar object containing the data in the given calendar
@@ -530,6 +657,16 @@
         """
         Returns a calendar component derived from this element.
         """
+        data = self.calendarData()
+        if data:
+            return iComponent.fromString(data)
+        else:
+            return None
+
+    def calendarData(self):
+        """
+        Returns the calendar data derived from this element.
+        """
         for data in self.children:
             if not isinstance(data, davxml.PCDATAElement):
                 return None
@@ -537,7 +674,7 @@
                 # We guaranteed in __init__() that there is only one child...
                 break
 
-        return iComponent.fromString(str(data))
+        return str(data)
 
     def expandRecurrence(self, calendar):
         """
@@ -758,12 +895,16 @@
 
     allowed_children = { (caldav_namespace, "comp-filter"): (1, 1) }
 
-    def match(self, component):
+    def match(self, component, access=None):
         """
         Returns True if the given calendar component matches this filter, False
         otherwise.
         """
         
+        # We only care about certain access restrictions.
+        if access not in (iComponent.ACCESS_CONFIDENTIAL, iComponent.ACCESS_RESTRICTED):
+            access = None
+
         # We need to prepare ourselves for a time-range query by pre-calculating
         # the set of instances up to the latest time-range limit. That way we can
         # avoid having to do some form of recurrence expansion for each query sub-part.
@@ -775,7 +916,7 @@
         self.children[0].setInstances(instances)
 
         # <filter> contains exactly one <comp-filter>
-        return self.children[0].match(component)
+        return self.children[0].match(component, access)
 
     def valid(self):
         """
@@ -827,7 +968,7 @@
     }
     allowed_attributes = { "name": True }
 
-    def match(self, item):
+    def match(self, item, access):
         """
         Returns True if the given calendar item (which is a component)
         matches this filter, False otherwise.
@@ -843,20 +984,26 @@
 
         if len(self.filters) > 0:
             for filter in self.filters:
-                if filter._match(item):
+                if filter._match(item, access):
                     return True
             return False
         else:
             return True
 
-    def _match(self, component):
+    def _match(self, component, access):
         # At least one subcomponent must match (or is-not-defined is set)
         for subcomponent in component.subcomponents():
+            # If access restrictions are in force, restrict matching to specific components only.
+            # In particular do not match VALARM.
+            if access and subcomponent.name() not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VTIMEZONE",):
+                continue
+            
+            # Try to match the component name
             if isinstance(self.filter_name, str):
                 if subcomponent.name() != self.filter_name: continue
             else:
                 if subcomponent.name() not in self.filter_name: continue
-            if self.match(subcomponent): break
+            if self.match(subcomponent, access): break
         else:
             return not self.defined
         return self.defined
@@ -982,10 +1129,22 @@
     }
     allowed_attributes = { "name": True }
 
-    def _match(self, component):
+    def _match(self, component, access):
+        # When access restriction is in force, we need to only allow matches against the properties
+        # allowed by the access restriction level.
+        if access:
+            allowedProperties = iComponent.confidentialPropertiesMap.get(component.name(), None)
+            if allowedProperties and access == iComponent.ACCESS_RESTRICTED:
+                allowedProperties += iComponent.extraRestrictedProperties
+        else:
+            allowedProperties = None
+
         # At least one property must match (or is-not-defined is set)
         for property in component.properties():
-            if property.name() == self.filter_name and self.match(property): break
+            # Apply access restrictions, if any.
+            if allowedProperties is not None and property.name() not in allowedProperties:
+                continue
+            if property.name() == self.filter_name and self.match(property, access): break
         else:
             return not self.defined
         return self.defined
@@ -1038,7 +1197,7 @@
     }
     allowed_attributes = { "name": True }
 
-    def _match(self, property):
+    def _match(self, property, access):
         # We have to deal with the problem that the 'Native' form of a property
         # will be missing the TZID parameter due to the conversion performed. Converting
         # to non-native for the entire calendar object causes problems elsewhere, so its
@@ -1051,7 +1210,7 @@
         # At least one property must match (or is-not-defined is set)
         result = not self.defined
         for parameterName in property.params().keys():
-            if parameterName == self.filter_name and self.match(property.params()[parameterName]):
+            if parameterName == self.filter_name and self.match(property.params()[parameterName], access):
                 result = self.defined
                 break
 
@@ -1065,7 +1224,7 @@
     """
     name = "is-defined"
 
-    def match(self, component):
+    def match(self, component, access):
         return component is not None
 
 class IsNotDefined (CalDAVEmptyElement):
@@ -1075,7 +1234,7 @@
     """
     name = "is-not-defined"
 
-    def match(self, component):
+    def match(self, component, access):
         # Oddly, this needs always to return True so that it appears there is
         # a match - but we then "negate" the result if is-not-defined is set.
         # Actually this method should never be called as we special case the
@@ -1130,7 +1289,7 @@
         else:
             self.negate = False
 
-    def match(self, item):
+    def match(self, item, access):
         """
         Match the text for the item.
         If the item is a property, then match the property value,
@@ -1221,7 +1380,7 @@
         # No other tests
         return True
 
-    def match(self, property):
+    def match(self, property, access):
         """
         NB This is only called when doing a time-range match on a property.
         """

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/config.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/config.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -148,6 +148,7 @@
     #
     "EnableDropBox"      : False, # Calendar Drop Box
     "EnableNotifications": False, # Drop Box Notifications
+    "EnablePrivateEvents": False, # Private Events
 
     #
     # Implementation details

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/customxml.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/customxml.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -36,6 +36,10 @@
     "calendar-proxy",
 )
 
+calendarserver_private_events_compliance = (
+    "calendarserver-private-events",
+)
+
 class TwistedGUIDProperty (davxml.WebDAVTextElement):
     """
     Contains the GUID value for a directory record corresponding to a principal.
@@ -58,6 +62,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.

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendar.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendar.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -26,7 +26,7 @@
     "DirectoryCalendarHomeResource",
 ]
 
-from twisted.web2 import responsecode
+from twisted.internet.defer import succeed
 from twisted.web2.dav import davxml
 from twisted.web2.dav.util import joinURL
 from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
@@ -259,6 +259,9 @@
     # ACL
     ##
 
+    def owner(self, request):
+        return succeed(davxml.HRef(self.principalForRecord().principalURL()))
+
     def defaultAccessControlList(self):
         # FIXME: directory.principalCollection smells like a hack
         # See DirectoryPrincipalProvisioningResource.__init__()

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendaruserproxy.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/directory/calendaruserproxy.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -35,11 +35,10 @@
 from twisted.web2.dav.util import joinURL
 from twisted.web2.http import HTTPError, StatusResponse
 
-from twistedcaldav import caldavxml
-from twistedcaldav import customxml
 from twistedcaldav.config import config
 from twistedcaldav.extensions import DAVFile, DAVPrincipalResource
 from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
+from twistedcaldav.resource import CalDAVComplianceMixIn
 from twistedcaldav.sql import AbstractSQLDatabase
 from twistedcaldav.sql import db_prefix
 from twistedcaldav.static import AutoProvisioningFileMixIn
@@ -76,17 +75,11 @@
         # Permissions here are fixed, and are not subject to inherritance rules, etc.
         return succeed(self.defaultAccessControlList())
 
-class CalendarUserProxyPrincipalResource (AutoProvisioningFileMixIn, PermissionsMixIn, DAVPrincipalResource, DAVFile):
+class CalendarUserProxyPrincipalResource (CalDAVComplianceMixIn, AutoProvisioningFileMixIn, PermissionsMixIn, DAVPrincipalResource, DAVFile):
     """
     Calendar user proxy principal resource.
     """
 
-    def davComplianceClasses(self):
-        extra_compliance = caldavxml.caldav_compliance
-        if config.EnableProxyPrincipals:
-            extra_compliance += customxml.calendarserver_proxy_compliance
-        return tuple(super(CalendarUserProxyPrincipalResource, self).davComplianceClasses()) + extra_compliance
-
     def __init__(self, path, parent, proxyType):
         """
         @param path: the path to the file which will back this resource.

Copied: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/fileops.py (from rev 2151, CalendarServer/trunk/twistedcaldav/fileops.py)
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/fileops.py	                        (rev 0)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/fileops.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -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/release/CalendarServer-1.2-dev/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/ical.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/ical.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -162,6 +162,31 @@
     """
     X{iCalendar} component.
     """
+
+    # Private Event access levels.
+    ACCESS_PROPERTY     = "X-CALENDARSERVER-ACCESS"
+    ACCESS_PUBLIC       = "PUBLIC"
+    ACCESS_PRIVATE      = "PRIVATE"
+    ACCESS_CONFIDENTIAL = "CONFIDENTIAL"
+    ACCESS_RESTRICTED   = "RESTRICTED"
+
+    accessMap = {
+        "PUBLIC"       : ACCESS_PUBLIC,
+        "PRIVATE"      : ACCESS_PRIVATE,
+        "CONFIDENTIAL" : ACCESS_CONFIDENTIAL,
+        "RESTRICTED"   : ACCESS_RESTRICTED,
+    }
+
+    confidentialPropertiesMap = {
+        "VCALENDAR": ("PRODID", "VERSION", "CALSCALE", ACCESS_PROPERTY),
+        "VEVENT":    ("UID", "RECURRENCE-ID", "SEQUENCE", "DTSTAMP", "STATUS", "TRANSP", "DTSTART", "DTEND", "DURATION", "RRULE", "RDATE", "EXRULE", "EXDATE", ),
+        "VTODO":     ("UID", "RECURRENCE-ID", "SEQUENCE", "DTSTAMP", "STATUS", "DTSTART", "COMPLETED", "DUE", "DURATION", "RRULE", "RDATE", "EXRULE", "EXDATE", ),
+        "VJOURNAL":  ("UID", "RECURRENCE-ID", "SEQUENCE", "DTSTAMP", "STATUS", "DTSTART", "RRULE", "RDATE", "EXRULE", "EXDATE", ),
+        "VFREEBUSY": ("UID", "DTSTAMP", "DTSTART", "DTEND", "DURATION", "FREEBUSY", ),
+        "VTIMEZONE": None,
+    }
+    extraRestrictedProperties = ("SUMMARY", "LOCATION",)
+
     @classmethod
     def fromString(clazz, string):
         """
@@ -366,6 +391,18 @@
         
         return None
     
+    def accessLevel(self, default=ACCESS_PUBLIC):
+        """
+        Return the access level for this component.
+        @return: the access level for the calendar data.
+        """
+        assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
+        
+        access = self.propertyValue(Component.ACCESS_PROPERTY)
+        if access:
+            access = access.upper()
+        return Component.accessMap.get(access, default)
+    
     def duplicate(self):
         """
         Duplicate this object and all its contents.

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/__init__.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/__init__.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/__init__.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -26,6 +26,7 @@
 __all__ = [
     "copymove",
     "delete",
+    "get",
     "mkcalendar",
     "mkcol",
     "put",

Copied: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/get.py (from rev 2151, CalendarServer/trunk/twistedcaldav/method/get.py)
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/get.py	                        (rev 0)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/get.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -0,0 +1,72 @@
+##
+# 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.
+##
+
+"""
+CalDAV GET method.
+"""
+
+__all__ = ["http_GET"]
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.web2.dav import davxml
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http_headers import MimeType
+from twisted.web2.stream import MemoryStream
+
+from twistedcaldav import caldavxml
+from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.ical import Component
+
+def http_GET(self, request):
+
+    # Look for calendar access restriction on existing resource.
+    if self.exists():
+        try:
+            access = self.readDeadProperty(TwistedCalendarAccessProperty)
+        except HTTPError:
+            access = None
+            
+        if access in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+    
+            # Check authorization first
+            d = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+            yield d
+            d.getResult()
+
+            # Non DAV:owner's have limited access to the data
+            d = waitForDeferred(self.isOwner(request))
+            yield d
+            isowner = d.getResult()
+            
+            if not isowner:
+
+                # Now "filter" the resource calendar data through the CALDAV:calendar-data element and apply
+                # access restrictions to the data.
+                caldata = caldavxml.CalendarData().elementFromResourceWithAccessRestrictions(self, access).calendarData()
+
+                response = Response()
+                response.stream = MemoryStream(caldata)
+                response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+                yield response
+                return
+
+    # Do normal GET behavior
+    d = waitForDeferred(super(CalDAVFile, self).http_GET(request))
+    yield d
+    yield d.getResult()
+
+http_GET = deferredGenerator(http_GET)

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/put_common.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/put_common.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -34,7 +34,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
@@ -49,11 +49,17 @@
 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
 from twistedcaldav.resource import CalDAVResource
 
+ at deferredGenerator
 def storeCalendarObjectResource(
     request,
     sourcecal, destinationcal,
@@ -156,7 +162,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())
 
@@ -263,6 +269,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
@@ -310,6 +349,7 @@
         Handle validation operations here.
         """
         reserved = False
+        access = None
         if destinationcal:
             # Valid resource name check
             result, message = validResourceName()
@@ -366,6 +406,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()
             
@@ -442,7 +488,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
@@ -451,7 +497,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")
     
         """
@@ -470,10 +516,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()
@@ -490,8 +536,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.
@@ -634,5 +690,3 @@
         # if the rollback has already ocurred or changes already committed.
         rollback.Rollback()
         raise
-
-storeCalendarObjectResource = deferredGenerator(storeCalendarObjectResource)

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_calquery.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_calquery.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_calquery.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -33,6 +33,7 @@
 from twisted.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.method import report_common
 
 import urllib
@@ -106,7 +107,7 @@
         @param uri: the uri for the calendar collecton resource.
         """
         
-        def queryCalendarObjectResource(resource, uri, name, calendar, query_ok = False):
+        def queryCalendarObjectResource(resource, uri, name, calendar, query_ok=False, isowner=True):
             """
             Run a query on the specified calendar.
             @param resource: the L{CalDAVFile} for the calendar.
@@ -115,7 +116,16 @@
             @param calendar: the L{Component} calendar read from the resource.
             """
             
-            if query_ok or filter.match(calendar):
+            # Handle private events access restrictions
+            if not isowner:
+                try:
+                    access = resource.readDeadProperty(TwistedCalendarAccessProperty)
+                except HTTPError:
+                    access = None
+            else:
+                access = None
+
+            if query_ok or filter.match(calendar, access):
                 # Check size of results is within limit
                 matchcount[0] += 1
                 if matchcount[0] > max_number_of_results:
@@ -126,7 +136,7 @@
                 else:
                     href = davxml.HRef.fromString(uri)
             
-                return report_common.responseForHref(request, responses, href, resource, calendar, propertiesForResource, query)
+                return report_common.responseForHref(request, responses, href, resource, calendar, propertiesForResource, query, isowner)
             else:
                 return succeed(None)
     
@@ -148,7 +158,12 @@
             filteredaces = waitForDeferred(calresource.inheritedACEsforChildren(request))
             yield filteredaces
             filteredaces = filteredaces.getResult()
-        
+
+            # Check private events access status
+            d = waitForDeferred(calresource.isOwner(request))
+            yield d
+            isowner = d.getResult()
+
             # Check for disabled access
             if filteredaces is not None:
                 # See whether the filter is valid for an index only query
@@ -185,7 +200,7 @@
                     else:
                         calendar = None
                     
-                    d = waitForDeferred(queryCalendarObjectResource(child, uri, child_uri_name, calendar, query_ok = index_query_ok))
+                    d = waitForDeferred(queryCalendarObjectResource(child, uri, child_uri_name, calendar, query_ok = index_query_ok, isowner=isowner))
                     yield d
                     d.getResult()
         else:
@@ -207,6 +222,11 @@
                     tz = tz.getResult()
                     filter.settimezone(tz)
 
+            # Check private events access status
+            d = waitForDeferred(calresource.isOwner(request))
+            yield d
+            isowner = d.getResult()
+
             calendar = calresource.iCalendar()
             d = waitForDeferred(queryCalendarObjectResource(calresource, uri, None, calendar))
             yield d

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_common.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_common.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -43,6 +43,7 @@
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap
 from twistedcaldav.ical import Component, Property, iCalendarProductID
 from twistedcaldav.instance import InstanceList
@@ -95,7 +96,7 @@
 
 applyToCalendarCollections = deferredGenerator(applyToCalendarCollections)
 
-def responseForHref(request, responses, href, resource, calendar, propertiesForResource, propertyreq):
+def responseForHref(request, responses, href, resource, calendar, propertiesForResource, propertyreq, isowner=True):
     """
     Create an appropriate property status response for the given resource.
 
@@ -104,10 +105,12 @@
     @param href: the L{HRef} element of the resource being targetted.
     @param resource: the L{CalDAVFile} for the targetted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
-                     if the calendar has not already been read in, in which case the resource
-                     will be used to get the calendar if needed.
+        if the calendar has not already been read in, in which case the resource
+        will be used to get the calendar if needed.
     @param propertiesForResource: the method to use to get the list of properties to return.
     @param propertyreq: the L{PropertyContainer} element for the properties of interest.
+    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
+        C{False} otherwise.
     """
 
     def _defer(properties_by_status):
@@ -124,38 +127,42 @@
                     )
                 )
 
-    d = propertiesForResource(request, propertyreq, resource, calendar)
+    d = propertiesForResource(request, propertyreq, resource, calendar, isowner)
     d.addCallback(_defer)
     return d
 
-def allPropertiesForResource(request, prop, resource, calendar=None): #@UnusedVariable
+def allPropertiesForResource(request, prop, resource, calendar=None, isowner=True): #@UnusedVariable
     """
     Return all (non-hidden) properties for the specified resource.
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
     @param resource: the L{CalDAVFile} for the targetted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
-                     if the calendar has not already been read in, in which case the resource
-                     will be used to get the calendar if needed.
+        if the calendar has not already been read in, in which case the resource
+        will be used to get the calendar if needed.
+    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
+        C{False} otherwise.
     @return: a map of OK and NOT FOUND property values.
     """
 
     def _defer(props):
-        return _namedPropertiesForResource(request, props, resource, calendar)
+        return _namedPropertiesForResource(request, props, resource, calendar, isowner)
 
     d = resource.listAllprop(request)
     d.addCallback(_defer)
     return d
 
-def propertyNamesForResource(request, prop, resource, calendar=None): #@UnusedVariable
+def propertyNamesForResource(request, prop, resource, calendar=None, isowner=True): #@UnusedVariable
     """
     Return property names for all properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
     @param resource: the L{CalDAVFile} for the targetted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
-                     if the calendar has not already been read in, in which case the resource
-                     will be used to get the calendar if needed.
+        if the calendar has not already been read in, in which case the resource
+        will be used to get the calendar if needed.
+    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
+        C{False} otherwise.
     @return: a map of OK and NOT FOUND property values.
     """
 
@@ -169,19 +176,21 @@
     d.addCallback(_defer)
     return d
 
-def propertyListForResource(request, prop, resource, calendar=None):
+def propertyListForResource(request, prop, resource, calendar=None, isowner=True):
     """
     Return the specified properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
     @param resource: the L{CalDAVFile} for the targetted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
-                     if the calendar has not already been read in, in which case the resource
-                     will be used to get the calendar if needed.
+        if the calendar has not already been read in, in which case the resource
+        will be used to get the calendar if needed.
+    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
+        C{False} otherwise.
     @return: a map of OK and NOT FOUND property values.
     """
     
-    return _namedPropertiesForResource(request, prop.children, resource, calendar)
+    return _namedPropertiesForResource(request, prop.children, resource, calendar, isowner)
 
 def validPropertyListCalendarDataTypeVersion(prop):
     """
@@ -206,15 +215,17 @@
 
     return result, message, generate_calendar_data
 
-def _namedPropertiesForResource(request, props, resource, calendar=None):
+def _namedPropertiesForResource(request, props, resource, calendar=None, isowner=True):
     """
     Return the specified properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param props: a list of property elements or qname tuples for the properties of interest.
     @param resource: the L{CalDAVFile} for the targetted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
-                     if the calendar has not already been read in, in which case the resource
-                     will be used to get the calendar if needed.
+        if the calendar has not already been read in, in which case the resource
+        will be used to get the calendar if needed.
+    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
+        C{False} otherwise.
     @return: a map of OK and NOT FOUND property values.
     """
     properties_by_status = {
@@ -224,10 +235,19 @@
     
     for property in props:
         if isinstance(property, caldavxml.CalendarData):
+            # Handle private events access restrictions
+            if not isowner:
+                try:
+                    access = resource.readDeadProperty(TwistedCalendarAccessProperty)
+                except HTTPError:
+                    access = None
+            else:
+                access = None
+
             if calendar:
-                propvalue = property.elementFromCalendar(calendar)
+                propvalue = property.elementFromCalendarWithAccessRestrictions(calendar, access)
             else:
-                propvalue = property.elementFromResource(resource)
+                propvalue = property.elementFromResourceWithAccessRestrictions(resource, access)
             if propvalue is None:
                 raise ValueError("Invalid CalDAV:calendar-data for request: %r" % (property,))
             properties_by_status[responsecode.OK].append(propvalue)
@@ -360,7 +380,7 @@
                 elif (calendar.getOrganizer() is None) and same_calendar_user:
                     continue
 
-        if filter.match(calendar):
+        if filter.match(calendar, None):
             # Check size of results is within limit
             matchtotal += 1
             if matchtotal > max_number_of_matches:

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_multiget.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_multiget.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/method/report_multiget.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -65,17 +65,15 @@
     
     if propertyreq.qname() == ("DAV:", "allprop"):
         propertiesForResource = report_common.allPropertiesForResource
-        generate_calendar_data = False
 
     elif propertyreq.qname() == ("DAV:", "propname"):
         propertiesForResource = report_common.propertyNamesForResource
-        generate_calendar_data = False
 
     elif propertyreq.qname() == ("DAV:", "prop"):
         propertiesForResource = report_common.propertyListForResource
         
         # Verify that any calendar-data element matches what we can handle
-        result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(propertyreq)
+        result, message, _ignore = report_common.validPropertyListCalendarDataTypeVersion(propertyreq)
         if not result:
             log.err(message)
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
@@ -115,14 +113,21 @@
         # Check for disabled access
         if filteredaces is None:
             disabled = True
+            
+        # Check private events access status
+        d = waitForDeferred(self.isOwner(request))
+        yield d
+        isowner = d.getResult()
 
     elif self.isCollection():
         requestURIis = "collection"
         filteredaces = None
         lastParent = None
+        isowner = None
     else:
         requestURIis = "resource"
         filteredaces = None
+        isowner = None
 
     if not disabled:
         
@@ -171,7 +176,7 @@
 
             # Get properties for all valid readable resources
             for resource, href in ok_resources:
-                d = waitForDeferred(report_common.responseForHref(request, responses, davxml.HRef.fromString(href), resource, None, propertiesForResource, propertyreq))
+                d = waitForDeferred(report_common.responseForHref(request, responses, davxml.HRef.fromString(href), resource, None, propertiesForResource, propertyreq, isowner=isowner))
                 yield d
                 d.getResult()
     
@@ -235,7 +240,11 @@
                         filteredaces = waitForDeferred(parent.inheritedACEsforChildren(request))
                         yield filteredaces
                         filteredaces = filteredaces.getResult()
-        
+
+                        # Check private events access status
+                        d = waitForDeferred(parent.isOwner(request))
+                        yield d
+                        isowner = d.getResult()
                 else:
                     name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                     if (resource_uri != request.uri) or not self.exists():
@@ -256,6 +265,11 @@
                     filteredaces = waitForDeferred(parent.inheritedACEsforChildren(request))
                     yield filteredaces
                     filteredaces = filteredaces.getResult()
+
+                    # Check private events access status
+                    d = waitForDeferred(parent.isOwner(request))
+                    yield d
+                    isowner = d.getResult()
         
                 # Check privileges - must have at least DAV:read
                 try:
@@ -266,7 +280,7 @@
                     responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                     continue
         
-                d = waitForDeferred(report_common.responseForHref(request, responses, href, child, None, propertiesForResource, propertyreq))
+                d = waitForDeferred(report_common.responseForHref(request, responses, href, child, None, propertiesForResource, propertyreq, isowner=isowner))
                 yield d
                 d.getResult()
 

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/query/calendarquery.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/query/calendarquery.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/query/calendarquery.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -59,6 +59,10 @@
     assert vcalfilter.filter_name == "VCALENDAR"
     
     if len(vcalfilter.children) > 0:
+        # Only comp-filters are handled
+        for _ignore in [x for x in vcalfilter.children if not isinstance(x, caldavxml.ComponentFilter)]:
+            raise ValueError
+        
         return compfilterListExpression(vcalfilter.children)
     else:
         return expression.allExpression()
@@ -113,7 +117,7 @@
         
     # Handle embedded components - we do not right now as our Index does not handle them
     comps = []
-    for c in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]:
+    for _ignore in [x for x in compfilter.filters if isinstance(x, caldavxml.ComponentFilter)]:
         raise ValueError
     if len(comps) > 1:
         compsExpression = expression.orExpression[comps]
@@ -164,7 +168,7 @@
     
     # Handle embedded parameters - we do not right now as our Index does not handle them
     params = []
-    for p in propfilter.filters:
+    for _ignore in propfilter.filters:
         raise ValueError
     if len(params) > 1:
         paramsExpression = expression.orExpression[params]

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/resource.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/resource.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -21,6 +21,7 @@
 """
 
 __all__ = [
+    "CalDAVComplianceMixIn",
     "CalDAVResource",
     "CalendarPrincipalCollectionResource",
     "CalendarPrincipalResource",
@@ -32,6 +33,8 @@
 
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, maybeDeferred, succeed
+from twisted.internet.defer import waitForDeferred
+from twisted.internet.defer import deferredGenerator
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.idav import IDAVPrincipalCollectionResource
@@ -49,7 +52,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
@@ -61,7 +66,18 @@
 else:
     serverVersion = twisted.web2.server.VERSION + " TwistedCalDAV/?"
 
-class CalDAVResource (DAVResource):
+class CalDAVComplianceMixIn(object):
+
+    def davComplianceClasses(self):
+        extra_compliance = caldavxml.caldav_compliance
+        if config.EnableProxyPrincipals:
+            extra_compliance += customxml.calendarserver_proxy_compliance
+        if config.EnablePrivateEvents:
+            extra_compliance += customxml.calendarserver_private_events_compliance
+        return tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses()) + extra_compliance
+
+
+class CalDAVResource (CalDAVComplianceMixIn, DAVResource):
     """
     CalDAV resource.
 
@@ -119,13 +135,8 @@
     # WebDAV
     ##
 
-    def davComplianceClasses(self):
-        extra_compliance = caldavxml.caldav_compliance
-        if config.EnableProxyPrincipals:
-            extra_compliance += customxml.calendarserver_proxy_compliance
-        return tuple(super(CalDAVResource, self).davComplianceClasses()) + extra_compliance
-
     liveProperties = DAVResource.liveProperties + (
+        (dav_namespace,    "owner"),               # Private Events needs this but it is also OK to return empty
         (caldav_namespace, "supported-calendar-component-set"),
         (caldav_namespace, "supported-calendar-data"         ),
     )
@@ -142,7 +153,13 @@
 
         namespace, name = qname
 
-        if namespace == caldav_namespace:
+        if namespace == dav_namespace:
+            if name == "owner":
+                d = self.owner(request)
+                d.addCallback(lambda x: davxml.Owner(x))
+                return d
+            
+        elif namespace == caldav_namespace:
             if name == "supported-calendar-component-set":
                 # CalDAV-access-09, section 5.2.3
                 if self.hasDeadProperty(qname):
@@ -222,12 +239,83 @@
         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):
+        """
+        Return the DAV:owner property value (MUST be a DAV:href or None).
+        """
+        d = waitForDeferred(self.locateParent(request, request.urlForResource(self)))
+        yield d
+        parent = d.getResult()
+        if parent and isinstance(parent, CalDAVResource):
+            d = waitForDeferred(parent.owner(request))
+            yield d
+            yield d.getResult()
+        else:
+            yield None
+
+    @deferredGenerator
+    def isOwner(self, request):
+        """
+        Determine whether the DAV:owner of this resource matches the currently authorized principal
+        in the request.
+        """
+
+        d = waitForDeferred(self.owner(request))
+        yield d
+        owner = d.getResult()
+        result = (davxml.Principal(owner) == self.currentPrincipal(request))
+        yield result
+ 
     ##
     # CalDAV
     ##
@@ -473,7 +561,7 @@
             ),
         )
 
-class CalendarPrincipalResource (DAVPrincipalResource):
+class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVPrincipalResource):
     """
     CalDAV principal resource.
 
@@ -481,12 +569,6 @@
     """
     implements(ICalendarPrincipalResource)
 
-    def davComplianceClasses(self):
-        extra_compliance = caldavxml.caldav_compliance
-        if config.EnableProxyPrincipals:
-            extra_compliance += customxml.calendarserver_proxy_compliance
-        return tuple(super(CalendarPrincipalResource, self).davComplianceClasses()) + extra_compliance
-
     liveProperties = tuple(DAVPrincipalResource.liveProperties) + (
         (caldav_namespace, "calendar-home-set"        ),
         (caldav_namespace, "calendar-user-address-set"),

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/schedule.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/schedule.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -38,6 +38,7 @@
 from twisted.web2.dav.util import joinURL
 
 from twistedcaldav import caldavxml
+from twistedcaldav import customxml
 from twistedcaldav import itip
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.caldavxml import caldav_namespace, TimeRange
@@ -273,6 +274,11 @@
         if not calendar.isValidITIP():
             log.err("POST request must have a calendar component that satisfies iTIP requirements: %s" % (calendar,))
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+        # X-CALENDARSERVER-ACCESS is not allowed in Outbox POSTs
+        if calendar.hasProperty(Component.ACCESS_PROPERTY):
+            logging.err("X-CALENDARSERVER-ACCESS not allowed in a calendar component POST request: %s" % (calendar,), system="CalDAV Outbox POST")
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (calendarserver_namespace, "no-access-restrictions")))
     
         # Verify that the ORGANIZER's cu address maps to the request.uri
         outboxURL = None

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/static.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/static.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -798,5 +798,6 @@
 # FIXME: Little bit of a circular dependency here...
 twistedcaldav.method.copymove.CalDAVFile = CalDAVFile
 twistedcaldav.method.delete.CalDAVFile   = CalDAVFile
+twistedcaldav.method.get.CalDAVFile      = CalDAVFile
 twistedcaldav.method.mkcol.CalDAVFile    = CalDAVFile
 twistedcaldav.method.put.CalDAVFile      = CalDAVFile

Modified: CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/test/test_xml.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/test/test_xml.py	2008-02-19 17:13:08 UTC (rev 2162)
+++ CalendarServer/branches/release/CalendarServer-1.2-dev/twistedcaldav/test/test_xml.py	2008-02-19 19:20:48 UTC (rev 2163)
@@ -48,7 +48,7 @@
                     name=component_name
                 ),
                 name="VCALENDAR"
-            ).match(self.calendar):
+            ).match(self.calendar, None):
                 self.fail("Calendar has %s%s?" % (no, component_name))
 
     def test_PropertyFilter(self):
@@ -70,7 +70,7 @@
                     name="VEVENT"
                 ),
                 name="VCALENDAR"
-            ).match(self.calendar):
+            ).match(self.calendar, None):
                 self.fail("Calendar has %sVEVENT with %s?" % (no, property_name))
 
     def test_ParameterFilter(self):
@@ -101,7 +101,7 @@
                     name="VEVENT"
                 ),
                 name="VCALENDAR"
-            ).match(self.calendar):
+            ).match(self.calendar, None):
                 self.fail("Calendar has %sVEVENT with UID %s? (caseless=%s)" % (no, uid, caseless))
 
     def test_TimeRange(self):

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080219/ec80bf0c/attachment-0001.html 


More information about the calendarserver-changes mailing list