[CalendarServer-changes] [13620] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Jun 9 09:25:14 PDT 2014


Revision: 13620
          http://trac.calendarserver.org//changeset/13620
Author:   cdaboo at apple.com
Date:     2014-06-09 09:25:14 -0700 (Mon, 09 Jun 2014)
Log Message:
-----------
Add the ability to have per-proxy properties for certain specific (configurable) properties (e.g., calendar color). Make sure read-only
proxies can write properties. This involved changing the way HTTP authentication is handled to make sure it is always done on the root
resource during request-URI traversal so that the authz_uid is known before any store transaction is created, because the txn needs
to know that to properly setup property stores so the right "view" of per-user properties is used.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/provision/root.py
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tools/cmdline.py
    CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
    CalendarServer/trunk/calendarserver/tools/test/test_principals.py
    CalendarServer/trunk/twistedcaldav/directory/util.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/base/propertystore/base.py
    CalendarServer/trunk/txdav/base/propertystore/sql.py
    CalendarServer/trunk/txdav/base/propertystore/test/base.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_external.py

Modified: CalendarServer/trunk/calendarserver/provision/root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/calendarserver/provision/root.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -364,12 +364,7 @@
                             .format(agent)
                         ))
 
-        if (
-            config.EnableResponseCache and
-            request.method == "PROPFIND" and
-            not getattr(request, "notInCache", False) and
-            len(segments) > 1
-        ):
+        if not hasattr(request, "authzUser"):
             try:
                 authnUser, authzUser = yield self.authenticate(request)
                 request.authnUser = authnUser
@@ -381,6 +376,13 @@
                 )
                 raise HTTPError(response)
 
+        if (
+            config.EnableResponseCache and
+            request.method == "PROPFIND" and
+            not getattr(request, "notInCache", False) and
+            len(segments) > 1
+        ):
+
             try:
                 if not getattr(request, "checkingCache", False):
                     request.checkingCache = True

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -38,6 +38,8 @@
 
 from zope.interface import implements
 
+from twistedcaldav.stdconfig import config
+
 from twisted.python.log import FileLogObserver, ILogObserver
 from twisted.python.logfile import LogFile
 from twisted.python.usage import Options, UsageError
@@ -96,7 +98,7 @@
 from txdav.who.groups import GroupCacher
 
 from twistedcaldav import memcachepool
-from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.config import ConfigurationError
 from twistedcaldav.localization import processLocalizationFiles
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 from twistedcaldav.upgrade import (

Modified: CalendarServer/trunk/calendarserver/tools/cmdline.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/cmdline.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/calendarserver/tools/cmdline.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -18,6 +18,7 @@
 Shared main-point between utilities.
 """
 
+from twistedcaldav.stdconfig import config
 from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
 from calendarserver.tap.util import checkDirectories
 from calendarserver.tools.util import loadConfig, autoDisableMemcached

Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -20,12 +20,12 @@
 import plistlib
 import xml
 
+from twistedcaldav.stdconfig import config
 from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
 from twisted.trial.unittest import TestCase
 from twistedcaldav import memcacher
-from twistedcaldav.config import config
 from twistedcaldav.memcacheclient import ClientFactory
 from twistedcaldav.test.util import CapturingProcessProtocol
 from txdav.common.datastore.test.util import (

Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -16,6 +16,7 @@
 
 import os
 
+from twistedcaldav.stdconfig import config
 from calendarserver.tools.principals import (
     parseCreationArgs, matchStrings,
     recordForPrincipalID, getProxies, setProxies
@@ -23,7 +24,6 @@
 from twext.python.filepath import CachingFilePath as FilePath
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
-from twistedcaldav.config import config
 from twistedcaldav.test.util import (
     TestCase, StoreTestCase, CapturingProcessProtocol, ErrorOutput
 )

Modified: CalendarServer/trunk/twistedcaldav/directory/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/util.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/directory/util.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -92,7 +92,11 @@
     """
     transaction = getattr(request, TRANSACTION_KEY, None)
     if transaction is None:
-        transaction = newStore.newTransaction(repr(request))
+        if hasattr(request, "authzUser") and request.authzUser is not None:
+            authz_uid = request.authzUser.record.uid
+        else:
+            authz_uid = None
+        transaction = newStore.newTransaction(repr(request), authz_uid=authz_uid)
         def abortIfUncommitted(request, response):
             try:
                 # TODO: missing 'yield' here.  For formal correctness as per

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -2635,11 +2635,16 @@
         if config.EnableProxyPrincipals:
             # Server may be read only
             if config.EnableReadOnlyServer:
-                rw_proxy_privs = (
+                ro_proxy_privs = rw_proxy_privs = (
                     element.Privilege(element.Read()),
                     element.Privilege(element.ReadCurrentUserPrivilegeSet()),
                 )
             else:
+                ro_proxy_privs = (
+                    element.Privilege(element.Read()),
+                    element.Privilege(element.ReadCurrentUserPrivilegeSet()),
+                    element.Privilege(element.WriteProperties()),
+                )
                 rw_proxy_privs = (
                     element.Privilege(element.Read()),
                     element.Privilege(element.ReadCurrentUserPrivilegeSet()),
@@ -2650,10 +2655,7 @@
                 # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
                 element.ACE(
                     element.Principal(element.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-read/"))),
-                    element.Grant(
-                        element.Privilege(element.Read()),
-                        element.Privilege(element.ReadCurrentUserPrivilegeSet()),
-                    ),
+                    element.Grant(*ro_proxy_privs),
                     element.Protected(),
                     TwistedACLInheritable(),
                 ),

Modified: CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -419,17 +419,6 @@
         originator = (yield self.loadOriginatorFromRequestDetails(request))
         recipients = self.loadRecipientsFromCalendarData(calendar)
 
-        # storeComponent needs to know who the auth'd user is for access control
-        # TODO: this needs to be done in a better way - ideally when the txn is created for the request,
-        # we should set a txn.authzid attribute.
-        authz = None
-        authz_principal = self.parent.currentPrincipal(request).children[0]
-        if isinstance(authz_principal, davxml.HRef):
-            principalURL = str(authz_principal)
-            if principalURL:
-                authz = (yield request.locateResource(principalURL))
-                self._associatedTransaction._authz_uid = authz.record.uid
-
         # Log extended item
         if not hasattr(request, "extendedLogItems"):
             request.extendedLogItems = {}

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -383,6 +383,7 @@
                     element.Grant(
                         element.Privilege(element.Read()),
                         element.Privilege(element.ReadCurrentUserPrivilegeSet()),
+                        element.Privilege(element.WriteProperties()),
                     ),
                     element.Protected(),
                     TwistedACLInheritable(),

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -606,9 +606,31 @@
             "IgnorePerUserProperties" : [
                 "X-APPLE-STRUCTURED-LOCATION",
             ],
+            "CollectionProperties": {
+                "Shadowable": [
+                    "{urn:ietf:params:xml:ns:caldav}calendar-description",
+                ],
+                "ProxyOverride": [
+                    "{urn:ietf:params:xml:ns:caldav}calendar-description",
+                    "{com.apple.ical:}calendarcolor",
+                    "{http://apple.com/ns/ical/}calendar-color",
+                    "{http://apple.com/ns/ical/}calendar-order",
+                ],
+                "Global": [
+                ],
+            },
         },
         "AddressBooks" : {
             "Enabled"         : False, # Address Books on/off switch
+            "CollectionProperties": {
+                "Shadowable": [
+                    "{urn:ietf:params:xml:ns:carddav}addressbook-description",
+                ],
+                "ProxyOverride": [
+                ],
+                "Global": [
+                ],
+            },
         },
     },
 

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -2882,17 +2882,6 @@
                     "Can't parse calendar data: %s" % (str(e),)
                 ))
 
