[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