<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13620] CalendarServer/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13620">13620</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-06-09 09:25:14 -0700 (Mon, 09 Jun 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>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 &quot;view&quot; of per-user properties is used.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarserverprovisionrootpy">CalendarServer/trunk/calendarserver/provision/root.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertapcaldavpy">CalendarServer/trunk/calendarserver/tap/caldav.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolscmdlinepy">CalendarServer/trunk/calendarserver/tools/cmdline.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstesttest_gatewaypy">CalendarServer/trunk/calendarserver/tools/test/test_gateway.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstesttest_principalspy">CalendarServer/trunk/calendarserver/tools/test/test_principals.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectoryutilpy">CalendarServer/trunk/twistedcaldav/directory/util.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavresourcepy">CalendarServer/trunk/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavscheduling_storecaldavresourcepy">CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavsharingpy">CalendarServer/trunk/twistedcaldav/sharing.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstdconfigpy">CalendarServer/trunk/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstorebridgepy">CalendarServer/trunk/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServertrunktxdavbasepropertystorebasepy">CalendarServer/trunk/txdav/base/propertystore/base.py</a></li>
<li><a href="#CalendarServertrunktxdavbasepropertystoresqlpy">CalendarServer/trunk/txdav/base/propertystore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavbasepropertystoretestbasepy">CalendarServer/trunk/txdav/base/propertystore/test/base.py</a></li>
<li><a href="#CalendarServertrunktxdavbasepropertystoretesttest_sqlpy">CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavbasepropertystoretesttest_xattrpy">CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorefilepy">CalendarServer/trunk/txdav/caldav/datastore/file.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresqlpy">CalendarServer/trunk/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_implicitpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sql_sharingpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorefilepy">CalendarServer/trunk/txdav/carddav/datastore/file.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoresqlpy">CalendarServer/trunk/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorefilepy">CalendarServer/trunk/txdav/common/datastore/file.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_externalpy">CalendarServer/trunk/txdav/common/datastore/sql_external.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarserverprovisionrootpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/provision/root.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -364,12 +364,7 @@
</span><span class="cx">                             .format(agent)
</span><span class="cx">                         ))
</span><span class="cx"> 
</span><del>-        if (
-            config.EnableResponseCache and
-            request.method == &quot;PROPFIND&quot; and
-            not getattr(request, &quot;notInCache&quot;, False) and
-            len(segments) &gt; 1
-        ):
</del><ins>+        if not hasattr(request, &quot;authzUser&quot;):
</ins><span class="cx">             try:
</span><span class="cx">                 authnUser, authzUser = yield self.authenticate(request)
</span><span class="cx">                 request.authnUser = authnUser
</span><span class="lines">@@ -381,6 +376,13 @@
</span><span class="cx">                 )
</span><span class="cx">                 raise HTTPError(response)
</span><span class="cx"> 
</span><ins>+        if (
+            config.EnableResponseCache and
+            request.method == &quot;PROPFIND&quot; and
+            not getattr(request, &quot;notInCache&quot;, False) and
+            len(segments) &gt; 1
+        ):
+
</ins><span class="cx">             try:
</span><span class="cx">                 if not getattr(request, &quot;checkingCache&quot;, False):
</span><span class="cx">                     request.checkingCache = True
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertapcaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/caldav.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -38,6 +38,8 @@
</span><span class="cx"> 
</span><span class="cx"> from zope.interface import implements
</span><span class="cx"> 
</span><ins>+from twistedcaldav.stdconfig import config
+
</ins><span class="cx"> from twisted.python.log import FileLogObserver, ILogObserver
</span><span class="cx"> from twisted.python.logfile import LogFile
</span><span class="cx"> from twisted.python.usage import Options, UsageError
</span><span class="lines">@@ -96,7 +98,7 @@
</span><span class="cx"> from txdav.who.groups import GroupCacher
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import memcachepool
</span><del>-from twistedcaldav.config import config, ConfigurationError
</del><ins>+from twistedcaldav.config import ConfigurationError
</ins><span class="cx"> from twistedcaldav.localization import processLocalizationFiles
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
</span><span class="cx"> from twistedcaldav.upgrade import (
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolscmdlinepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/cmdline.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -18,6 +18,7 @@
</span><span class="cx"> Shared main-point between utilities.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+from twistedcaldav.stdconfig import config
</ins><span class="cx"> from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
</span><span class="cx"> from calendarserver.tap.util import checkDirectories
</span><span class="cx"> from calendarserver.tools.util import loadConfig, autoDisableMemcached
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstesttest_gatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -20,12 +20,12 @@
</span><span class="cx"> import plistlib
</span><span class="cx"> import xml
</span><span class="cx"> 
</span><ins>+from twistedcaldav.stdconfig import config
</ins><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> from twistedcaldav import memcacher
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.memcacheclient import ClientFactory
</span><span class="cx"> from twistedcaldav.test.util import CapturingProcessProtocol
</span><span class="cx"> from txdav.common.datastore.test.util import (
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstesttest_principalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -16,6 +16,7 @@
</span><span class="cx"> 
</span><span class="cx"> import os
</span><span class="cx"> 
</span><ins>+from twistedcaldav.stdconfig import config
</ins><span class="cx"> from calendarserver.tools.principals import (
</span><span class="cx">     parseCreationArgs, matchStrings,
</span><span class="cx">     recordForPrincipalID, getProxies, setProxies
</span><span class="lines">@@ -23,7 +24,6 @@
</span><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.test.util import (
</span><span class="cx">     TestCase, StoreTestCase, CapturingProcessProtocol, ErrorOutput
</span><span class="cx"> )
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectoryutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/util.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -92,7 +92,11 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     transaction = getattr(request, TRANSACTION_KEY, None)
</span><span class="cx">     if transaction is None:
</span><del>-        transaction = newStore.newTransaction(repr(request))
</del><ins>+        if hasattr(request, &quot;authzUser&quot;) 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)
</ins><span class="cx">         def abortIfUncommitted(request, response):
</span><span class="cx">             try:
</span><span class="cx">                 # TODO: missing 'yield' here.  For formal correctness as per
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/resource.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -2635,11 +2635,16 @@
</span><span class="cx">         if config.EnableProxyPrincipals:
</span><span class="cx">             # Server may be read only
</span><span class="cx">             if config.EnableReadOnlyServer:
</span><del>-                rw_proxy_privs = (
</del><ins>+                ro_proxy_privs = rw_proxy_privs = (
</ins><span class="cx">                     element.Privilege(element.Read()),
</span><span class="cx">                     element.Privilege(element.ReadCurrentUserPrivilegeSet()),
</span><span class="cx">                 )
</span><span class="cx">             else:
</span><ins>+                ro_proxy_privs = (
+                    element.Privilege(element.Read()),
+                    element.Privilege(element.ReadCurrentUserPrivilegeSet()),
+                    element.Privilege(element.WriteProperties()),
+                )
</ins><span class="cx">                 rw_proxy_privs = (
</span><span class="cx">                     element.Privilege(element.Read()),
</span><span class="cx">                     element.Privilege(element.ReadCurrentUserPrivilegeSet()),
</span><span class="lines">@@ -2650,10 +2655,7 @@
</span><span class="cx">                 # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
</span><span class="cx">                 element.ACE(
</span><span class="cx">                     element.Principal(element.HRef(joinURL(myPrincipal.principalURL(), &quot;calendar-proxy-read/&quot;))),
</span><del>-                    element.Grant(
-                        element.Privilege(element.Read()),
-                        element.Privilege(element.ReadCurrentUserPrivilegeSet()),
-                    ),
</del><ins>+                    element.Grant(*ro_proxy_privs),
</ins><span class="cx">                     element.Protected(),
</span><span class="cx">                     TwistedACLInheritable(),
</span><span class="cx">                 ),
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavscheduling_storecaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/scheduling_store/caldav/resource.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -419,17 +419,6 @@
</span><span class="cx">         originator = (yield self.loadOriginatorFromRequestDetails(request))
</span><span class="cx">         recipients = self.loadRecipientsFromCalendarData(calendar)
</span><span class="cx"> 
</span><del>-        # 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
-
</del><span class="cx">         # Log extended item
</span><span class="cx">         if not hasattr(request, &quot;extendedLogItems&quot;):
</span><span class="cx">             request.extendedLogItems = {}
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavsharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/sharing.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -383,6 +383,7 @@
</span><span class="cx">                     element.Grant(
</span><span class="cx">                         element.Privilege(element.Read()),
</span><span class="cx">                         element.Privilege(element.ReadCurrentUserPrivilegeSet()),
</span><ins>+                        element.Privilege(element.WriteProperties()),
</ins><span class="cx">                     ),
</span><span class="cx">                     element.Protected(),
</span><span class="cx">                     TwistedACLInheritable(),
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -606,9 +606,31 @@
</span><span class="cx">             &quot;IgnorePerUserProperties&quot; : [
</span><span class="cx">                 &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
</span><span class="cx">             ],
</span><ins>+            &quot;CollectionProperties&quot;: {
+                &quot;Shadowable&quot;: [
+                    &quot;{urn:ietf:params:xml:ns:caldav}calendar-description&quot;,
+                ],
+                &quot;ProxyOverride&quot;: [
+                    &quot;{urn:ietf:params:xml:ns:caldav}calendar-description&quot;,
+                    &quot;{com.apple.ical:}calendarcolor&quot;,
+                    &quot;{http://apple.com/ns/ical/}calendar-color&quot;,
+                    &quot;{http://apple.com/ns/ical/}calendar-order&quot;,
+                ],
+                &quot;Global&quot;: [
+                ],
+            },
</ins><span class="cx">         },
</span><span class="cx">         &quot;AddressBooks&quot; : {
</span><span class="cx">             &quot;Enabled&quot;         : False, # Address Books on/off switch
</span><ins>+            &quot;CollectionProperties&quot;: {
+                &quot;Shadowable&quot;: [
+                    &quot;{urn:ietf:params:xml:ns:carddav}addressbook-description&quot;,
+                ],
+                &quot;ProxyOverride&quot;: [
+                ],
+                &quot;Global&quot;: [
+                ],
+            },
</ins><span class="cx">         },
</span><span class="cx">     },
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/storebridge.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -2882,17 +2882,6 @@
</span><span class="cx">                     &quot;Can't parse calendar data: %s&quot; % (str(e),)
</span><span class="cx">                 ))
</span><span class="cx"> 
</span><del>-            # 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
-
</del><span class="cx">             try:
</span><span class="cx">                 response = (yield self.storeComponent(component, smart_merge=schedule_tag_match))
</span><span class="cx">             except ResourceDeletedError:
</span><span class="lines">@@ -3623,17 +3612,6 @@
</span><span class="cx">                     &quot;Could not parse vCard&quot;,
</span><span class="cx">                 ))
</span><span class="cx"> 
</span><del>-            # 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
-
</del><span class="cx">             try:
</span><span class="cx">                 response = (yield self.storeComponent(component))
</span><span class="cx">             except ResourceDeletedError:
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasepropertystorebasepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/propertystore/base.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -26,6 +26,7 @@
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.base import encodeXMLName
</span><ins>+from txdav.xml.element import lookupElement
</ins><span class="cx"> from txweb2.dav.resource import TwistedGETContentMD5, \
</span><span class="cx">     TwistedQuotaRootProperty
</span><span class="cx"> 
</span><span class="lines">@@ -98,7 +99,11 @@
</span><span class="cx">         return encodeXMLName(self.namespace, self.name)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def toElement(self):
+        return lookupElement((self.namespace, self.name,))
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class AbstractPropertyStore(DictMixin, object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Base property store.
</span><span class="lines">@@ -108,6 +113,7 @@
</span><span class="cx">     implements(IPropertyStore)
</span><span class="cx"> 
</span><span class="cx">     _defaultShadowableKeys = frozenset()
</span><ins>+    _defaultProxyOverrideKeys = frozenset()
</ins><span class="cx">     _defaultGlobalKeys = frozenset((
</span><span class="cx">         PropertyName.fromElement(davxml.ACL),
</span><span class="cx">         PropertyName.fromElement(davxml.ResourceID),
</span><span class="lines">@@ -117,7 +123,7 @@
</span><span class="cx">         PropertyName.fromElement(TwistedQuotaRootProperty),
</span><span class="cx">     ))
</span><span class="cx"> 
</span><del>-    def __init__(self, defaultUser, shareeUser=None):
</del><ins>+    def __init__(self, defaultUser, shareeUser=None, proxyUser=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Instantiate the property store for a user. The default is the default user
</span><span class="cx">         (owner) property to read in the case of global or shadowable properties.
</span><span class="lines">@@ -128,12 +134,17 @@
</span><span class="cx"> 
</span><span class="cx">         @param shareeUser: the per user uid or None if the same as defaultUser
</span><span class="cx">         @type shareeUser: C{str}
</span><ins>+
+        @param proxyUser: the proxy uid or None if no proxy
+        @type proxyUser: C{str}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         assert(defaultUser is not None or shareeUser is not None)
</span><span class="cx">         self._defaultUser = shareeUser if defaultUser is None else defaultUser
</span><span class="cx">         self._perUser = defaultUser if shareeUser is None else shareeUser
</span><ins>+        self._proxyUser = self._perUser if proxyUser is None else proxyUser
</ins><span class="cx">         self._shadowableKeys = set(AbstractPropertyStore._defaultShadowableKeys)
</span><ins>+        self._proxyOverrideKeys = set(AbstractPropertyStore._defaultProxyOverrideKeys)
</ins><span class="cx">         self._globalKeys = set(AbstractPropertyStore._defaultGlobalKeys)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -149,8 +160,13 @@
</span><span class="cx">         self._perUser = uid
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def setSpecialProperties(self, shadowableKeys, globalKeys):
</del><ins>+    def _setProxyUID(self, uid):
+        self._proxyUser = uid
+
+
+    def setSpecialProperties(self, shadowableKeys, globalKeys, proxyOverrideKeys):
</ins><span class="cx">         self._shadowableKeys.update(shadowableKeys)
</span><ins>+        self._proxyOverrideKeys.update(proxyOverrideKeys)
</ins><span class="cx">         self._globalKeys.update(globalKeys)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -191,6 +207,13 @@
</span><span class="cx">     #
</span><span class="cx"> 
</span><span class="cx">     def __getitem__(self, key):
</span><ins>+        # 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
+
</ins><span class="cx">         # Handle per-user behavior
</span><span class="cx">         if self.isShadowableProperty(key):
</span><span class="cx">             try:
</span><span class="lines">@@ -208,11 +231,23 @@
</span><span class="cx">         # Handle per-user behavior
</span><span class="cx">         if self.isGlobalProperty(key):
</span><span class="cx">             return self._setitem_uid(key, value, self._defaultUser)
</span><ins>+        # Handle proxy behavior
+        elif self._proxyUser != self._perUser and self.isProxyOverrideProperty(key):
+            return self._setitem_uid(key, value, self._proxyUser)
+        # Remainder is per user
</ins><span class="cx">         else:
</span><span class="cx">             return self._setitem_uid(key, value, self._perUser)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __delitem__(self, key):
</span><ins>+        # 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
+
</ins><span class="cx">         # Handle per-user behavior
</span><span class="cx">         if self.isShadowableProperty(key):
</span><span class="cx">             try:
</span><span class="lines">@@ -245,11 +280,14 @@
</span><span class="cx">             self[key] = other[key]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    # Per-user property handling
</del><span class="cx">     def isShadowableProperty(self, key):
</span><span class="cx">         return key in self._shadowableKeys
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def isProxyOverrideProperty(self, key):
+        return key in self._proxyOverrideKeys
+
+
</ins><span class="cx">     def isGlobalProperty(self, key):
</span><span class="cx">         return key in self._globalKeys
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasepropertystoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -117,17 +117,19 @@
</span><span class="cx">         yield _cache_user_props(self._defaultUser)
</span><span class="cx">         if self._perUser != self._defaultUser:
</span><span class="cx">             yield _cache_user_props(self._perUser)
</span><ins>+        if self._proxyUser != self._perUser:
+            yield _cache_user_props(self._proxyUser)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def load(cls, defaultuser, shareUser, txn, resourceID, created=False, notifyCallback=None):
</del><ins>+    def load(cls, defaultuser, shareUser, proxyUser, txn, resourceID, created=False, notifyCallback=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param notifyCallback: a callable used to trigger notifications when the
</span><span class="cx">             property store changes.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self = cls.__new__(cls)
</span><del>-        super(PropertyStore, self).__init__(defaultuser, shareUser)
</del><ins>+        super(PropertyStore, self).__init__(defaultuser, shareUser, proxyUser)
</ins><span class="cx">         self._txn = txn
</span><span class="cx">         self._resourceID = resourceID
</span><span class="cx">         if not self._txn.store().queryCachingEnabled():
</span><span class="lines">@@ -141,7 +143,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def forMultipleResources(cls, defaultUser, txn,
</del><ins>+    def forMultipleResources(cls, defaultUser, shareeUser, proxyUser, txn,
</ins><span class="cx">                              childColumn, parentColumn, parentID):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Load all property stores for all objects in a collection.  This is used
</span><span class="lines">@@ -179,13 +181,13 @@
</span><span class="cx">             Where=parentColumn == parentID
</span><span class="cx">         )
</span><span class="cx">         rows = yield query.on(txn)
</span><del>-        stores = cls._createMultipleStores(defaultUser, txn, rows)
</del><ins>+        stores = cls._createMultipleStores(defaultUser, shareeUser, proxyUser, txn, rows)
</ins><span class="cx">         returnValue(stores)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def forMultipleResourcesWithResourceIDs(cls, defaultUser, txn, resourceIDs):
</del><ins>+    def forMultipleResourcesWithResourceIDs(cls, defaultUser, shareeUser, proxyUser, txn, resourceIDs):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Load all property stores for all specified resources.  This is used
</span><span class="cx">         to optimize Depth:1 operations on that collection, by loading all
</span><span class="lines">@@ -214,13 +216,13 @@
</span><span class="cx">             Where=prop.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs)))
</span><span class="cx">         )
</span><span class="cx">         rows = yield query.on(txn, resourceIDs=resourceIDs)
</span><del>-        stores = cls._createMultipleStores(defaultUser, txn, rows)
</del><ins>+        stores = cls._createMultipleStores(defaultUser, shareeUser, proxyUser, txn, rows)
</ins><span class="cx"> 
</span><span class="cx">         # Make sure we have a store for each resourceID even if no properties exist
</span><span class="cx">         for resourceID in resourceIDs:
</span><span class="cx">             if resourceID not in stores:
</span><span class="cx">                 store = cls.__new__(cls)
</span><del>-                super(PropertyStore, store).__init__(defaultUser)
</del><ins>+                super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
</ins><span class="cx">                 store._txn = txn
</span><span class="cx">                 store._resourceID = resourceID
</span><span class="cx">                 store._cached = {}
</span><span class="lines">@@ -230,7 +232,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def _createMultipleStores(cls, defaultUser, txn, rows):
</del><ins>+    def _createMultipleStores(cls, defaultUser, shareeUser, proxyUser, txn, rows):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a set of stores for the set of rows passed in.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -245,7 +247,7 @@
</span><span class="cx">             if resource_id:
</span><span class="cx">                 if resource_id not in createdStores:
</span><span class="cx">                     store = cls.__new__(cls)
</span><del>-                    super(PropertyStore, store).__init__(defaultUser)
</del><ins>+                    super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
</ins><span class="cx">                     store._txn = txn
</span><span class="cx">                     store._resourceID = resource_id
</span><span class="cx">                     store._cached = {}
</span><span class="lines">@@ -253,7 +255,7 @@
</span><span class="cx">                 createdStores[resource_id]._cached[(name, view_uid)] = value
</span><span class="cx">             elif object_resource_id:
</span><span class="cx">                 store = cls.__new__(cls)
</span><del>-                super(PropertyStore, store).__init__(defaultUser)
</del><ins>+                super(PropertyStore, store).__init__(defaultUser, shareeUser, proxyUser)
</ins><span class="cx">                 store._txn = txn
</span><span class="cx">                 store._resourceID = object_resource_id
</span><span class="cx">                 store._cached = {}
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasepropertystoretestbasepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/propertystore/test/base.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -140,29 +140,45 @@
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         self.propertyStore2[name] = value2
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value2)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore2[name]
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore1[name]
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), None)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failIf(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -170,8 +186,10 @@
</span><span class="cx"> 
</span><span class="cx">         name = propertyName(&quot;shadow&quot;)
</span><span class="cx"> 
</span><del>-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
</del><ins>+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
+        self.propertyStore3.setSpecialProperties((name,), (), ())
+        self.propertyStore4.setSpecialProperties((name,), (), ())
</ins><span class="cx"> 
</span><span class="cx">         value1 = propertyValue(&quot;Hello, World1!&quot;)
</span><span class="cx">         value2 = propertyValue(&quot;Hello, World2!&quot;)
</span><span class="lines">@@ -180,29 +198,45 @@
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value1)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         self.propertyStore2[name] = value2
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value2)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore2[name]
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value1)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore1[name]
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), None)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failIf(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -213,8 +247,10 @@
</span><span class="cx"> 
</span><span class="cx">         name = propertyName(&quot;shadow&quot;)
</span><span class="cx"> 
</span><del>-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
</del><ins>+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
+        self.propertyStore3.setSpecialProperties((name,), (), ())
+        self.propertyStore4.setSpecialProperties((name,), (), ())
</ins><span class="cx"> 
</span><span class="cx">         value1 = propertyValue(&quot;Hello, World1!&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -222,22 +258,34 @@
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value1)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore2[name]
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value1)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore1[name]
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), None)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failIf(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -245,8 +293,10 @@
</span><span class="cx"> 
</span><span class="cx">         name = propertyName(&quot;global&quot;)
</span><span class="cx"> 
</span><del>-        self.propertyStore1.setSpecialProperties((), (name,))
-        self.propertyStore2.setSpecialProperties((), (name,))
</del><ins>+        self.propertyStore1.setSpecialProperties((), (name,), ())
+        self.propertyStore2.setSpecialProperties((), (name,), ())
+        self.propertyStore3.setSpecialProperties((), (name,), ())
+        self.propertyStore4.setSpecialProperties((), (name,), ())
</ins><span class="cx"> 
</span><span class="cx">         value1 = propertyValue(&quot;Hello, World1!&quot;)
</span><span class="cx">         value2 = propertyValue(&quot;Hello, World2!&quot;)
</span><span class="lines">@@ -255,24 +305,250 @@
</span><span class="cx">         yield self._changed(self.propertyStore1)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value1)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value1)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value1)
+        self.assertEquals(self.propertyStore4.get(name, None), value1)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         self.propertyStore2[name] = value2
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), value2)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), value2)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), value2)
+        self.assertEquals(self.propertyStore4.get(name, None), value2)
</ins><span class="cx">         self.failUnless(name in self.propertyStore1)
</span><span class="cx">         self.failUnless(name in self.propertyStore2)
</span><ins>+        self.failUnless(name in self.propertyStore3)
+        self.failUnless(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx">         del self.propertyStore2[name]
</span><span class="cx">         yield self._changed(self.propertyStore2)
</span><span class="cx">         self.assertEquals(self.propertyStore1.get(name, None), None)
</span><span class="cx">         self.assertEquals(self.propertyStore2.get(name, None), None)
</span><ins>+        self.assertEquals(self.propertyStore3.get(name, None), None)
+        self.assertEquals(self.propertyStore4.get(name, None), None)
</ins><span class="cx">         self.failIf(name in self.propertyStore1)
</span><span class="cx">         self.failIf(name in self.propertyStore2)
</span><ins>+        self.failIf(name in self.propertyStore3)
+        self.failIf(name in self.propertyStore4)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_proxy(self):
+
+        name = propertyName(&quot;test&quot;)
+        value1 = propertyValue(&quot;Hello, World1!&quot;)
+        value2 = propertyValue(&quot;Hello, World2!&quot;)
+
+        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(&quot;override&quot;)
+
+        self.propertyStore1.setSpecialProperties((), (), (name,))
+        self.propertyStore2.setSpecialProperties((), (), (name,))
+        self.propertyStore3.setSpecialProperties((), (), (name,))
+        self.propertyStore4.setSpecialProperties((), (), (name,))
+
+        value1 = propertyValue(&quot;Hello, World1!&quot;)
+        value2 = propertyValue(&quot;Hello, World2!&quot;)
+
+        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(&quot;override&quot;)
+
+        self.propertyStore1.setSpecialProperties((name,), (), (name,))
+        self.propertyStore2.setSpecialProperties((name,), (), (name,))
+        self.propertyStore3.setSpecialProperties((name,), (), (name,))
+        self.propertyStore4.setSpecialProperties((name,), (), (name,))
+
+        value1 = propertyValue(&quot;Hello, World1!&quot;)
+        value2 = propertyValue(&quot;Hello, World2!&quot;)
+        value3 = propertyValue(&quot;Hello, World3!&quot;)
+        value4 = propertyValue(&quot;Hello, World4!&quot;)
+
+        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)
+
+
</ins><span class="cx">     def test_iteration(self):
</span><span class="cx"> 
</span><span class="cx">         value = propertyValue(&quot;Hello, World!&quot;)
</span><span class="lines">@@ -333,8 +609,8 @@
</span><span class="cx"> 
</span><span class="cx">         name = propertyName(&quot;shadow&quot;)
</span><span class="cx"> 
</span><del>-        self.propertyStore1.setSpecialProperties((name,), ())
-        self.propertyStore2.setSpecialProperties((name,), ())
</del><ins>+        self.propertyStore1.setSpecialProperties((name,), (), ())
+        self.propertyStore2.setSpecialProperties((name,), (), ())
</ins><span class="cx"> 
</span><span class="cx">         value1 = propertyValue(&quot;Hello, World1!&quot;)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasepropertystoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -49,10 +49,11 @@
</span><span class="cx">         self.store = yield buildStore(self, self.notifierFactory)
</span><span class="cx">         self.addCleanup(self.maybeCommitLast)
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><del>-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            &quot;user01&quot;, None, self._txn, 1
-        )
-        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 1)
</del><ins>+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 1)
+        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 1)
+        self.propertyStore3 = yield PropertyStore.load(&quot;user01&quot;, None, &quot;user03&quot;, self._txn, 1)
+        self.propertyStore4 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, &quot;user04&quot;, self._txn, 1)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -62,7 +63,11 @@
</span><span class="cx">             delattr(self, &quot;_txn&quot;)
</span><span class="cx">         else:
</span><span class="cx">             result = None
</span><del>-        self.propertyStore = self.propertyStore1 = self.propertyStore2 = None
</del><ins>+        self.propertyStore = \
+        self.propertyStore1 = \
+        self.propertyStore2 = \
+        self.propertyStore3 = \
+        self.propertyStore4 = None
</ins><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -74,18 +79,31 @@
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         store = self.propertyStore1
</span><del>-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            &quot;user01&quot;, None, self._txn, 1
-        )
</del><ins>+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 1)
</ins><span class="cx">         self.propertyStore1._shadowableKeys = store._shadowableKeys
</span><ins>+        self.propertyStore1._proxyOverrideKeys = store._proxyOverrideKeys
</ins><span class="cx">         self.propertyStore1._globalKeys = store._globalKeys
</span><span class="cx"> 
</span><span class="cx">         store = self.propertyStore2
</span><del>-        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 1)
</del><ins>+        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 1)
</ins><span class="cx">         self.propertyStore2._shadowableKeys = store._shadowableKeys
</span><ins>+        self.propertyStore2._proxyOverrideKeys = store._proxyOverrideKeys
</ins><span class="cx">         self.propertyStore2._globalKeys = store._globalKeys
</span><span class="cx"> 
</span><ins>+        store = self.propertyStore3
+        self.propertyStore3 = yield PropertyStore.load(&quot;user01&quot;, None, &quot;user03&quot;, self._txn, 1)
+        self.propertyStore3._shadowableKeys = store._shadowableKeys
+        self.propertyStore3._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore3._globalKeys = store._globalKeys
</ins><span class="cx"> 
</span><ins>+        store = self.propertyStore4
+        self.propertyStore4 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, &quot;user04&quot;, self._txn, 1)
+        self.propertyStore4._shadowableKeys = store._shadowableKeys
+        self.propertyStore4._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore4._globalKeys = store._globalKeys
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _abort(self, store):
</span><span class="cx">         if hasattr(self, &quot;_txn&quot;):
</span><span class="lines">@@ -95,18 +113,31 @@
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         store = self.propertyStore1
</span><del>-        self.propertyStore = self.propertyStore1 = yield PropertyStore.load(
-            &quot;user01&quot;, None, self._txn, 1
-        )
</del><ins>+        self.propertyStore = \
+        self.propertyStore1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 1)
</ins><span class="cx">         self.propertyStore1._shadowableKeys = store._shadowableKeys
</span><ins>+        self.propertyStore1._proxyOverrideKeys = store._proxyOverrideKeys
</ins><span class="cx">         self.propertyStore1._globalKeys = store._globalKeys
</span><span class="cx"> 
</span><span class="cx">         store = self.propertyStore2
</span><del>-        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 1)
</del><ins>+        self.propertyStore2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 1)
</ins><span class="cx">         self.propertyStore2._shadowableKeys = store._shadowableKeys
</span><ins>+        self.propertyStore2._proxyOverrideKeys = store._proxyOverrideKeys
</ins><span class="cx">         self.propertyStore2._globalKeys = store._globalKeys
</span><span class="cx"> 
</span><ins>+        store = self.propertyStore3
+        self.propertyStore3 = yield PropertyStore.load(&quot;user01&quot;, None, &quot;user03&quot;, self._txn, 1)
+        self.propertyStore3._shadowableKeys = store._shadowableKeys
+        self.propertyStore3._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore3._globalKeys = store._globalKeys
</ins><span class="cx"> 
</span><ins>+        store = self.propertyStore4
+        self.propertyStore4 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, &quot;user04&quot;, self._txn, 1)
+        self.propertyStore4._shadowableKeys = store._shadowableKeys
+        self.propertyStore4._proxyOverrideKeys = store._proxyOverrideKeys
+        self.propertyStore4._globalKeys = store._globalKeys
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_concurrentInsertion(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -126,7 +157,7 @@
</span><span class="cx">                 pass
</span><span class="cx">         self.addCleanup(maybeAbortIt)
</span><span class="cx">         concurrentPropertyStore = yield PropertyStore.load(
</span><del>-            &quot;user01&quot;, None, concurrentTxn, 1
</del><ins>+            &quot;user01&quot;, None, None, concurrentTxn, 1
</ins><span class="cx">         )
</span><span class="cx">         concurrentPropertyStore[pname] = pval1
</span><span class="cx">         race = []
</span><span class="lines">@@ -155,8 +186,8 @@
</span><span class="cx">     def test_copy(self):
</span><span class="cx"> 
</span><span class="cx">         # Existing store
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 2)
-        store1_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 2)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 2)
+        store1_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 2)
</ins><span class="cx"> 
</span><span class="cx">         # Populate current store with data
</span><span class="cx">         props_user1 = (
</span><span class="lines">@@ -178,18 +209,18 @@
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         # Existing store
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 2)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 2)
</ins><span class="cx"> 
</span><span class="cx">         # New store
</span><del>-        store2_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 3)
</del><ins>+        store2_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 3)
</ins><span class="cx"> 
</span><span class="cx">         # Do copy and check results
</span><span class="cx">         yield store2_user1.copyAllProperties(store1_user1)
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(store1_user1.keys(), store2_user1.keys())
</span><span class="cx"> 
</span><del>-        store1_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 2)
-        store2_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, self._txn, 3)
</del><ins>+        store1_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 2)
+        store2_user2 = yield PropertyStore.load(&quot;user01&quot;, &quot;user02&quot;, None, self._txn, 3)
</ins><span class="cx">         self.assertEqual(store1_user2.keys(), store2_user2.keys())
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -197,7 +228,7 @@
</span><span class="cx">     def test_insert_delete(self):
</span><span class="cx"> 
</span><span class="cx">         # Existing store
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 2)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 2)
</ins><span class="cx"> 
</span><span class="cx">         pname = propertyName(&quot;dummy1&quot;)
</span><span class="cx">         pvalue = propertyValue(&quot;value1-user1&quot;)
</span><span class="lines">@@ -221,7 +252,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Existing store - add a normal property
</span><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 10)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 10)
</ins><span class="cx">         self.assertTrue(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><span class="cx"> 
</span><span class="cx">         pname1 = propertyName(&quot;dummy1&quot;)
</span><span class="lines">@@ -237,7 +268,7 @@
</span><span class="cx">         # Existing store - add a large property
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 10)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 10)
</ins><span class="cx">         self.assertTrue(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><span class="cx"> 
</span><span class="cx">         pname2 = propertyName(&quot;dummy2&quot;)
</span><span class="lines">@@ -253,7 +284,7 @@
</span><span class="cx">         # Try again - the cacher will fail large values
</span><span class="cx">         self._txn = self.store.newTransaction()
</span><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 10)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 10)
</ins><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in store1_user1._cacher._memcacheProtocol._cache)
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(store1_user1[pname1], pvalue1)
</span><span class="lines">@@ -280,7 +311,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Existing store - add a normal property
</span><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 10)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 10)
</ins><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><span class="cx"> 
</span><span class="cx">         pname1 = propertyName(&quot;dummy1&quot;)
</span><span class="lines">@@ -297,7 +328,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Existing store - check a normal property
</span><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><del>-        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, self._txn, 10)
</del><ins>+        store1_user1 = yield PropertyStore.load(&quot;user01&quot;, None, None, self._txn, 10)
</ins><span class="cx">         self.assertFalse(&quot;SQL.props:10/user01&quot; in PropertyStore._cacher._memcacheProtocol._cache)
</span><span class="cx">         self.assertEqual(store1_user1[pname1], pvalue1)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasepropertystoretesttest_xattrpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_xattr.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -43,6 +43,12 @@
</span><span class="cx">         self.propertyStore1 = self.propertyStore
</span><span class="cx">         self.propertyStore2 = PropertyStore(&quot;user01&quot;, lambda : tempFile)
</span><span class="cx">         self.propertyStore2._setPerUserUID(&quot;user02&quot;)
</span><ins>+        self.propertyStore2._setProxyUID(&quot;user02&quot;)
+        self.propertyStore3 = PropertyStore(&quot;user01&quot;, lambda : tempFile)
+        self.propertyStore3._setProxyUID(&quot;user03&quot;)
+        self.propertyStore4 = PropertyStore(&quot;user01&quot;, lambda : tempFile)
+        self.propertyStore4._setPerUserUID(&quot;user02&quot;)
+        self.propertyStore4._setProxyUID(&quot;user04&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_init(self):
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -319,6 +319,7 @@
</span><span class="cx">                 PropertyName.fromElement(customxml.GETCTag),
</span><span class="cx">                 PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
</span><span class="cx">             ),
</span><ins>+            (),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -748,6 +749,7 @@
</span><span class="cx">                 PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
</span><span class="cx">                 PropertyName.fromElement(customxml.ScheduleChanges),
</span><span class="cx">             ),
</span><ins>+            (),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -48,7 +48,7 @@
</span><span class="cx"> from twisted.python import hashlib
</span><span class="cx"> from twisted.python.failure import Failure
</span><span class="cx"> 
</span><del>-from twistedcaldav import caldavxml, customxml, ical
</del><ins>+from twistedcaldav import customxml, ical
</ins><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.dateops import normalizeForIndex, datetimeMktime, \
</span><span class="lines">@@ -96,6 +96,7 @@
</span><span class="cx">     InvalidResourceMove, InvalidComponentForStoreError, \
</span><span class="cx">     NoSuchObjectResourceError, ConcurrentModification
</span><span class="cx"> from txdav.xml import element
</span><ins>+from txdav.xml.parser import WebDAVDocument
</ins><span class="cx"> 
</span><span class="cx"> from txdav.idav import ChangeCategory
</span><span class="cx"> 
</span><span class="lines">@@ -966,6 +967,10 @@
</span><span class="cx"> 
</span><span class="cx">     _supportedComponents = None
</span><span class="cx"> 
</span><ins>+    _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])
+
</ins><span class="cx">     def __init__(self, *args, **kw):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Initialize a calendar pointing at a record in a database.
</span><span class="lines">@@ -1294,17 +1299,9 @@
</span><span class="cx">     def initPropertyStore(self, props):
</span><span class="cx">         # Setup peruser special properties
</span><span class="cx">         props.setSpecialProperties(
</span><del>-            # Shadowable
-            (
-                PropertyName.fromElement(caldavxml.CalendarDescription),
-                PropertyName.fromElement(caldavxml.CalendarTimeZone),
-            ),
-
-            # Global
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-                PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet),
-            ),
</del><ins>+            self._shadowProperties,
+            self._globalProperties,
+            self._proxyProperties,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1353,9 +1350,9 @@
</span><span class="cx">         Sub-classes should override to expose the properties they care about.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         props = {}
</span><del>-        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)])
</del><ins>+        for ename in (PropertyName.fromElement(element.DisplayName),) + self._shadowProperties:
+            if ename in self.properties():
+                props[ename.toString()] = self.properties()[ename].toxml()
</ins><span class="cx">         return props
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1366,15 +1363,15 @@
</span><span class="cx">         care about.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Initialize these for all shares
</span><del>-        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()])
</del><ins>+        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
</ins><span class="cx"> 
</span><span class="cx">         # Only initialize these for direct shares
</span><span class="cx">         if self.direct():
</span><del>-            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()])
</del><ins>+            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
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     # FIXME: this is DAV-ish.  Data store calendar objects don't have
</span><span class="lines">@@ -2169,17 +2166,14 @@
</span><span class="cx">                 cutype == &quot;RESOURCE&quot; and config.Scheduling.Options.TrackUnscheduledResourceData):
</span><span class="cx"> 
</span><span class="cx">                 # Find current principal and update modified by details
</span><del>-                if self._txn._authz_uid is not None:
-                    authz = yield self.directoryService().recordWithUID(self._txn._authz_uid.decode(&quot;utf-8&quot;))
-                    prop = Property(&quot;X-CALENDARSERVER-MODIFIED-BY&quot;, authz.canonicalCalendarUserAddress())
-                    prop.setParameter(&quot;CN&quot;, authz.displayName)
-                    for candidate in authz.calendarUserAddresses:
-                        if candidate.startswith(&quot;mailto:&quot;):
-                            prop.setParameter(&quot;EMAIL&quot;, candidate[7:])
-                            break
-                    component.replacePropertyInAllComponents(prop)
-                else:
-                    component.removeAllPropertiesWithName(&quot;X-CALENDARSERVER-MODIFIED-BY&quot;)
</del><ins>+                authz = yield self.directoryService().recordWithUID(self.calendar().viewerHome().authzuid().decode(&quot;utf-8&quot;))
+                prop = Property(&quot;X-CALENDARSERVER-MODIFIED-BY&quot;, authz.canonicalCalendarUserAddress())
+                prop.setParameter(&quot;CN&quot;, authz.displayName)
+                for candidate in authz.calendarUserAddresses:
+                    if candidate.startswith(&quot;mailto:&quot;):
+                        prop.setParameter(&quot;EMAIL&quot;, candidate[7:])
+                        break
+                component.replacePropertyInAllComponents(prop)
</ins><span class="cx">                 self._componentChanged = True
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2197,7 +2191,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Only DAV:owner is able to set the property to other than PUBLIC
</span><span class="cx">             if internal_state == ComponentUpdateState.NORMAL:
</span><del>-                if (self._txn._authz_uid is None or self.calendar().viewerHome().uid() != self._txn._authz_uid) and access != Component.ACCESS_PUBLIC:
</del><ins>+                if (self.calendar().viewerHome().uid() != self.calendar().viewerHome().authzuid()) and access != Component.ACCESS_PUBLIC:
</ins><span class="cx">                     raise InvalidCalendarAccessError(&quot;Private event access level change not allowed&quot;)
</span><span class="cx"> 
</span><span class="cx">             self.accessMode = access
</span><span class="lines">@@ -3916,13 +3910,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def initPropertyStore(self, props):
</span><del>-        # Setup peruser special properties
</del><ins>+        # 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 &quot;live&quot; property which we
+        # keep in the propstore.
</ins><span class="cx">         props.setSpecialProperties(
</span><span class="cx">             (
</span><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 PropertyName.fromElement(customxml.ScheduleChanges),
</span><span class="cx">             ),
</span><ins>+            (),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_implicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_implicit.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -306,18 +306,18 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.patch(config, &quot;EnablePrivateEvents&quot;, True)
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user02&quot;
</ins><span class="cx">         calendar_collection = (yield self.calendarUnderTest(home=&quot;user01&quot;))
</span><span class="cx">         calendar = Component.fromString(data1)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user02&quot;
</del><span class="cx">         yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName(&quot;test.ics&quot;, calendar), InvalidCalendarAccessError)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # This one should be OK
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_collection = (yield self.calendarUnderTest(home=&quot;user01&quot;))
</span><span class="cx">         calendar = Component.fromString(data1)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_collection.createCalendarObjectWithName(&quot;test.ics&quot;, calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -335,10 +335,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_resource.setComponent(calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -516,10 +516,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_resource.setComponent(calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -569,10 +569,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_resource.setComponent(calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -623,10 +623,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_resource.setComponent(calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -682,10 +682,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         yield calendar_resource.setComponent(calendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -789,10 +789,10 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user01&quot;
</ins><span class="cx">         calendar_resource = (yield self.calendarObjectUnderTest(name=&quot;test.ics&quot;, home=&quot;user01&quot;,))
</span><span class="cx">         calendar = Component.fromString(data2)
</span><del>-        txn = self.transactionUnderTest()
-        txn._authz_uid = &quot;user01&quot;
</del><span class="cx">         result = (yield calendar_resource.setComponent(calendar))
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertTrue(result)
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -23,6 +23,10 @@
</span><span class="cx">     populateCalendarsFrom
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
</span><span class="cx">     _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
</span><ins>+from txdav.base.propertystore.base import PropertyName
+from txdav.xml.base import WebDAVTextElement
+from twistedcaldav import customxml
+from txdav.xml.element import registerElement, registerElementClass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class BaseSharingTests(CommonCommonTests, TestCase):
</span><span class="lines">@@ -489,7 +493,94 @@
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_perUserSharedProxyCollectionProperties(self):
+        &quot;&quot;&quot;
+        Test that sharees and proxies get their own per-user properties, with some being
+        initialized based ont he owner value.
+        &quot;&quot;&quot;
+        @registerElement
+        @registerElementClass
+        class DummySharingProperty (WebDAVTextElement):
+            namespace = &quot;http://calendarserver.org/ns/&quot;
+            name = &quot;dummy-sharing&quot;
</ins><span class="cx"> 
</span><ins>+        shared_name = yield self._createShare()
+
+        # Add owner properties
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString(&quot;user01&quot;)
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString(&quot;#000001&quot;)
+        yield self.commit()
+
+        # Check/add sharee properties
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        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(&quot;user02&quot;)
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString(&quot;#000002&quot;)
+        yield self.commit()
+
+        # Check/add owner proxy properties
+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user03&quot;
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        self.assertTrue(PropertyName.fromElement(DummySharingProperty) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user01&quot;)
+        self.assertTrue(PropertyName.fromElement(customxml.CalendarColor) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000001&quot;)
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString(&quot;user03&quot;)
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString(&quot;#000003&quot;)
+        yield self.commit()
+
+        # Check/add sharee proxy properties
+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user04&quot;
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertTrue(PropertyName.fromElement(DummySharingProperty) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user02&quot;)
+        self.assertTrue(PropertyName.fromElement(customxml.CalendarColor) in calendar.properties())
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000002&quot;)
+        calendar.properties()[PropertyName.fromElement(DummySharingProperty)] = DummySharingProperty.fromString(&quot;user04&quot;)
+        calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)] = customxml.CalendarColor.fromString(&quot;#000004&quot;)
+        yield self.commit()
+
+        # Validate all properties
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user03&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000001&quot;)
+        yield self.commit()
+
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user04&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000002&quot;)
+        yield self.commit()
+
+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user03&quot;
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user03&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000003&quot;)
+        yield self.commit()
+
+        txn = self.transactionUnderTest()
+        txn._authz_uid = &quot;user04&quot;
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(DummySharingProperty)]), &quot;user04&quot;)
+        self.assertEqual(str(calendar.properties()[PropertyName.fromElement(customxml.CalendarColor)]), &quot;#000004&quot;)
+        yield self.commit()
+
+
+
</ins><span class="cx"> class SharingRevisions(BaseSharingTests):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test store-based sharing and interaction with revision table.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -139,6 +139,7 @@
</span><span class="cx">             (
</span><span class="cx">                 PropertyName.fromElement(customxml.GETCTag),
</span><span class="cx">             ),
</span><ins>+            (),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -39,7 +39,6 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.python import hashlib
</span><span class="cx"> 
</span><del>-from twistedcaldav import carddavxml, customxml
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError, Property, \
</span><span class="lines">@@ -65,6 +64,7 @@
</span><span class="cx">     AllRetriesFailed, ObjectResourceNameAlreadyExistsError, \
</span><span class="cx">     SyncTokenValidException, IndexedSearchException
</span><span class="cx"> from txdav.xml import element
</span><ins>+from txdav.xml.parser import WebDAVDocument
</ins><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><span class="lines">@@ -87,9 +87,9 @@
</span><span class="cx">     _cacher = Memcacher(&quot;SQL.adbkhome&quot;, pickle=True, key_normalization=False)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, transaction, ownerUID):
</del><ins>+    def __init__(self, transaction, ownerUID, authzUID=None):
</ins><span class="cx"> 
</span><del>-        super(AddressBookHome, self).__init__(transaction, ownerUID)
</del><ins>+        super(AddressBookHome, self).__init__(transaction, ownerUID, authzUID=authzUID)
</ins><span class="cx">         self._addressbookPropertyStoreID = None
</span><span class="cx">         self._addressbook = None
</span><span class="cx"> 
</span><span class="lines">@@ -467,6 +467,9 @@
</span><span class="cx">         &quot;UID&quot;: _objectSchema.UID,
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _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])
</ins><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -797,6 +800,7 @@
</span><span class="cx">             props = yield PropertyStore.load(
</span><span class="cx">                 self.ownerHome().uid(),
</span><span class="cx">                 self.viewerHome().uid(),
</span><ins>+                None,
</ins><span class="cx">                 self._txn,
</span><span class="cx">                 self.ownerHome()._addressbookPropertyStoreID,  # not ._resourceID as in CommonHomeChild._loadPropertyStore()
</span><span class="cx">                 notifyCallback=self.notifyPropertyChanged
</span><span class="lines">@@ -807,25 +811,22 @@
</span><span class="cx">     def initPropertyStore(self, props):
</span><span class="cx">         # Setup peruser special properties
</span><span class="cx">         props.setSpecialProperties(
</span><del>-            (
-                PropertyName.fromElement(carddavxml.AddressBookDescription),
-            ),
-            (
-                PropertyName.fromElement(customxml.GETCTag),
-            ),
</del><ins>+            self._shadowProperties,
+            self._globalProperties,
+            self._proxyProperties,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getInviteCopyProperties(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Get a dictionary of property name/values (as strings) for properties that are shadowable and
</del><ins>+        Get a dictionary of property name/values (as XML strings) for properties that are shadowable and
</ins><span class="cx">         need to be copied to a sharee's collection when an external (cross-pod) share is created.
</span><span class="cx">         Sub-classes should override to expose the properties they care about.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         props = {}
</span><del>-        for elem in (element.DisplayName, carddavxml.AddressBookDescription,):
-            if PropertyName.fromElement(elem) in self.properties():
-                props[elem.sname()] = str(self.properties()[PropertyName.fromElement(elem)])
</del><ins>+        for ename in (PropertyName.fromElement(element.DisplayName),) + self._shadowProperties:
+            if ename in self.properties():
+                props[ename.toString()] = self.properties()[ename].toxml()
</ins><span class="cx">         return props
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -836,15 +837,15 @@
</span><span class="cx">         care about.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Initialize these for all shares
</span><del>-        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()])
</del><ins>+        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
</ins><span class="cx"> 
</span><span class="cx">         # Only initialize these for direct shares
</span><span class="cx">         if self.direct():
</span><del>-            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()])
</del><ins>+            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
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def contentType(self):
</span><span class="lines">@@ -1167,7 +1168,7 @@
</span><span class="cx">             # Get property stores for all these child resources (if any found)
</span><span class="cx">             addressbookPropertyStoreIDs = [ownerHomeItem._addressbookPropertyStoreID for ownerHomeItem in ownerHomeToDataRowMap]
</span><span class="cx">             propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
</span><del>-                home.uid(), home._txn, addressbookPropertyStoreIDs
</del><ins>+                home.uid(), None, None, home._txn, addressbookPropertyStoreIDs
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">             addressbookResourceIDs = [ownerHomeItem.addressbook()._resourceID for ownerHomeItem in ownerHomeToDataRowMap]
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/file.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1701,6 +1701,7 @@
</span><span class="cx">             (
</span><span class="cx">                 PropertyName.fromElement(customxml.NotificationType),
</span><span class="cx">             ),
</span><ins>+            (),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -694,7 +694,7 @@
</span><span class="cx">         ).on(self)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _determineMemo(self, storeType, uid, create=False):
</del><ins>+    def _determineMemo(self, storeType, uid, create=False, authzUID=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Determine the memo dictionary to use for homeWithUID.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -720,19 +720,27 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @memoizedKey(&quot;uid&quot;, _determineMemo)
</span><del>-    def homeWithUID(self, storeType, uid, create=False):
</del><ins>+    def homeWithUID(self, storeType, uid, create=False, authzUID=None):
+        &quot;&quot;&quot;
+        We need to distinguish between various different users &quot;looking&quot; at a home and its
+        child resources because we have per-user properties that depend on which user is &quot;looking&quot;.
+        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.
+        &quot;&quot;&quot;
</ins><span class="cx">         if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
</span><span class="cx">             raise RuntimeError(&quot;Unknown home type.&quot;)
</span><span class="cx"> 
</span><del>-        return self._homeClass[storeType].homeWithUID(self, uid, create)
</del><ins>+        return self._homeClass[storeType].homeWithUID(self, uid, create, authzUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def calendarHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(ECALENDARTYPE, uid, create=create)
</del><ins>+    def calendarHomeWithUID(self, uid, create=False, authzUID=None):
+        return self.homeWithUID(ECALENDARTYPE, uid, create=create, authzUID=authzUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def addressbookHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
</del><ins>+    def addressbookHomeWithUID(self, uid, create=False, authzUID=None):
+        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create, authzUID=authzUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -740,10 +748,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Load a calendar or addressbook home by its integer resource ID.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        uid = (yield self._homeClass[storeType]
-               .homeUIDWithResourceID(self, rid))
</del><ins>+        uid = (yield self._homeClass[storeType].homeUIDWithResourceID(self, rid))
</ins><span class="cx">         if uid:
</span><del>-            result = (yield self.homeWithUID(storeType, uid))
</del><ins>+            # Always get the owner's view of the home = i.e., authzUID=uid
+            result = (yield self.homeWithUID(storeType, uid, authzUID=uid))
</ins><span class="cx">         else:
</span><span class="cx">             result = None
</span><span class="cx">         returnValue(result)
</span><span class="lines">@@ -2838,7 +2846,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def makeClass(cls, transaction, ownerUID, no_cache=False):
</del><ins>+    def makeClass(cls, transaction, ownerUID, no_cache=False, authzUID=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Build the actual home class taking into account the possibility that we might need to
</span><span class="cx">         switch in the external version of the class.
</span><span class="lines">@@ -2850,14 +2858,20 @@
</span><span class="cx">         @param no_cache: should cached query be used
</span><span class="cx">         @type no_cache: C{bool}
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        home = cls(transaction, ownerUID)
</del><ins>+        home = cls(transaction, ownerUID, authzUID=authzUID)
</ins><span class="cx">         actualHome = yield home.initFromStore(no_cache)
</span><span class="cx">         returnValue(actualHome)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, transaction, ownerUID):
</del><ins>+    def __init__(self, transaction, ownerUID, authzUID=None):
</ins><span class="cx">         self._txn = transaction
</span><span class="cx">         self._ownerUID = ownerUID
</span><ins>+        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
</ins><span class="cx">         self._resourceID = None
</span><span class="cx">         self._status = _HOME_STATUS_NORMAL
</span><span class="cx">         self._dataVersion = None
</span><span class="lines">@@ -3052,11 +3066,11 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def homeWithUID(cls, txn, uid, create=False):
</del><ins>+    def homeWithUID(cls, txn, uid, create=False, authzUID=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param uid: I'm going to assume uid is utf-8 encoded bytes
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        homeObject = yield cls.makeClass(txn, uid)
</del><ins>+        homeObject = yield cls.makeClass(txn, uid, authzUID=authzUID)
</ins><span class="cx">         if homeObject is not None:
</span><span class="cx">             returnValue(homeObject)
</span><span class="cx">         else:
</span><span class="lines">@@ -3091,7 +3105,7 @@
</span><span class="cx">                 yield savepoint.rollback(txn)
</span><span class="cx"> 
</span><span class="cx">                 # Retry the query - row may exist now, if not re-raise
</span><del>-                homeObject = yield cls.makeClass(txn, uid)
</del><ins>+                homeObject = yield cls.makeClass(txn, uid, authzUID=authzUID)
</ins><span class="cx">                 if homeObject:
</span><span class="cx">                     returnValue(homeObject)
</span><span class="cx">                 else:
</span><span class="lines">@@ -3102,7 +3116,7 @@
</span><span class="cx">                 # Note that we must not cache the owner_uid-&gt;resource_id
</span><span class="cx">                 # mapping in _cacher when creating as we don't want that to appear
</span><span class="cx">                 # until AFTER the commit
</span><del>-                home = yield cls.makeClass(txn, uid, no_cache=True)
</del><ins>+                home = yield cls.makeClass(txn, uid, no_cache=True, authzUID=authzUID)
</ins><span class="cx">                 yield home.createdHome()
</span><span class="cx">                 returnValue(home)
</span><span class="cx"> 
</span><span class="lines">@@ -3140,6 +3154,15 @@
</span><span class="cx">         return self._ownerUID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def authzuid(self):
+        &quot;&quot;&quot;
+        Retrieve the unique identifier of the user accessing the data in this home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return self._authzUID
+
+
</ins><span class="cx">     def external(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Is this an external home.
</span><span class="lines">@@ -3540,9 +3563,13 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self):
</span><ins>+
+        # Use any authz uid in place of the viewer uid so delegates have their own
+        # set of properties
</ins><span class="cx">         props = yield PropertyStore.load(
</span><span class="cx">             self.uid(),
</span><span class="cx">             self.uid(),
</span><ins>+            self.authzuid(),
</ins><span class="cx">             self._txn,
</span><span class="cx">             self._resourceID,
</span><span class="cx">             notifyCallback=self.notifyChanged
</span><span class="lines">@@ -4652,6 +4679,9 @@
</span><span class="cx">     def ownerView(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the owner resource counterpart of this shared resource.
</span><ins>+
+        Note we have to play a trick with the property store to coerce it to match
+        the per-user properties for the owner.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Get the child of the owner home that has the same resource id as the owned one
</span><span class="cx">         ownerView = yield self.ownerHome().childWithID(self.id())
</span><span class="lines">@@ -4662,10 +4692,13 @@
</span><span class="cx">     def shareeView(self, shareeUID):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the shared resource counterpart of this owned resource for the specified sharee.
</span><ins>+
+        Note we have to play a trick with the property store to coerce it to match
+        the per-user properties for the sharee.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Get the child of the sharee home that has the same resource id as the owned one
</span><del>-        shareeHome = yield self._txn.homeWithUID(self._home._homeType, shareeUID)
</del><ins>+        shareeHome = yield self._txn.homeWithUID(self._home._homeType, shareeUID, authzUID=shareeUID)
</ins><span class="cx">         shareeView = (yield shareeHome.allChildWithID(self.id())) if shareeHome is not None else None
</span><span class="cx">         returnValue(shareeView)
</span><span class="cx"> 
</span><span class="lines">@@ -5434,7 +5467,7 @@
</span><span class="cx">             childResourceIDs = [dataRow[2] for dataRow in dataRows]
</span><span class="cx"> 
</span><span class="cx">             propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
</span><del>-                home.uid(), home._txn, childResourceIDs
</del><ins>+                home.uid(), None, None, home._txn, childResourceIDs
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">             # Get revisions
</span><span class="lines">@@ -6216,9 +6249,12 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self, props=None):
</span><span class="cx">         if props is None:
</span><ins>+            # Use any authz uid in place of the viewer uid so delegates have their own
+            # set of properties
</ins><span class="cx">             props = yield PropertyStore.load(
</span><span class="cx">                 self.ownerHome().uid(),
</span><span class="cx">                 self.viewerHome().uid(),
</span><ins>+                self.viewerHome().authzuid(),
</ins><span class="cx">                 self._txn,
</span><span class="cx">                 self._resourceID,
</span><span class="cx">                 notifyCallback=self.notifyPropertyChanged
</span><span class="lines">@@ -6534,6 +6570,8 @@
</span><span class="cx">             if parent.objectResourcesHaveProperties():
</span><span class="cx">                 propertyStores = (yield PropertyStore.forMultipleResources(
</span><span class="cx">                     parent._home.uid(),
</span><ins>+                    None,
+                    None,
</ins><span class="cx">                     parent._txn,
</span><span class="cx">                     cls._objectSchema.RESOURCE_ID,
</span><span class="cx">                     cls._objectSchema.PARENT_RESOURCE_ID,
</span><span class="lines">@@ -6611,6 +6649,8 @@
</span><span class="cx">             if parent.objectResourcesHaveProperties():
</span><span class="cx">                 propertyStores = (yield PropertyStore.forMultipleResourcesWithResourceIDs(
</span><span class="cx">                     parent._home.uid(),
</span><ins>+                    None,
+                    None,
</ins><span class="cx">                     parent._txn,
</span><span class="cx">                     tuple([row[0] for row in dataRows]),
</span><span class="cx">                 ))
</span><span class="lines">@@ -6789,8 +6829,9 @@
</span><span class="cx">         if props is None:
</span><span class="cx">             if self._parentCollection.objectResourcesHaveProperties():
</span><span class="cx">                 props = yield PropertyStore.load(
</span><ins>+                    self._parentCollection.ownerHome().uid(),
</ins><span class="cx">                     self._parentCollection.viewerHome().uid(),
</span><del>-                    self._parentCollection.ownerHome().uid(),
</del><ins>+                    self._parentCollection.viewerHome().authzuid(),
</ins><span class="cx">                     self._txn,
</span><span class="cx">                     self._resourceID,
</span><span class="cx">                     created=created
</span><span class="lines">@@ -7153,6 +7194,7 @@
</span><span class="cx">         self._propertyStore = yield PropertyStore.load(
</span><span class="cx">             self._uid,
</span><span class="cx">             self._uid,
</span><ins>+            None,
</ins><span class="cx">             self._txn,
</span><span class="cx">             self._resourceID,
</span><span class="cx">             notifyCallback=self.notifyChanged
</span><span class="lines">@@ -7492,6 +7534,8 @@
</span><span class="cx">             # Get property stores for all these child resources (if any found)
</span><span class="cx">             propertyStores = (yield PropertyStore.forMultipleResources(
</span><span class="cx">                 parent.uid(),
</span><ins>+                None,
+                None,
</ins><span class="cx">                 parent._txn,
</span><span class="cx">                 schema.NOTIFICATION.RESOURCE_ID,
</span><span class="cx">                 schema.NOTIFICATION.NOTIFICATION_HOME_RESOURCE_ID,
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_external.py (13619 => 13620)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -149,6 +149,7 @@
</span><span class="cx">         props = yield PropertyStore.load(
</span><span class="cx">             self.uid(),
</span><span class="cx">             self.uid(),
</span><ins>+            None,
</ins><span class="cx">             self._txn,
</span><span class="cx">             self._resourceID,
</span><span class="cx">             notifyCallback=self.notifyChanged
</span></span></pre>
</div>
</div>

</body>
</html>