-            # storeComponent needs to know who the auth'd user is for access control
-            # TODO: this needs to be done in a better way - ideally when the txn is created for the request,
-            # we should set a txn.authzid attribute.
-            authz = None
-            authz_principal = self._parentResource.currentPrincipal(request).children[0]
-            if isinstance(authz_principal, davxml.HRef):
-                principalURL = str(authz_principal)
-                if principalURL:
-                    authz = (yield request.locateResource(principalURL))
-                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.uid
-
             try:
                 response = (yield self.storeComponent(component, smart_merge=schedule_tag_match))
             except ResourceDeletedError:
@@ -3623,17 +3612,6 @@
                     "Could not parse vCard",
                 ))
 
-            # storeComponent needs to know who the auth'd user is for access control
-            # TODO: this needs to be done in a better way - ideally when the txn is created for the request,
-            # we should set a txn.authzid attribute.
-            authz = None
-            authz_principal = self._parentResource.currentPrincipal(request).children[0]
-            if isinstance(authz_principal, davxml.HRef):
-                principalURL = str(authz_principal)
-                if principalURL:
-                    authz = (yield request.locateResource(principalURL))
-                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.uid
-
             try:
                 response = (yield self.storeComponent(component))
             except ResourceDeletedError:

Modified: CalendarServer/trunk/txdav/base/propertystore/base.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/base.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/base/propertystore/base.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -26,6 +26,7 @@
 from twext.python.log import Logger
 from txdav.xml import element as davxml
 from txdav.xml.base import encodeXMLName
+from txdav.xml.element import lookupElement
 from txweb2.dav.resource import TwistedGETContentMD5, \
     TwistedQuotaRootProperty
 
@@ -98,7 +99,11 @@
         return encodeXMLName(self.namespace, self.name)
 
 
+    def toElement(self):
+        return lookupElement((self.namespace, self.name,))
 
+
+
 class AbstractPropertyStore(DictMixin, object):
     """
     Base property store.
@@ -108,6 +113,7 @@
     implements(IPropertyStore)
 
     _defaultShadowableKeys = frozenset()
+    _defaultProxyOverrideKeys = frozenset()
     _defaultGlobalKeys = frozenset((
         PropertyName.fromElement(davxml.ACL),
         PropertyName.fromElement(davxml.ResourceID),
@@ -117,7 +123,7 @@
         PropertyName.fromElement(TwistedQuotaRootProperty),
     ))
 
-    def __init__(self, defaultUser, shareeUser=None):
+    def __init__(self, defaultUser, shareeUser=None, proxyUser=None):
         """
         Instantiate the property store for a user. The default is the default user
         (owner) property to read in the case of global or shadowable properties.
@@ -128,12 +134,17 @@
 
         @param shareeUser: the per user uid or None if the same as defaultUser
         @type shareeUser: C{str}
+
+        @param proxyUser: the proxy uid or None if no proxy
+        @type proxyUser: C{str}
         """
 
         assert(defaultUser is not None or shareeUser is not None)
         self._defaultUser = shareeUser if defaultUser is None else defaultUser
         self._perUser = defaultUser if shareeUser is None else shareeUser
+        self._proxyUser = self._perUser if proxyUser is None else proxyUser
         self._shadowableKeys = set(AbstractPropertyStore._defaultShadowableKeys)
+        self._proxyOverrideKeys = set(AbstractPropertyStore._defaultProxyOverrideKeys)
         self._globalKeys = set(AbstractPropertyStore._defaultGlobalKeys)
 
 
@@ -149,8 +160,13 @@
         self._perUser = uid
 
 
-    def setSpecialProperties(self, shadowableKeys, globalKeys):
+    def _setProxyUID(self, uid):
+        self._proxyUser = uid
+
+
+    def setSpecialProperties(self, shadowableKeys, globalKeys, proxyOverrideKeys):
         self._shadowableKeys.update(shadowableKeys)
+        self._proxyOverrideKeys.update(proxyOverrideKeys)
         self._globalKeys.update(globalKeys)
 
 
@@ -191,6 +207,13 @@
     #
 
     def __getitem__(self, key):
+        # Return proxy value if it exists, else fall through to normal logic
+        if self._proxyUser != self._perUser and self.isProxyOverrideProperty(key):
+            try:
+                return self._getitem_uid(key, self._proxyUser)
+            except KeyError:
+                pass
+
         # Handle per-user behavior
         if self.isShadowableProperty(key):
             try:
@@ -208,11 +231,23 @@
         # Handle per-user behavior
         if self.isGlobalProperty(key):
             return self._setitem_uid(key, value, self._defaultUser)
+        # Handle proxy behavior
+        elif self._proxyUser != self._perUser and self.isProxyOverrideProperty(key):
+            return self._setitem_uid(key, value, self._proxyUser)
+        # Remainder is per user
         else:
             return self._setitem_uid(key, value, self._perUser)
 
 
     def __delitem__(self, key):
+        # Delete proxy value if it exists, else fall through to normal logic
+        if self._proxyUser != self._perUser and self.isProxyOverrideProperty(key):
+            try:
+                self._delitem_uid(key, self._proxyUser)
+                return
+            except KeyError:
+                pass
+
         # Handle per-user behavior
         if self.isShadowableProperty(key):
             try:
@@ -245,11 +280,14 @@
             self[key] = other[key]
 
 
-    # Per-user property handling
     def isShadowableProperty(self, key):
         return key in self._shadowableKeys
 
 
+    def isProxyOverrideProperty(self, key):
+        return key in self._proxyOverrideKeys
+
+
     def isGlobalProperty(self, key):
         return key in self._globalKeys
 

Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -117,17 +117,19 @@
         yield _cache_user_props(self._defaultUser)
         if self._perUser != self._defaultUser:
             yield _cache_user_props(self._perUser)
+        if self._proxyUser != self._perUser:
+            yield _cache_user_props(self._proxyUser)
 
 
     @classmethod
     @inlineCallbacks
-    def load(cls, defaultuser, shareUser, txn, resourceID, created=False, notifyCallback=None):
+    def load(cls, defaultuser, shareUser, proxyUser, txn, resourceID, created=False, notifyCallback=None):
         """
         @param notifyCallback: a callable used to trigger notifications when the
             property store changes.
         """
         self = cls.__new__(cls)
-        super(PropertyStore, self).__init__(defaultuser, shareUser)
+        super(PropertyStore, self).__init__(defaultuser, shareUser, proxyUser)
         self._txn = txn
         self._resourceID = resourceID
         if not self._txn.store().queryCachingEnabled():
@@ -141,7 +143,7 @@
 
     @classmethod
     @inlineCallbacks
-    def forMultipleResources(cls, defaultUser, txn,
+    def forMultipleResources(cls, defaultUser, shareeUser, proxyUser, txn,
                              childColumn, parentColumn, parentID):
         """
         Load all property stores for all objects in a collection.  This is used
