[CalendarServer-changes] [5226] CalendarServer/branches/users/cdaboo/shared-calendars-5187
source_changes at macosforge.org
source_changes at macosforge.org
Mon Mar 1 13:23:11 PST 2010
Revision: 5226
http://trac.macosforge.org/projects/calendarserver/changeset/5226
Author: cdaboo at apple.com
Date: 2010-03-01 13:23:11 -0800 (Mon, 01 Mar 2010)
Log Message:
-----------
Implementation of per-user WebDAV properties. Not hooked up to anything yet.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/auth.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -96,7 +96,12 @@
pswd = str(pcreds.authnPrincipal.readDeadProperty(TwistedPasswordProperty))
d = defer.maybeDeferred(credentials.checkPassword, pswd)
- d.addCallback(self._cbPasswordMatch, (pcreds.authnPrincipal.principalURL(), pcreds.authzPrincipal.principalURL()))
+ d.addCallback(self._cbPasswordMatch, (
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
return d
##
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/noneprops.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -49,19 +49,19 @@
def __init__(self, resource):
pass
- def get(self, qname):
+ def get(self, qname, uid=None):
raise HTTPError(StatusResponse(responsecode.NOT_FOUND, "No such property: {%s}%s" % qname))
- def set(self, property):
+ def set(self, property, uid=None):
raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Permission denied for setting property: %s" % (property,)))
- def delete(self, qname):
+ def delete(self, qname, uid=None):
# RFC 2518 Section 12.13.1 says that removal of
# non-existing property is not an error.
pass
- def contains(self, qname):
+ def contains(self, qname, uid=None):
return False
- def list(self):
+ def list(self, uid=None):
return ()
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/test/test_xattrprops.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -81,40 +81,42 @@
self._forbiddenTest('get')
- def _makeValue(self):
+ def _makeValue(self, uid=None):
"""
Create and return any old WebDAVDocument for use by the get tests.
"""
- element = Depth("0")
+ element = Depth(uid if uid is not None else "0")
document = WebDAVDocument(element)
return document
- def _setValue(self, originalDocument, value):
+ def _setValue(self, originalDocument, value, uid=None):
element = originalDocument.root_element
attribute = (
self.propertyStore.deadPropertyXattrPrefix +
+ (uid if uid is not None else "") +
"{%s}%s" % element.qname())
self.attrs[attribute] = value
- def _getValue(self, originalDocument):
+ def _getValue(self, originalDocument, uid=None):
element = originalDocument.root_element
attribute = (
self.propertyStore.deadPropertyXattrPrefix +
+ (uid if uid is not None else "") +
"{%s}%s" % element.qname())
return self.attrs[attribute]
- def _checkValue(self, originalDocument):
+ def _checkValue(self, originalDocument, uid=None):
property = originalDocument.root_element.qname()
# Try to load it via xattrPropertyStore.get
- loadedDocument = self.propertyStore.get(property)
+ loadedDocument = self.propertyStore.get(property, uid)
# XXX Why isn't this a WebDAVDocument?
self.assertIsInstance(loadedDocument, Depth)
- self.assertEquals(str(loadedDocument), "0")
+ self.assertEquals(str(loadedDocument), uid if uid else "0")
def test_getXML(self):
@@ -306,3 +308,99 @@
# Make sure that the status is FORBIDDEN, a roughly reasonable mapping
# of the EPERM failure.
self.assertEquals(error.response.code, FORBIDDEN)
+
+ def test_get_uids(self):
+ """
+ L{xattrPropertyStore.get} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self._setValue(document, document.toxml(), uid=uid)
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self._checkValue(document, uid=uid)
+
+
+ def test_set_uids(self):
+ """
+ L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.propertyStore.set(document.root_element, uid=uid)
+ self.assertEquals(
+ decompress(self._getValue(document, uid)), document.toxml())
+
+ def test_delete_uids(self):
+ """
+ L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
+ compressed XML document representing it in an extended attribute.
+ """
+
+ for delete_uid in (None, "123", "456",):
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.propertyStore.set(document.root_element, uid=uid)
+ self.propertyStore.delete(document.root_element.qname(), uid=delete_uid)
+ self.assertRaises(KeyError, self._getValue, document, uid=delete_uid)
+ for uid in (None, "123", "456",):
+ if uid == delete_uid:
+ continue
+ document = self._makeValue(uid)
+ self.assertEquals(
+ decompress(self._getValue(document, uid)), document.toxml())
+
+ def test_contains_uids(self):
+ """
+ L{xattrPropertyStore.contains} returns C{True} if the given property
+ has a value, C{False} otherwise.
+ """
+ for uid in (None, "123", "456",):
+ document = self._makeValue(uid)
+ self.assertFalse(
+ self.propertyStore.contains(document.root_element.qname(), uid=uid))
+ self._setValue(document, document.toxml(), uid=uid)
+ self.assertTrue(
+ self.propertyStore.contains(document.root_element.qname(), uid=uid))
+
+ def test_list_uids(self):
+ """
+ L{xattrPropertyStore.list} returns a C{list} of property names
+ associated with the wrapped file.
+ """
+ prefix = self.propertyStore.deadPropertyXattrPrefix
+ for uid in (None, "123", "456",):
+ user = uid if uid is not None else ""
+ self.attrs[prefix + '%s{foo}bar' % (user,)] = 'baz%s' % (user,)
+ self.attrs[prefix + '%s{bar}baz' % (user,)] = 'quux%s' % (user,)
+ self.attrs[prefix + '%s{moo}mar%s' % (user, user,)] = 'quux%s' % (user,)
+
+ for uid in (None, "123", "456",):
+ user = uid if uid is not None else ""
+ self.assertEquals(
+ set(self.propertyStore.list(uid)),
+ set([
+ (u'foo', u'bar'),
+ (u'bar', u'baz'),
+ (u'moo', u'mar%s' % (user,)),
+ ]))
+
+ self.assertEquals(
+ set(self.propertyStore.list(filterByUID=False)),
+ set([
+ (u'foo', u'bar', None),
+ (u'bar', u'baz', None),
+ (u'moo', u'mar', None),
+ (u'foo', u'bar', "123"),
+ (u'bar', u'baz', "123"),
+ (u'moo', u'mar123', "123"),
+ (u'foo', u'bar', "456"),
+ (u'bar', u'baz', "456"),
+ (u'moo', u'mar456', "456"),
+ ]))
+
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twext/web2/dav/xattrprops.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -84,20 +84,29 @@
if sys.platform == "linux2":
deadPropertyXattrPrefix = "user."
- def _encode(clazz, name):
+ def _encode(clazz, name, uid=None):
result = urllib.quote("{%s}%s" % name, safe='{}:')
+ if uid:
+ result = uid + result
r = clazz.deadPropertyXattrPrefix + result
return r
def _decode(clazz, name):
name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
- index = name.find("}")
+ index1 = name.find("{")
+ index2 = name.find("}")
- if (index is -1 or not len(name) > index or not name[0] == "{"):
+ if (index1 is -1 or index2 is -1 or not len(name) > index2):
raise ValueError("Invalid encoded name: %r" % (name,))
+ if index1 == 0:
+ uid = None
+ else:
+ uid = name[:index1]
+ propnamespace = name[index1+1:index2]
+ propname = name[index2+1:]
- return (name[1:index], name[index+1:])
+ return (propnamespace, propname, uid)
_encode = classmethod(_encode)
_decode = classmethod(_decode)
@@ -107,7 +116,7 @@
self.attrs = xattr.xattr(self.resource.fp.path)
- def get(self, qname):
+ def get(self, qname, uid=None):
"""
Retrieve the value of a property stored as an extended attribute on the
wrapped path.
@@ -115,6 +124,8 @@
@param qname: The property to retrieve as a two-tuple of namespace URI
and local name.
+ @param uid: The per-user identifier for per user properties.
+
@raise HTTPError: If there is no value associated with the given
property.
@@ -122,7 +133,7 @@
given property.
"""
try:
- data = self.attrs.get(self._encode(qname))
+ data = self.attrs.get(self._encode(qname, uid))
except KeyError:
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
@@ -176,13 +187,15 @@
return doc.root_element
- def set(self, property):
+ def set(self, property, uid=None):
"""
Store the given property as an extended attribute on the wrapped path.
+ @param uid: The per-user identifier for per user properties.
+
@param property: A L{WebDAVElement} to store.
"""
- key = self._encode(property.qname())
+ key = self._encode(property.qname(), uid)
value = compress(property.toxml())
untilConcludes(setitem, self.attrs, key, value)
@@ -190,15 +203,17 @@
self.resource.fp.restat()
- def delete(self, qname):
+ def delete(self, qname, uid=None):
"""
Remove the extended attribute from the wrapped path which stores the
property given by C{qname}.
+ @param uid: The per-user identifier for per user properties.
+
@param qname: The property to delete as a two-tuple of namespace URI
and local name.
"""
- key = self._encode(qname)
+ key = self._encode(qname, uid)
try:
try:
self.attrs.remove(key)
@@ -214,7 +229,7 @@
"Unable to delete property: " + key))
- def contains(self, qname):
+ def contains(self, qname, uid=None):
"""
Determine whether the property given by C{qname} is stored in an
extended attribute of the wrapped path.
@@ -222,9 +237,11 @@
@param qname: The property to look up as a two-tuple of namespace URI
and local name.
+ @param uid: The per-user identifier for per user properties.
+
@return: C{True} if the property exists, C{False} otherwise.
"""
- key = self._encode(qname)
+ key = self._encode(qname, uid)
try:
self.attrs.get(key)
except KeyError:
@@ -240,11 +257,13 @@
return True
- def list(self):
+ def list(self, uid=None, filterByUID=True):
"""
Enumerate the property names stored in extended attributes of the
wrapped path.
+ @param uid: The per-user identifier for per user properties.
+
@return: A C{list} of property names as two-tuples of namespace URI and
local name.
"""
@@ -257,8 +276,16 @@
statusForFailure(Failure()),
"Unable to list properties: " + self.resource.fp.path))
else:
- return [
+ results = [
self._decode(name)
- for name
- in attrs
- if name.startswith(prefix)]
+ for name in attrs
+ if name.startswith(prefix)
+ ]
+ if filterByUID:
+ return [
+ (namespace, name)
+ for namespace, name, propuid in results
+ if propuid == uid
+ ]
+ else:
+ return results
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/authkerb.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -179,7 +179,12 @@
self.log_error("%s" % (ex[0],))
raise error.UnauthorizedLogin("Bad credentials for: %s (%s: %s)" % (pcreds.authnURI, ex[0], ex[1],))
else:
- return succeed((pcreds.authnURI, pcreds.authzURI,))
+ return succeed((
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
@@ -307,7 +312,12 @@
creds = pcreds.credentials
if isinstance(creds, NegotiateCredentials):
- return succeed((pcreds.authnURI, pcreds.authzURI,))
+ return succeed((
+ pcreds.authnPrincipal.principalURL(),
+ pcreds.authzPrincipal.principalURL(),
+ pcreds.authnPrincipal,
+ pcreds.authzPrincipal,
+ ))
raise error.UnauthorizedLogin("Bad credentials for: %s" % (pcreds.authnURI,))
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/customxml.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -579,6 +579,87 @@
# Notifications
##
+class SharedOwner (davxml.WebDAVEmptyElement):
+ """
+ Denotes a shared collection.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-owner"
+
+class Shared (davxml.WebDAVEmptyElement):
+ """
+ Denotes a shared collection.
+ """
+ namespace = calendarserver_namespace
+ name = "shared"
+
+class Subscribed (davxml.WebDAVEmptyElement):
+ """
+ Denotes a subscribed calendar collection.
+ """
+ namespace = calendarserver_namespace
+ name = "subscribed"
+
+class SharedURL (davxml.WebDAVTextElement):
+ """
+ The source url for a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-url"
+
+class SharedAcceptEmailNotification (davxml.WebDAVTextElement):
+ """
+ The accept email flag for a shared calendar.
+ """
+ namespace = calendarserver_namespace
+ name = "shared-accept-email-notification"
+
+class Birthday (davxml.WebDAVEmptyElement):
+ """
+ Denotes a birthday calendar collection.
+ """
+ namespace = calendarserver_namespace
+ name = "birthday"
+
+class InviteShare (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "share"
+
+ allowed_children = {
+ (calendarserver_namespace, "set" ) : (0, None),
+ }
+
+class InviteSet (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "set"
+
+ allowed_children = {
+ (calendarserver_namespace, "summary" ) : (0, 1),
+ (calendarserver_namespace, "attendee" ) : (1, 1),
+ (calendarserver_namespace, "read" ) : (0, 1),
+ (calendarserver_namespace, "read-write" ): (0, 1),
+ }
+
+class InviteRemove (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "remove"
+
+ allowed_children = {
+ (calendarserver_namespace, "attendee" ) : (1, 1),
+ (calendarserver_namespace, "read" ) : (0, 1),
+ (calendarserver_namespace, "read-write" ): (0, 1),
+ }
+
+class InviteUser (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "user"
+
+ allowed_children = {
+ (calendarserver_namespace, "summary" ) : (0, 1),
+ (calendarserver_namespace, "attendee" ) : (1, 1),
+ (calendarserver_namespace, "access" ) : (0, 1),
+ }
+
class InviteAccess (davxml.WebDAVElement):
namespace = calendarserver_namespace
name = "access"
@@ -588,6 +669,14 @@
(calendarserver_namespace, "read-write" ): (0, 1),
}
+class Invite (davxml.WebDAVElement):
+ namespace = calendarserver_namespace
+ name = "invite"
+
+ allowed_children = {
+ (calendarserver_namespace, "user" ) : (0, None),
+ }
+
class InviteSummary (davxml.WebDAVTextElement):
namespace = calendarserver_namespace
name = "summary"
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/directory.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -124,12 +124,16 @@
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
raise UnauthorizedLogin("Incorrect credentials for %s" % (credentials.credentials.username,))
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/directory/sudo.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -109,6 +109,8 @@
return (
credentials.authnPrincipal.principalURL(),
credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
)
else:
raise UnauthorizedLogin(
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -77,6 +77,26 @@
else:
serverVersion = twext.web2.server.VERSION + " TwistedCardDAV/?"
+##
+# Sharing Conts
+##
+SHARE_ACCEPT_STATE_NEEDS_ACTION = "0"
+SHARE_ACCEPT_STATE_ACCEPTED = "1"
+SHARE_ACCEPT_STATE_DECLINED = "2"
+SHARE_ACCEPT_STATE_DELETED = "-1"
+
+shareAccpetStates = {}
+shareAccpetStates[SHARE_ACCEPT_STATE_NEEDS_ACTION] = "NEEDS-ACTION"
+shareAccpetStates[SHARE_ACCEPT_STATE_ACCEPTED] = "ACCEPTED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DECLINED] = "DECLINED"
+shareAccpetStates[SHARE_ACCEPT_STATE_DELETED] = "DELETED"
+
+shareAcceptStatesByXML = {}
+shareAcceptStatesByXML["NEEDS-ACTION"] = customxml.InviteStatusNoResponse()
+shareAcceptStatesByXML["ACCEPTED"] = customxml.InviteStatusAccepted()
+shareAcceptStatesByXML["DECLINED"] = customxml.InviteStatusDeclined()
+shareAcceptStatesByXML["DELETED"] = customxml.InviteStatusDeleted()
+
class CalDAVComplianceMixIn(object):
def davComplianceClasses(self):
@@ -206,6 +226,14 @@
*[caldavxml.CalendarComponent(name=item) for item in allowedComponents]
)
+ @classmethod
+ def enableSharing(clz, enable):
+ qname = (calendarserver_namespace, "invite" )
+ if enable and qname not in clz.liveProperties:
+ clz.liveProperties += (qname,)
+ elif not enable and qname in clz.liveProperties:
+ clz.liveProperties = tuple([p for p in clz.liveProperties if p != qname])
+
def hasProperty(self, property, request):
"""
Need to special case schedule-calendar-transp for backwards compatability.
@@ -222,6 +250,23 @@
else:
return super(CalDAVResource, self).hasProperty(property, request)
+ def isShadowableProperty(self, qname):
+ """
+ Shadowable properties are ones on shared resources where a "default" exists until
+ a user overrides with their own value.
+ """
+ return False
+
+ def isGlobalProperty(self, qname):
+ """
+ A global property is one that is the same for all users.
+ """
+ if qname == (u'DAV:', u'displayname'): return False # XXX HACK
+ if qname in self.liveProperties:
+ return True
+ else:
+ return False
+
@inlineCallbacks
def readProperty(self, property, request):
if type(property) is tuple:
@@ -229,6 +274,30 @@
else:
qname = property.qname()
+ isvirt = (yield self.isVirtualShare(request))
+ if self.isShadowableProperty(qname):
+ if isvirt:
+ p = self.deadProperties().get(qname, uid=request.authzPrincipal.principalUID())
+ if p is not None:
+ returnValue(p)
+ else:
+ res = (yield self._readGlobalProperty(qname, property, request))
+ returnValue(res)
+
+ elif (not self.isGlobalProperty(qname)) and isvirt:
+ p = self.deadProperties().get(qname, uid=request.authzPrincipal.principalUID())
+ if p is not None:
+ returnValue(p)
+
+ if qname == customxml.Invite.qname():
+ isShared = (yield self.isShared(request))
+ if not isShared:
+ returnValue(None)
+ res = (yield self._readGlobalProperty(qname, property, request))
+ returnValue(res)
+
+ @inlineCallbacks
+ def _readGlobalProperty(self, qname, property, request):
namespace, name = qname
if namespace == dav_namespace:
@@ -275,6 +344,28 @@
opaque = url in fbset
self.writeDeadProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Opaque() if opaque else caldavxml.Transparent()))
+ elif namespace == calendarserver_namespace:
+ if name == "invite":
+ shared = (yield self.isShared(request))
+ if shared:
+ userlist = []
+ accesses = (yield self.getInviteUsers(request))
+ if accesses:
+ for access in accesses:
+ acl = access()
+ for principal, metaData in accesses[access]:
+ children = []
+ mailtoemail = "mailto:" + metaData["email"]
+ children.append(davxml.HRef(mailtoemail))
+ children.append(customxml.InviteAccess(acl))
+ if "inviteStatus" in metaData and metaData["inviteStatus"]:
+ children.append(shareAcceptStatesByXML[metaData["inviteStatus"]])
+ else:
+ children.append(customxml.InviteStatusNoResponse())
+ userlist.append(customxml.InviteUser(*children))
+ result = customxml.Invite(*userlist)
+ returnValue(result)
+
result = (yield super(CalDAVResource, self).readProperty(property, request))
returnValue(result)
@@ -283,7 +374,18 @@
assert isinstance(property, davxml.WebDAVElement), (
"%r is not a WebDAVElement instance" % (property,)
)
+
+ # Per-user Dav props currently only apply to a sharee's copy of a calendar
+ isvirt = (yield self.isVirtualShare(request))
+ if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
+ p = (yield self.deadProperties().set(property, uid=request.authzPrincipal.principalUID()))
+ returnValue(p)
+
+ res = (yield self._writeGlobalProperty(property, request))
+ returnValue(res)
+ @inlineCallbacks
+ def _writeGlobalProperty(self, property, request):
if property.qname() == (caldav_namespace, "supported-calendar-component-set"):
if not self.isPseudoCalendarCollection():
raise HTTPError(StatusResponse(
@@ -331,12 +433,24 @@
myurl = (yield self.canonicalURL(request))
inbox.processFreeBusyCalendar(myurl, property.children[0] == caldavxml.Opaque())
+ elif property.qname() == (dav_namespace, "resourcetype"):
+ if self.isCalendarCollection() and config.EnableSharing:
+ # Check if adding or removing share
+ shared = (yield self.isShared(request))
+ sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
+ if not shared and sawShare:
+ # Owner is trying to share a collection
+ yield self.upgradeToShare(request)
+ elif shared and not sawShare:
+ # Remove share
+ yield self.downgradeFromShare(request)
+ returnValue(None)
+
result = (yield super(CalDAVResource, self).writeProperty(property, request))
returnValue(result)
def writeDeadProperty(self, property):
val = super(CalDAVResource, self).writeDeadProperty(property)
-
return val
@@ -851,6 +965,45 @@
"""
return None
+ ##
+ # Sharing operations
+ ##
+
+ def upgradeToShare(self, request):
+ """ Upgrade this collection to a shared state """
+ return succeed(True)
+
+ def downgradeFromShare(self, request):
+ return succeed(True)
+
+ def addUserToShare(self, userid, request, ace):
+ """ Add a user to this shared calendar """
+ return succeed(True)
+
+ def removeUserFromShare(self, userid, request):
+ """ Remove a user from this shared calendar """
+ return succeed(True)
+
+ def isShared(self, request):
+ """ Return True if this is an owner shared calendar collection """
+ succeed(False)
+
+ def isVirtualShare(self, request):
+ """ Return True if this is a shared calendar collection """
+ succeed(False)
+
+ def removeVirtualShare(self, request):
+ """ As user of a shared calendar, unlink this calendar collection """
+ succeed(False)
+
+ def getInviteUsers(self, request):
+ succeed(True)
+
+ def sendNotificationOnChange(self, icalendarComponent, request, state="added"):
+ """ Possibly send a push and or email notification on a change to a resource in a shared collection """
+ succeed(True)
+
+
class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
"""
CalDAV principal collection.
Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py 2010-03-01 18:49:46 UTC (rev 5225)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/stdconfig.py 2010-03-01 21:23:11 UTC (rev 5226)
@@ -903,7 +903,8 @@
#
# FIXME: Use the config object instead of doing this here
#
- from twistedcaldav.resource import CalendarPrincipalResource
+ from twistedcaldav.resource import CalDAVResource, CalendarPrincipalResource
+ CalDAVResource.enableSharing(configDict.EnableSharing)
CalendarPrincipalResource.enableSharing(configDict.EnableSharing)
def _updatePartitions(configDict):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100301/c4eff2f6/attachment-0001.html>
More information about the calendarserver-changes
mailing list