@@ -179,13 +181,13 @@
             Where=parentColumn == parentID
         )
         rows = yield query.on(txn)
-        stores = cls._createMultipleStores(defaultUser, txn, rows)
+        stores = cls._createMultipleStores(defaultUser, shareeUser, proxyUser, txn, rows)
         returnValue(stores)
 
 
     @classmethod
     @inlineCallbacks
-    def forMultipleResourcesWithResourceIDs(cls, defaultUser, txn, resourceIDs):
+    def forMultipleResourcesWithResourceIDs(cls, defaultUser, shareeUser, proxyUser, txn, resourceIDs):
         """
         Load all property stores for all specified resources.  This is used
         to optimize Depth:1 operations on that collection, by loading all
@@ -214,13 +216,13 @@
             Where=prop.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs)))
         )
         rows = yield query.on(txn, resourceIDs=resourceIDs)
-        stores = cls._createMultipleStores(defaultUser, txn, rows)
+        stores = cls._createMultipleStores(defaultUser, shareeUser, proxyUser, txn, rows)
 
         # Make sure we have a store for each resourceID even if no properties exist
         for resourceID in resourceIDs:
             if resourceID not in stores:
                 store = cls.__new__(cls)
-                super(PropertyStore, store).__init__(defaultUser)
+                super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
                 store._txn = txn
                 store._resourceID = resourceID
                 store._cached = {}
@@ -230,7 +232,7 @@
 
 
     @classmethod
-    def _createMultipleStores(cls, defaultUser, txn, rows):
+    def _createMultipleStores(cls, defaultUser, shareeUser, proxyUser, txn, rows):
         """
         Create a set of stores for the set of rows passed in.
         """
@@ -245,7 +247,7 @@
             if resource_id:
                 if resource_id not in createdStores:
                     store = cls.__new__(cls)
-                    super(PropertyStore, store).__init__(defaultUser)
+                    super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
                     store._txn = txn
                     store._resourceID = resource_id
                     store._cached = {}
@@ -253,7 +255,7 @@
                 createdStores[resource_id]._cached[(name, view_uid)] = value
             elif object_resource_id:
                 store = cls.__new__(cls)
-                super(PropertyStore, store).__init__(defaultUser)
+                super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
                 store._txn = txn
                 store._resourceID = object_resource_id
                 store._cached = {}

Modified: CalendarServer/trunk/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/base.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/base/propertystore/test/base.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -140,29 +140,45 @@
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failUnless(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
         self.propertyStore2[name] = value2
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore2[name]
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failUnless(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
         del self.propertyStore1[name]
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
 
     @inlineCallbacks
@@ -170,8 +186,10 @@
 
         name = propertyName("shadow")
 
-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
+        self.propertyStore3.setSpecialProperties((name,), (), ())
+        self.propertyStore4.setSpecialProperties((name,), (), ())
 
         value1 = propertyValue("Hello, World1!")
         value2 = propertyValue("Hello, World2!")
@@ -180,29 +198,45 @@
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         self.propertyStore2[name] = value2
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore2[name]
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore1[name]
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
 
     @inlineCallbacks
@@ -213,8 +247,10 @@
 
         name = propertyName("shadow")
 
-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
+        self.propertyStore3.setSpecialProperties((name,), (), ())
+        self.propertyStore4.setSpecialProperties((name,), (), ())
 
         value1 = propertyValue("Hello, World1!")
 
@@ -222,22 +258,34 @@
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore2[name]
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore1[name]
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
 
     @inlineCallbacks
@@ -245,8 +293,10 @@
 
         name = propertyName("global")
 
-        self.propertyStore1.setSpecialProperties((), (name,))
-        self.propertyStore2.setSpecialProperties((), (name,))
+        self.propertyStore1.setSpecialProperties((), (name,), ())
+        self.propertyStore2.setSpecialProperties((), (name,), ())
+        self.propertyStore3.setSpecialProperties((), (name,), ())
+        self.propertyStore4.setSpecialProperties((), (name,), ())
 
         value1 = propertyValue("Hello, World1!")
         value2 = propertyValue("Hello, World2!")
@@ -255,24 +305,250 @@
         yield self._changed(self.propertyStore1)
         self.assertEquals(self.propertyStore1.get(name, None), value1)
         self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         self.propertyStore2[name] = value2
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), value2)
         self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value2)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
         self.failUnless(name in self.propertyStore1)
         self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
 
         del self.propertyStore2[name]
         yield self._changed(self.propertyStore2)
         self.assertEquals(self.propertyStore1.get(name, None), None)
         self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
         self.failIf(name in self.propertyStore1)
         self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
 
 
+    @inlineCallbacks
+    def test_proxy(self):
+
+        name = propertyName("test")
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+
+        self.propertyStore3[name] = value1
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failUnless(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+        self.propertyStore4[name] = value2
+        yield self._changed(self.propertyStore4)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        del self.propertyStore4[name]
+        yield self._changed(self.propertyStore4)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failUnless(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+        del self.propertyStore3[name]
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), None)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failIf(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+
+    @inlineCallbacks
+    def test_proxyOverride(self):
+
+        name = propertyName("override")
+
+        self.propertyStore1.setSpecialProperties((), (), (name,))
+        self.propertyStore2.setSpecialProperties((), (), (name,))
+        self.propertyStore3.setSpecialProperties((), (), (name,))
+        self.propertyStore4.setSpecialProperties((), (), (name,))
+
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+
+        self.propertyStore1[name] = value1
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failUnless(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+        self.propertyStore3[name] = value2
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value2)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failUnless(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+        del self.propertyStore3[name]
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failUnless(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+        del self.propertyStore1[name]
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), None)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failIf(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+
+    @inlineCallbacks
+    def test_proxyOverrideShadow(self):
+
+        name = propertyName("override")
+
+        self.propertyStore1.setSpecialProperties((name,), (), (name,))
+        self.propertyStore2.setSpecialProperties((name,), (), (name,))
+        self.propertyStore3.setSpecialProperties((name,), (), (name,))
+        self.propertyStore4.setSpecialProperties((name,), (), (name,))
+
+        value1 = propertyValue("Hello, World1!")
+        value2 = propertyValue("Hello, World2!")
+        value3 = propertyValue("Hello, World3!")
+        value4 = propertyValue("Hello, World4!")
+
+        self.propertyStore1[name] = value1
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        self.propertyStore3[name] = value3
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value3)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        self.propertyStore4[name] = value4
+        yield self._changed(self.propertyStore4)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value3)
+        self.assertEquals(self.propertyStore4.get(name, None), value4)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        self.propertyStore2[name] = value2
+        yield self._changed(self.propertyStore2)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value3)
+        self.assertEquals(self.propertyStore4.get(name, None), value4)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        del self.propertyStore3[name]
+        yield self._changed(self.propertyStore3)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value4)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        del self.propertyStore4[name]
+        yield self._changed(self.propertyStore4)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value2)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        del self.propertyStore2[name]
+        yield self._changed(self.propertyStore2)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
+
+        del self.propertyStore1[name]
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), None)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
+        self.failIf(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
+
+
     def test_iteration(self):
 
         value = propertyValue("Hello, World!")
@@ -333,8 +609,8 @@
 
         name = propertyName("shadow")
 
-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
 
         value1 = propertyValue("Hello, World1!")
 

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -49,10 +49,11 @@
         self.store = yield buildStore(self, self.notifierFactory)
         self.addCleanup(self.maybeCommitLast)
         self._txn = self.store.newTransaction()
-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            "user01", None, self._txn, 1
-        )
-        self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load("user01", None, None, self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", "user02", None, self._txn, 1)
+        self.propertyStore3 = yield PropertyStore.load("user01", None, "user03", self._txn, 1)
+        self.propertyStore4 = yield PropertyStore.load("user01", "user02", "user04", self._txn, 1)
 
 
     @inlineCallbacks
@@ -62,7 +63,11 @@
             delattr(self, "_txn")
         else:
             result = None
-        self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
+        self.propertyStore = \
+        self.propertyStore1 = \
+        self.propertyStore2 = \
+        self.propertyStore3 = \
+        self.propertyStore4 = None
         returnValue(result)
 
 
@@ -74,18 +79,31 @@
         self._txn = self.store.newTransaction()
 
         store = self.propertyStore1
-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            "user01", None, self._txn, 1
-        )
+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load("user01", None, None, self._txn, 1)
         self.propertyStore1._shadowableKeys = store._shadowableKeys
+        self.propertyStore1._proxyOverrideKeys = store._proxyOverrideKeys
         self.propertyStore1._globalKeys = store._globalKeys
 
         store = self.propertyStore2
-        self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", "user02", None, self._txn, 1)
         self.propertyStore2._shadowableKeys = store._shadowableKeys
+        self.propertyStore2._proxyOverrideKeys = store._proxyOverrideKeys
         self.propertyStore2._globalKeys = store._globalKeys
 
+        store = self.propertyStore3
+        self.propertyStore3 = yield PropertyStore.load("user01", None, "user03", self._txn, 1)
+        self.propertyStore3._shadowableKeys = store._shadowableKeys
+        self.propertyStore3._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore3._globalKeys = store._globalKeys
 
+        store = self.propertyStore4
+        self.propertyStore4 = yield PropertyStore.load("user01", "user02", "user04", self._txn, 1)
+        self.propertyStore4._shadowableKeys = store._shadowableKeys
+        self.propertyStore4._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore4._globalKeys = store._globalKeys
+
+
     @inlineCallbacks
     def _abort(self, store):
         if hasattr(self, "_txn"):
@@ -95,18 +113,31 @@
         self._txn = self.store.newTransaction()
 
         store = self.propertyStore1
-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            "user01", None, self._txn, 1
-        )
+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load("user01", None, None, self._txn, 1)
         self.propertyStore1._shadowableKeys = store._shadowableKeys
+        self.propertyStore1._proxyOverrideKeys = store._proxyOverrideKeys
         self.propertyStore1._globalKeys = store._globalKeys
 
         store = self.propertyStore2
-        self.propertyStore2 = yield PropertyStore.load("user01", "user02", self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load("user01", "user02", None, self._txn, 1)
         self.propertyStore2._shadowableKeys = store._shadowableKeys
+        self.propertyStore2._proxyOverrideKeys = store._proxyOverrideKeys
         self.propertyStore2._globalKeys = store._globalKeys
 
+        store = self.propertyStore3
+        self.propertyStore3 = yield PropertyStore.load("user01", None, "user03", self._txn, 1)
+        self.propertyStore3._shadowableKeys = store._shadowableKeys
+        self.propertyStore3._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore3._globalKeys = store._globalKeys
 
+        store = self.propertyStore4
+        self.propertyStore4 = yield PropertyStore.load("user01", "user02", "user04", self._txn, 1)
+        self.propertyStore4._shadowableKeys = store._shadowableKeys
+        self.propertyStore4._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore4._globalKeys = store._globalKeys
+
+
     @inlineCallbacks
     def test_concurrentInsertion(self):
         """
@@ -126,7 +157,7 @@
                 pass
         self.addCleanup(maybeAbortIt)
         concurrentPropertyStore = yield PropertyStore.load(
-            "user01", None, concurrentTxn, 1
+            "user01", None, None, concurrentTxn, 1
         )
         concurrentPropertyStore[pname] = pval1
         race = []
@@ -155,8 +186,8 @@
     def test_copy(self):
 
         # Existing store
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
-        store1_user2 = yield PropertyStore.load("user01", "user02", self._txn, 2)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 2)
+        store1_user2 = yield PropertyStore.load("user01", "user02", None, self._txn, 2)
 
         # Populate current store with data
         props_user1 = (
@@ -178,18 +209,18 @@
         self._txn = self.store.newTransaction()
 
         # Existing store
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 2)
 
         # New store
-        store2_user1 = yield PropertyStore.load("user01", None, self._txn, 3)
+        store2_user1 = yield PropertyStore.load("user01", None, None, self._txn, 3)
 
         # Do copy and check results
         yield store2_user1.copyAllProperties(store1_user1)
 
         self.assertEqual(store1_user1.keys(), store2_user1.keys())
 
-        store1_user2 = yield PropertyStore.load("user01", "user02", self._txn, 2)
-        store2_user2 = yield PropertyStore.load("user01", "user02", self._txn, 3)
+        store1_user2 = yield PropertyStore.load("user01", "user02", None, self._txn, 2)
+        store2_user2 = yield PropertyStore.load("user01", "user02", None, self._txn, 3)
         self.assertEqual(store1_user2.keys(), store2_user2.keys())
 
 
@@ -197,7 +228,7 @@
     def test_insert_delete(self):
 
         # Existing store
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 2)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 2)
 
         pname = propertyName("dummy1")
         pvalue = propertyValue("value1-user1")
@@ -221,7 +252,7 @@
 
         # Existing store - add a normal property
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 10)
         self.assertTrue("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
 
         pname1 = propertyName("dummy1")
@@ -237,7 +268,7 @@
         # Existing store - add a large property
         self._txn = self.store.newTransaction()
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 10)
         self.assertTrue("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
 
         pname2 = propertyName("dummy2")
@@ -253,7 +284,7 @@
         # Try again - the cacher will fail large values
         self._txn = self.store.newTransaction()
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 10)
         self.assertFalse("SQL.props:10/user01" in store1_user1._cacher._memcacheProtocol._cache)
 
         self.assertEqual(store1_user1[pname1], pvalue1)
@@ -280,7 +311,7 @@
 
         # Existing store - add a normal property
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 10)
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
 
         pname1 = propertyName("dummy1")
@@ -297,7 +328,7 @@
 
         # Existing store - check a normal property
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
-        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        store1_user1 = yield PropertyStore.load("user01", None, None, self._txn, 10)
         self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
         self.assertEqual(store1_user1[pname1], pvalue1)
 

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -43,6 +43,12 @@
         self.propertyStore1 = self.propertyStore
         self.propertyStore2 = PropertyStore("user01", lambda : tempFile)
         self.propertyStore2._setPerUserUID("user02")
+        self.propertyStore2._setProxyUID("user02")
+        self.propertyStore3 = PropertyStore("user01", lambda : tempFile)
+        self.propertyStore3._setProxyUID("user03")
+        self.propertyStore4 = PropertyStore("user01", lambda : tempFile)
+        self.propertyStore4._setPerUserUID("user02")
+        self.propertyStore4._setProxyUID("user04")
 
 
     def test_init(self):

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -319,6 +319,7 @@
                 PropertyName.fromElement(customxml.GETCTag),
                 PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
             ),
+            (),
         )
 
 
@@ -748,6 +749,7 @@
                 PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
                 PropertyName.fromElement(customxml.ScheduleChanges),
             ),
+            (),
         )
 
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -48,7 +48,7 @@
 from twisted.python import hashlib
 from twisted.python.failure import Failure
 
-from twistedcaldav import caldavxml, customxml, ical
+from twistedcaldav import customxml, ical
 from twistedcaldav.config import config
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.dateops import normalizeForIndex, datetimeMktime, \
@@ -96,6 +96,7 @@
     InvalidResourceMove, InvalidComponentForStoreError, \
     NoSuchObjectResourceError, ConcurrentModification
 from txdav.xml import element
+from txdav.xml.parser import WebDAVDocument
 
 from txdav.idav import ChangeCategory
 
@@ -966,6 +967,10 @@
 
     _supportedComponents = None
 
+    _shadowProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.Calendars.CollectionProperties.Shadowable])
+    _proxyProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.Calendars.CollectionProperties.ProxyOverride])
+    _globalProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.Calendars.CollectionProperties.Global])
+
     def __init__(self, *args, **kw):
         """
         Initialize a calendar pointing at a record in a database.
@@ -1294,17 +1299,9 @@
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
-            # Shadowable
-            (
-                PropertyName.fromElement(caldavxml.CalendarDescription),
-                PropertyName.fromElement(caldavxml.CalendarTimeZone),
-            ),
-
-            # Global
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-                PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
-            ),
+            self._shadowProperties,
+            self._globalProperties,
+            self._proxyProperties,
         )
 
 
@@ -1353,9 +1350,9 @@
         Sub-classes should override to expose the properties they care about.
         """
         props = {}
-        for elem in (element.DisplayName, caldavxml.CalendarDescription, caldavxml.CalendarTimeZone, customxml.CalendarColor,):
-            if PropertyName.fromElement(elem) in self.properties():
-                props[elem.sname()] = str(self.properties()[PropertyName.fromElement(elem)])
+        for ename in (PropertyName.fromElement(element.DisplayName),) + self._shadowProperties:
+            if ename in self.properties():
+                props[ename.toString()] = self.properties()[ename].toxml()
         return props
 
 
@@ -1366,15 +1363,15 @@
         care about.
         """
         # Initialize these for all shares
-        for elem in (caldavxml.CalendarDescription, caldavxml.CalendarTimeZone,):
-            if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
-                self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+        for ename in self._shadowProperties:
+            if ename not in self.properties() and ename.toString() in props:
+                self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
 
         # Only initialize these for direct shares
         if self.direct():
-            for elem in (element.DisplayName, customxml.CalendarColor,):
-                if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
-                    self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+            for ename in (PropertyName.fromElement(element.DisplayName),):
+                if ename not in self.properties() and ename.toString() in props:
+                    self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
 
 
     # FIXME: this is DAV-ish.  Data store calendar objects don't have
@@ -2169,17 +2166,14 @@
                 cutype == "RESOURCE" and config.Scheduling.Options.TrackUnscheduledResourceData):
 
                 # Find current principal and update modified by details
-                if self._txn._authz_uid is not None:
-                    authz = yield self.directoryService().recordWithUID(self._txn._authz_uid.decode("utf-8"))
-                    prop = Property("X-CALENDARSERVER-MODIFIED-BY", authz.canonicalCalendarUserAddress())
-                    prop.setParameter("CN", authz.displayName)
-                    for candidate in authz.calendarUserAddresses:
-                        if candidate.startswith("mailto:"):
-                            prop.setParameter("EMAIL", candidate[7:])
-                            break
-                    component.replacePropertyInAllComponents(prop)
-                else:
-                    component.removeAllPropertiesWithName("X-CALENDARSERVER-MODIFIED-BY")
+                authz = yield self.directoryService().recordWithUID(self.calendar().viewerHome().authzuid().decode("utf-8"))
+                prop = Property("X-CALENDARSERVER-MODIFIED-BY", authz.canonicalCalendarUserAddress())
+                prop.setParameter("CN", authz.displayName)
+                for candidate in authz.calendarUserAddresses:
+                    if candidate.startswith("mailto:"):
+                        prop.setParameter("EMAIL", candidate[7:])
+                        break
+                component.replacePropertyInAllComponents(prop)
                 self._componentChanged = True
 
 
@@ -2197,7 +2191,7 @@
 
             # Only DAV:owner is able to set the property to other than PUBLIC
             if internal_state == ComponentUpdateState.NORMAL:
-                if (self._txn._authz_uid is None or self.calendar().viewerHome().uid() != self._txn._authz_uid) and access != Component.ACCESS_PUBLIC:
+                if (self.calendar().viewerHome().uid() != self.calendar().viewerHome().authzuid()) and access != Component.ACCESS_PUBLIC:
                     raise InvalidCalendarAccessError("Private event access level change not allowed")
 
             self.accessMode = access
@@ -3916,13 +3910,16 @@
 
 
     def initPropertyStore(self, props):
-        # Setup peruser special properties
+        # Setup peruser special properties - these are hard-coded for now as clients are not expected
+        # to be using properties on resources - but we do use one special "live" property which we
+        # keep in the propstore.
         props.setSpecialProperties(
             (
             ),
             (
                 PropertyName.fromElement(customxml.ScheduleChanges),
             ),
+            (),
         )
 
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -306,18 +306,18 @@
 """
 
         self.patch(config, "EnablePrivateEvents", True)
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user02"
         calendar_collection = (yield self.calendarUnderTest(home="user01"))
         calendar = Component.fromString(data1)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user02"
         yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), InvalidCalendarAccessError)
         yield self.commit()
 
         # This one should be OK
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_collection = (yield self.calendarUnderTest(home="user01"))
         calendar = Component.fromString(data1)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
         yield self.commit()
 
@@ -335,10 +335,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_resource.setComponent(calendar)
         yield self.commit()
 
@@ -516,10 +516,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_resource.setComponent(calendar)
         yield self.commit()
 
@@ -569,10 +569,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_resource.setComponent(calendar)
         yield self.commit()
 
@@ -623,10 +623,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_resource.setComponent(calendar)
         yield self.commit()
 
@@ -682,10 +682,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         yield calendar_resource.setComponent(calendar)
         yield self.commit()
 
@@ -789,10 +789,10 @@
 END:VCALENDAR
 """
 
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user01"
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",))
         calendar = Component.fromString(data2)
-        txn = self.transactionUnderTest()
-        txn._authz_uid = "user01"
         result = (yield calendar_resource.setComponent(calendar))
         yield self.commit()
         self.assertTrue(result)

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -23,6 +23,10 @@
     populateCalendarsFrom
 from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
     _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
+from txdav.base.propertystore.base import PropertyName
+from txdav.xml.base import WebDAVTextElement
+from twistedcaldav import customxml
+from txdav.xml.element import registerElement, registerElementClass
 
 
 class BaseSharingTests(CommonCommonTests, TestCase):
@@ -489,7 +493,94 @@
         yield self.commit()
 
 
+    @inlineCallbacks
+    def test_perUserSharedProxyCollectionProperties(self):
+        """
+        Test that sharees and proxies get their own per-user properties, with some being
+        initialized based ont he owner value.
+        """
+        @registerElement
+        @registerElementClass
+        class DummySharingProperty (WebDAVTextElement):
+            namespace = "http://calendarserver.org/ns/"
+            name = "dummy-sharing"
 
+        shared_name = yield self._createShare()
+
+        # Add owner properties
+        home = yield self.homeUnderTest(name="user01")
+        calendar = yield home.calendarWithName("calendar")
+
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString("user01")
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString("#000001")
+        yield self.commit()
+
+        # Check/add sharee properties
+        home = yield self.homeUnderTest(name="user02")
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertTrue(PropertyName.fromElement(DummySharingProperty) not in calendar.properties())
+        self.assertTrue(PropertyName.fromElement(customxml.CalendarColor) not in calendar.properties())
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString("user02")
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString("#000002")
+        yield self.commit()
+
+        # Check/add owner proxy properties
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user03"
+        home = yield self.homeUnderTest(name="user01")
+        calendar = yield home.calendarWithName("calendar")
+        self.assertTrue(PropertyName.fromElement(DummySharingProperty) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user01")
+        self.assertTrue(PropertyName.fromElement(customxml.CalendarColor) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000001")
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString("user03")
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString("#000003")
+        yield self.commit()
+
+        # Check/add sharee proxy properties
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user04"
+        home = yield self.homeUnderTest(name="user02")
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertTrue(PropertyName.fromElement(DummySharingProperty) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user02")
+        self.assertTrue(PropertyName.fromElement(customxml.CalendarColor) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000002")
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString("user04")
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString("#000004")
+        yield self.commit()
+
+        # Validate all properties
+        home = yield self.homeUnderTest(name="user01")
+        calendar = yield home.calendarWithName("calendar")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user03")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000001")
+        yield self.commit()
+
+        home = yield self.homeUnderTest(name="user02")
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user04")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000002")
+        yield self.commit()
+
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user03"
+        home = yield self.homeUnderTest(name="user01")
+        calendar = yield home.calendarWithName("calendar")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user03")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000003")
+        yield self.commit()
+
+        txn = self.transactionUnderTest()
+        txn._authz_uid = "user04"
+        home = yield self.homeUnderTest(name="user02")
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), "user04")
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), "#000004")
+        yield self.commit()
+
+
+
 class SharingRevisions(BaseSharingTests):
     """
     Test store-based sharing and interaction with revision table.

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -139,6 +139,7 @@
             (
                 PropertyName.fromElement(customxml.GETCTag),
             ),
+            (),
         )
 
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -39,7 +39,6 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.python import hashlib
 
-from twistedcaldav import carddavxml, customxml
 from twistedcaldav.config import config
 from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError, Property, \
@@ -65,6 +64,7 @@
     AllRetriesFailed, ObjectResourceNameAlreadyExistsError, \
     SyncTokenValidException, IndexedSearchException
 from txdav.xml import element
+from txdav.xml.parser import WebDAVDocument
 
 from zope.interface.declarations import implements
 
@@ -87,9 +87,9 @@
     _cacher = Memcacher("SQL.adbkhome", pickle=True, key_normalization=False)
 
 
-    def __init__(self, transaction, ownerUID):
+    def __init__(self, transaction, ownerUID, authzUID=None):
 
-        super(AddressBookHome, self).__init__(transaction, ownerUID)
+        super(AddressBookHome, self).__init__(transaction, ownerUID, authzUID=authzUID)
         self._addressbookPropertyStoreID = None
         self._addressbook = None
 
@@ -467,6 +467,9 @@
         "UID": _objectSchema.UID,
     }
 
+    _shadowProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.AddressBooks.CollectionProperties.Shadowable])
+    _proxyProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.AddressBooks.CollectionProperties.ProxyOverride])
+    _globalProperties = tuple([PropertyName.fromString(prop) for prop in config.Sharing.AddressBooks.CollectionProperties.Global])
 
     @classmethod
     @inlineCallbacks
@@ -797,6 +800,7 @@
             props = yield PropertyStore.load(
                 self.ownerHome().uid(),
                 self.viewerHome().uid(),
+                None,
                 self._txn,
                 self.ownerHome()._addressbookPropertyStoreID,  # not ._resourceID as in CommonHomeChild._loadPropertyStore()
                 notifyCallback=self.notifyPropertyChanged
@@ -807,25 +811,22 @@
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
-            (
-                PropertyName.fromElement(carddavxml.AddressBookDescription),
-            ),
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-            ),
+            self._shadowProperties,
+            self._globalProperties,
+            self._proxyProperties,
         )
 
 
     def getInviteCopyProperties(self):
         """
-        Get a dictionary of property name/values (as strings) for properties that are shadowable and
+        Get a dictionary of property name/values (as XML strings) for properties that are shadowable and
         need to be copied to a sharee's collection when an external (cross-pod) share is created.
         Sub-classes should override to expose the properties they care about.
         """
         props = {}
-        for elem in (element.DisplayName, carddavxml.AddressBookDescription,):
-            if PropertyName.fromElement(elem) in self.properties():
-                props[elem.sname()] = str(self.properties()[PropertyName.fromElement(elem)])
+        for ename in (PropertyName.fromElement(element.DisplayName),) + self._shadowProperties:
+            if ename in self.properties():
+                props[ename.toString()] = self.properties()[ename].toxml()
         return props
 
 
@@ -836,15 +837,15 @@
         care about.
         """
         # Initialize these for all shares
-        for elem in (carddavxml.AddressBookDescription,):
-            if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
-                self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+        for ename in self._shadowProperties:
+            if ename not in self.properties() and ename.toString() in props:
+                self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
 
         # Only initialize these for direct shares
         if self.direct():
-            for elem in (element.DisplayName,):
-                if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
-                    self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+            for ename in (PropertyName.fromElement(element.DisplayName),):
+                if ename not in self.properties() and ename.toString() in props:
+                    self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
 
 
     def contentType(self):
@@ -1167,7 +1168,7 @@
             # Get property stores for all these child resources (if any found)
             addressbookPropertyStoreIDs = [ownerHomeItem._addressbookPropertyStoreID for ownerHomeItem in ownerHomeToDataRowMap]
             propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
-                home.uid(), home._txn, addressbookPropertyStoreIDs
+                home.uid(), None, None, home._txn, addressbookPropertyStoreIDs
             )
 
             addressbookResourceIDs = [ownerHomeItem.addressbook()._resourceID for ownerHomeItem in ownerHomeToDataRowMap]

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -1701,6 +1701,7 @@
             (
                 PropertyName.fromElement(customxml.NotificationType),
             ),
+            (),
         )
 
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -694,7 +694,7 @@
         ).on(self)
 
 
-    def _determineMemo(self, storeType, uid, create=False):
+    def _determineMemo(self, storeType, uid, create=False, authzUID=None):
         """
         Determine the memo dictionary to use for homeWithUID.
         """
@@ -720,19 +720,27 @@
 
 
     @memoizedKey("uid", _determineMemo)
-    def homeWithUID(self, storeType, uid, create=False):
+    def homeWithUID(self, storeType, uid, create=False, authzUID=None):
+        """
+        We need to distinguish between various different users "looking" at a home and its
+        child resources because we have per-user properties that depend on which user is "looking".
+        By default the viewer is set to the authz_uid on the transaction, or the owner if no authz,
+        but it can be overridden using L{authzUID}. This is useful when the store needs to get to
+        other user's homes with the viewer being the owner of that home as opposed to authz_uid. That
+        often happens when manipulating shares.
+        """
         if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
             raise RuntimeError("Unknown home type.")
 
-        return self._homeClass[storeType].homeWithUID(self, uid, create)
+        return self._homeClass[storeType].homeWithUID(self, uid, create, authzUID)
 
 
-    def calendarHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(ECALENDARTYPE, uid, create=create)
+    def calendarHomeWithUID(self, uid, create=False, authzUID=None):
+        return self.homeWithUID(ECALENDARTYPE, uid, create=create, authzUID=authzUID)
 
 
-    def addressbookHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
+    def addressbookHomeWithUID(self, uid, create=False, authzUID=None):
+        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create, authzUID=authzUID)
 
 
     @inlineCallbacks
@@ -740,10 +748,10 @@
         """
         Load a calendar or addressbook home by its integer resource ID.
         """
-        uid = (yield self._homeClass[storeType]
-               .homeUIDWithResourceID(self, rid))
+        uid = (yield self._homeClass[storeType].homeUIDWithResourceID(self, rid))
         if uid:
-            result = (yield self.homeWithUID(storeType, uid))
+            # Always get the owner's view of the home = i.e., authzUID=uid
+            result = (yield self.homeWithUID(storeType, uid, authzUID=uid))
         else:
             result = None
         returnValue(result)
@@ -2838,7 +2846,7 @@
 
     @classmethod
     @inlineCallbacks
-    def makeClass(cls, transaction, ownerUID, no_cache=False):
+    def makeClass(cls, transaction, ownerUID, no_cache=False, authzUID=None):
         """
         Build the actual home class taking into account the possibility that we might need to
         switch in the external version of the class.
@@ -2850,14 +2858,20 @@
         @param no_cache: should cached query be used
         @type no_cache: C{bool}
         """
-        home = cls(transaction, ownerUID)
+        home = cls(transaction, ownerUID, authzUID=authzUID)
         actualHome = yield home.initFromStore(no_cache)
         returnValue(actualHome)
 
 
-    def __init__(self, transaction, ownerUID):
+    def __init__(self, transaction, ownerUID, authzUID=None):
         self._txn = transaction
         self._ownerUID = ownerUID
+        self._authzUID = authzUID
+        if self._authzUID is None:
+            if self._txn._authz_uid is not None:
+                self._authzUID = self._txn._authz_uid
+            else:
+                self._authzUID = self._ownerUID
         self._resourceID = None
         self._status = _HOME_STATUS_NORMAL
         self._dataVersion = None
@@ -3052,11 +3066,11 @@
 
     @classmethod
     @inlineCallbacks
-    def homeWithUID(cls, txn, uid, create=False):
+    def homeWithUID(cls, txn, uid, create=False, authzUID=None):
         """
         @param uid: I'm going to assume uid is utf-8 encoded bytes
         """
-        homeObject = yield cls.makeClass(txn, uid)
+        homeObject = yield cls.makeClass(txn, uid, authzUID=authzUID)
         if homeObject is not None:
             returnValue(homeObject)
         else:
@@ -3091,7 +3105,7 @@
                 yield savepoint.rollback(txn)
 
                 # Retry the query - row may exist now, if not re-raise
-                homeObject = yield cls.makeClass(txn, uid)
+                homeObject = yield cls.makeClass(txn, uid, authzUID=authzUID)
                 if homeObject:
                     returnValue(homeObject)
                 else:
@@ -3102,7 +3116,7 @@
                 # Note that we must not cache the owner_uid->resource_id
                 # mapping in _cacher when creating as we don't want that to appear
                 # until AFTER the commit
-                home = yield cls.makeClass(txn, uid, no_cache=True)
+                home = yield cls.makeClass(txn, uid, no_cache=True, authzUID=authzUID)
                 yield home.createdHome()
                 returnValue(home)
 
@@ -3140,6 +3154,15 @@
         return self._ownerUID
 
 
+    def authzuid(self):
+        """
+        Retrieve the unique identifier of the user accessing the data in this home.
+
+        @return: a string.
+        """
+        return self._authzUID
+
+
     def external(self):
         """
         Is this an external home.
@@ -3540,9 +3563,13 @@
 
     @inlineCallbacks
     def _loadPropertyStore(self):
+
+        # Use any authz uid in place of the viewer uid so delegates have their own
+        # set of properties
         props = yield PropertyStore.load(
             self.uid(),
             self.uid(),
+            self.authzuid(),
             self._txn,
             self._resourceID,
             notifyCallback=self.notifyChanged
@@ -4652,6 +4679,9 @@
     def ownerView(self):
         """
         Return the owner resource counterpart of this shared resource.
+
+        Note we have to play a trick with the property store to coerce it to match
+        the per-user properties for the owner.
         """
         # Get the child of the owner home that has the same resource id as the owned one
         ownerView = yield self.ownerHome().childWithID(self.id())
@@ -4662,10 +4692,13 @@
     def shareeView(self, shareeUID):
         """
         Return the shared resource counterpart of this owned resource for the specified sharee.
+
+        Note we have to play a trick with the property store to coerce it to match
+        the per-user properties for the sharee.
         """
 
         # Get the child of the sharee home that has the same resource id as the owned one
-        shareeHome = yield self._txn.homeWithUID(self._home._homeType, shareeUID)
+        shareeHome = yield self._txn.homeWithUID(self._home._homeType, shareeUID, authzUID=shareeUID)
         shareeView = (yield shareeHome.allChildWithID(self.id())) if shareeHome is not None else None
         returnValue(shareeView)
 
@@ -5434,7 +5467,7 @@
             childResourceIDs = [dataRow[2] for dataRow in dataRows]
 
             propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
-                home.uid(), home._txn, childResourceIDs
+                home.uid(), None, None, home._txn, childResourceIDs
             )
 
             # Get revisions
@@ -6216,9 +6249,12 @@
     @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
+            # Use any authz uid in place of the viewer uid so delegates have their own
+            # set of properties
             props = yield PropertyStore.load(
                 self.ownerHome().uid(),
                 self.viewerHome().uid(),
+                self.viewerHome().authzuid(),
                 self._txn,
                 self._resourceID,
                 notifyCallback=self.notifyPropertyChanged
@@ -6534,6 +6570,8 @@
             if parent.objectResourcesHaveProperties():
                 propertyStores = (yield PropertyStore.forMultipleResources(
                     parent._home.uid(),
+                    None,
+                    None,
                     parent._txn,
                     cls._objectSchema.RESOURCE_ID,
                     cls._objectSchema.PARENT_RESOURCE_ID,
@@ -6611,6 +6649,8 @@
             if parent.objectResourcesHaveProperties():
                 propertyStores = (yield PropertyStore.forMultipleResourcesWithResourceIDs(
                     parent._home.uid(),
+                    None,
+                    None,
                     parent._txn,
                     tuple([row[0] for row in dataRows]),
                 ))
@@ -6789,8 +6829,9 @@
         if props is None:
             if self._parentCollection.objectResourcesHaveProperties():
                 props = yield PropertyStore.load(
+                    self._parentCollection.ownerHome().uid(),
                     self._parentCollection.viewerHome().uid(),
-                    self._parentCollection.ownerHome().uid(),
+                    self._parentCollection.viewerHome().authzuid(),
                     self._txn,
                     self._resourceID,
                     created=created
@@ -7153,6 +7194,7 @@
         self._propertyStore = yield PropertyStore.load(
             self._uid,
             self._uid,
+            None,
             self._txn,
             self._resourceID,
             notifyCallback=self.notifyChanged
@@ -7492,6 +7534,8 @@
             # Get property stores for all these child resources (if any found)
             propertyStores = (yield PropertyStore.forMultipleResources(
                 parent.uid(),
+                None,
+                None,
                 parent._txn,
                 schema.NOTIFICATION.RESOURCE_ID,
                 schema.NOTIFICATION.NOTIFICATION_HOME_RESOURCE_ID,

Modified: CalendarServer/trunk/txdav/common/datastore/sql_external.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_external.py	2014-06-09 16:04:50 UTC (rev 13619)
+++ CalendarServer/trunk/txdav/common/datastore/sql_external.py	2014-06-09 16:25:14 UTC (rev 13620)
@@ -149,6 +149,7 @@
         props = yield PropertyStore.load(
             self.uid(),
             self.uid(),
+            None,
             self._txn,
             self._resourceID,
             notifyCallback=self.notifyChanged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140609/184dc4e1/attachment-0001.html>


More information about the calendarserver-changes mailing list