<!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>[12881] CalendarServer/branches/users/sagen/move2who-2</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/12881">12881</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-03-12 11:49:18 -0700 (Wed, 12 Mar 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Porting more over to twext.who; removed twistedcaldav/directory/directory.py</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who2calendarserveraccesslogpy">CalendarServer/branches/users/sagen/move2who-2/calendarserver/accesslog.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2calendarservertapcaldavpy">CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2calendarservertaputilpy">CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2calendarservertoolsprincipalspy">CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/principals.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2calendarservertoolsutilpy">CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2confauthaccountstestxml">CalendarServer/branches/users/sagen/move2who-2/conf/auth/accounts-test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2contribperformanceloadtesttest_simpy">CalendarServer/branches/users/sagen/move2who-2/contrib/performance/loadtest/test_sim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryaddressbookpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/addressbook.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycalendarpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycalendaruserproxypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendaruserproxy.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycommonpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/common.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryprincipalpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/principal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytestaccountsxml">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_augmentpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_principalpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_principal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryutilpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorywikipy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavextensionspy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/extensions.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavstdconfigpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavstorebridgepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_addressbookmultigetpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookmultiget.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_addressbookquerypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_calendarquerypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_calendarquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_collectioncontentspy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_collectioncontents.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_mkcalendarpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_mkcalendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_multigetpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_multiget.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_propspy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_props.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_resourcepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_sharingpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_wrappingpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_wrapping.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavtestutilpy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavupgradepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/upgrade.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txdavdpsclientpy">CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txdavwhoaugmentpy">CalendarServer/branches/users/sagen/move2who-2/txdav/who/augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txdavwhodirectorypy">CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txdavwhogroupspy">CalendarServer/branches/users/sagen/move2who-2/txdav/who/groups.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txweb2channelhttppy">CalendarServer/branches/users/sagen/move2who-2/txweb2/channel/http.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2txweb2serverpy">CalendarServer/branches/users/sagen/move2who-2/txweb2/server.py</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryaggregatepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/aggregate.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryappleopendirectorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/appleopendirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycachingdirectorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/cachingdirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorydirectorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryldapdirectorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/ldapdirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_aggregatepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_aggregate.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_buildquerypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_buildquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_cachedirectorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_cachedirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_directorypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_modifypy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_modify.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_proxyprincipalmemberspy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_proxyprincipalmembers.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_resourcespy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_xmlfilepy">CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_xmlfile.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagenmove2who2calendarserveraccesslogpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/accesslog.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/accesslog.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/accesslog.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -48,7 +48,6 @@
</span><span class="cx"> from twisted.protocols import amp
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.directory import DirectoryService
</del><span class="cx"> 
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><span class="lines">@@ -91,17 +90,17 @@
</span><span class="cx">                     if hasattr(request, &quot;authzUser&quot;) and str(request.authzUser.children[0]) != uidn:
</span><span class="cx">                         uidz = str(request.authzUser.children[0])
</span><span class="cx"> 
</span><del>-                    def convertUIDtoShortName(uid):
-                        uid = uid.rstrip(&quot;/&quot;)
-                        uid = uid[uid.rfind(&quot;/&quot;) + 1:]
-                        record = request.site.resource.getDirectory().recordWithUID(uid)
-                        if record:
-                            if record.recordType == DirectoryService.recordType_users:
-                                return record.shortNames[0]
-                            else:
-                                return &quot;(%s)%s&quot; % (record.recordType, record.shortNames[0],)
-                        else:
-                            return uid
</del><ins>+                    # def convertUIDtoShortName(uid):
+                    #     uid = uid.rstrip(&quot;/&quot;)
+                    #     uid = uid[uid.rfind(&quot;/&quot;) + 1:]
+                    #     record = request.site.resource.getDirectory().recordWithUID(uid)
+                    #     if record:
+                    #         if record.recordType == DirectoryService.recordType_users:
+                    #             return record.shortNames[0]
+                    #         else:
+                    #             return &quot;(%s)%s&quot; % (record.recordType, record.shortNames[0],)
+                    #     else:
+                    #         return uid
</ins><span class="cx"> 
</span><span class="cx">                     # MOVE2WHO
</span><span class="cx">                     # Better to stick the records directly on the request at
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2calendarservertapcaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -88,12 +88,10 @@
</span><span class="cx"> )
</span><span class="cx"> from txdav.dps.server import directoryFromConfig
</span><span class="cx"> from txdav.dps.client import DirectoryService as DirectoryProxyClientService
</span><del>-from txdav.who.groups import GroupCacher as NewGroupCacher
</del><ins>+from txdav.who.groups import GroupCacher
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav import memcachepool
</span><span class="cx"> from twistedcaldav.config import config, ConfigurationError
</span><del>-from twistedcaldav.directory import calendaruserproxy
-from twistedcaldav.directory.directory import GroupMembershipCacheUpdater
</del><span class="cx"> from txdav.who.groups import scheduleNextGroupCachingUpdate
</span><span class="cx"> from twistedcaldav.localization import processLocalizationFiles
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
</span><span class="lines">@@ -550,10 +548,7 @@
</span><span class="cx">             )
</span><span class="cx">             self.monitor.addProcessObject(process, PARENT_ENVIRONMENT)
</span><span class="cx"> 
</span><del>-        if (
-            config.DirectoryProxy.Enabled and
-            config.DirectoryProxy.SocketPath != &quot;&quot;
-        ):
</del><ins>+        if (config.DirectoryProxy.SocketPath != &quot;&quot;):
</ins><span class="cx">             log.info(&quot;Adding directory proxy service&quot;)
</span><span class="cx"> 
</span><span class="cx">             dpsArgv = [
</span><span class="lines">@@ -1004,26 +999,18 @@
</span><span class="cx"> 
</span><span class="cx">         # Optionally set up group cacher
</span><span class="cx">         if config.GroupCaching.Enabled:
</span><del>-            groupCacher = GroupMembershipCacheUpdater(
-                calendaruserproxy.ProxyDBService,
</del><ins>+            groupCacher = GroupCacher(
</ins><span class="cx">                 directory,
</span><del>-                config.GroupCaching.UpdateSeconds,
-                config.GroupCaching.ExpireSeconds,
-                config.GroupCaching.LockSeconds,
-                namespace=config.GroupCaching.MemcachedPool,
-                useExternalProxies=config.GroupCaching.UseExternalProxies,
</del><ins>+                updateSeconds=config.GroupCaching.UpdateSeconds
</ins><span class="cx">             )
</span><del>-            newGroupCacher = NewGroupCacher(directory)
</del><span class="cx">         else:
</span><span class="cx">             groupCacher = None
</span><del>-            newGroupCacher = None
</del><span class="cx"> 
</span><span class="cx">         def decorateTransaction(txn):
</span><span class="cx">             txn._pushDistributor = pushDistributor
</span><span class="cx">             txn._rootResource = result.rootResource
</span><span class="cx">             txn._mailRetriever = mailRetriever
</span><span class="cx">             txn._groupCacher = groupCacher
</span><del>-            txn._newGroupCacher = newGroupCacher
</del><span class="cx"> 
</span><span class="cx">         store.callWithNewTransactions(decorateTransaction)
</span><span class="cx"> 
</span><span class="lines">@@ -1357,19 +1344,12 @@
</span><span class="cx"> 
</span><span class="cx">             # Optionally set up group cacher
</span><span class="cx">             if config.GroupCaching.Enabled:
</span><del>-                groupCacher = GroupMembershipCacheUpdater(
-                    calendaruserproxy.ProxyDBService,
</del><ins>+                groupCacher = GroupCacher(
</ins><span class="cx">                     directory,
</span><del>-                    config.GroupCaching.UpdateSeconds,
-                    config.GroupCaching.ExpireSeconds,
-                    config.GroupCaching.LockSeconds,
-                    namespace=config.GroupCaching.MemcachedPool,
-                    useExternalProxies=config.GroupCaching.UseExternalProxies
</del><ins>+                    updateSeconds=config.GroupCaching.UpdateSeconds
</ins><span class="cx">                 )
</span><del>-                newGroupCacher = NewGroupCacher(directory)
</del><span class="cx">             else:
</span><span class="cx">                 groupCacher = None
</span><del>-                newGroupCacher = None
</del><span class="cx"> 
</span><span class="cx">             # Optionally enable Manhole access
</span><span class="cx">             if config.Manhole.Enabled:
</span><span class="lines">@@ -1405,7 +1385,6 @@
</span><span class="cx">                 txn._rootResource = result.rootResource
</span><span class="cx">                 txn._mailRetriever = mailRetriever
</span><span class="cx">                 txn._groupCacher = groupCacher
</span><del>-                txn._newGroupCacher = newGroupCacher
</del><span class="cx"> 
</span><span class="cx">             store.callWithNewTransactions(decorateTransaction)
</span><span class="cx"> 
</span><span class="lines">@@ -1942,26 +1921,18 @@
</span><span class="cx"> 
</span><span class="cx">             # Optionally set up group cacher
</span><span class="cx">             if config.GroupCaching.Enabled:
</span><del>-                groupCacher = GroupMembershipCacheUpdater(
-                    calendaruserproxy.ProxyDBService,
</del><ins>+                groupCacher = GroupCacher(
</ins><span class="cx">                     directory,
</span><del>-                    config.GroupCaching.UpdateSeconds,
-                    config.GroupCaching.ExpireSeconds,
-                    config.GroupCaching.LockSeconds,
-                    namespace=config.GroupCaching.MemcachedPool,
-                    useExternalProxies=config.GroupCaching.UseExternalProxies
</del><ins>+                    updateSeconds=config.GroupCaching.UpdateSeconds
</ins><span class="cx">                 )
</span><del>-                newGroupCacher = NewGroupCacher(directory)
</del><span class="cx">             else:
</span><span class="cx">                 groupCacher = None
</span><del>-                newGroupCacher = None
</del><span class="cx"> 
</span><span class="cx">             def decorateTransaction(txn):
</span><span class="cx">                 txn._pushDistributor = None
</span><span class="cx">                 txn._rootResource = rootResource
</span><span class="cx">                 txn._mailRetriever = mailRetriever
</span><span class="cx">                 txn._groupCacher = groupCacher
</span><del>-                txn._newGroupCacher = newGroupCacher
</del><span class="cx"> 
</span><span class="cx">             store.callWithNewTransactions(decorateTransaction)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2calendarservertaputilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/util.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/util.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/util.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -53,10 +53,8 @@
</span><span class="cx"> from twistedcaldav.cache import CacheStoreNotifierFactory
</span><span class="cx"> from twistedcaldav.directory import calendaruserproxy
</span><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
</span><del>-from twistedcaldav.directory.aggregate import AggregateDirectoryService
</del><span class="cx"> from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.digest import QopDigestCredentialFactory
</span><del>-from twistedcaldav.directory.directory import GroupMembershipCache
</del><span class="cx"> from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.wiki import WikiDirectoryService
</span><span class="cx"> from calendarserver.push.notifier import NotifierFactory
</span><span class="lines">@@ -491,7 +489,7 @@
</span><span class="cx">     portal.registerChecker(HTTPDigestCredentialChecker(directory))
</span><span class="cx">     portal.registerChecker(PrincipalCredentialChecker())
</span><span class="cx"> 
</span><del>-    realm = directory.realmName or &quot;&quot;
</del><ins>+    realm = directory.realmName.encode(&quot;utf-8&quot;) or &quot;&quot;
</ins><span class="cx"> 
</span><span class="cx">     log.info(&quot;Configuring authentication for realm: {realm}&quot;, realm=realm)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2calendarservertoolsprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/principals.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/principals.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/principals.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -30,7 +30,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.directory import UnknownRecordTypeError
</del><span class="cx"> from txdav.who.groups import schedulePolledGroupCachingUpdate
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tools.util import (
</span><span class="lines">@@ -44,11 +43,6 @@
</span><span class="cx"> 
</span><span class="cx"> def usage(e=None):
</span><span class="cx">     if e:
</span><del>-        if isinstance(e, UnknownRecordTypeError):
-            print(&quot;Valid record types:&quot;)
-            for recordType in config.directory.recordTypes():
-                print(&quot;    %s&quot; % (recordType,))
-
</del><span class="cx">         print(e)
</span><span class="cx">         print(&quot;&quot;)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2calendarservertoolsutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/util.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/util.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/util.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -20,8 +20,6 @@
</span><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><span class="cx">     &quot;loadConfig&quot;,
</span><del>-    &quot;getDirectory&quot;,
-    &quot;dummyDirectoryRecord&quot;,
</del><span class="cx">     &quot;UsageError&quot;,
</span><span class="cx">     &quot;booleanArgument&quot;,
</span><span class="cx"> ]
</span><span class="lines">@@ -48,8 +46,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import memcachepool
</span><span class="cx"> from twistedcaldav.directory import calendaruserproxy
</span><del>-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
</del><ins>+# from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
</ins><span class="cx"> from txdav.who.groups import schedulePolledGroupCachingUpdate
</span><span class="cx"> from calendarserver.push.notifier import NotifierFactory
</span><span class="cx"> 
</span><span class="lines">@@ -78,145 +75,145 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def getDirectory(config=config):
</del><ins>+# def getDirectory(config=config):
</ins><span class="cx"> 
</span><del>-    class MyDirectoryService (AggregateDirectoryService):
-        def getPrincipalCollection(self):
-            if not hasattr(self, &quot;_principalCollection&quot;):
</del><ins>+#     class MyDirectoryService (AggregateDirectoryService):
+#         def getPrincipalCollection(self):
+#             if not hasattr(self, &quot;_principalCollection&quot;):
</ins><span class="cx"> 
</span><del>-                if config.Notifications.Enabled:
-                    # FIXME: NotifierFactory needs reference to the store in order
-                    # to get a txn in order to create a Work item
-                    notifierFactory = NotifierFactory(
-                        None, config.ServerHostName,
-                        config.Notifications.CoalesceSeconds,
-                    )
-                else:
-                    notifierFactory = None
</del><ins>+#                 if config.Notifications.Enabled:
+#                     # FIXME: NotifierFactory needs reference to the store in order
+#                     # to get a txn in order to create a Work item
+#                     notifierFactory = NotifierFactory(
+#                         None, config.ServerHostName,
+#                         config.Notifications.CoalesceSeconds,
+#                     )
+#                 else:
+#                     notifierFactory = None
</ins><span class="cx"> 
</span><del>-                # Need a data store
-                _newStore = CommonDataStore(FilePath(config.DocumentRoot),
-                    notifierFactory, self, True, False)
-                if notifierFactory is not None:
-                    notifierFactory.store = _newStore
</del><ins>+#                 # Need a data store
+#                 _newStore = CommonDataStore(FilePath(config.DocumentRoot),
+#                     notifierFactory, self, True, False)
+#                 if notifierFactory is not None:
+#                     notifierFactory.store = _newStore
</ins><span class="cx"> 
</span><del>-                #
-                # Instantiating a DirectoryCalendarHomeProvisioningResource with a directory
-                # will register it with the directory (still smells like a hack).
-                #
-                # We need that in order to locate calendar homes via the directory.
-                #
-                from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-                DirectoryCalendarHomeProvisioningResource(self, &quot;/calendars/&quot;, _newStore)
</del><ins>+#                 #
+#                 # Instantiating a DirectoryCalendarHomeProvisioningResource with a directory
+#                 # will register it with the directory (still smells like a hack).
+#                 #
+#                 # We need that in order to locate calendar homes via the directory.
+#                 #
+#                 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+#                 DirectoryCalendarHomeProvisioningResource(self, &quot;/calendars/&quot;, _newStore)
</ins><span class="cx"> 
</span><del>-                from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-                self._principalCollection = DirectoryPrincipalProvisioningResource(&quot;/principals/&quot;, self)
</del><ins>+#                 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+#                 self._principalCollection = DirectoryPrincipalProvisioningResource(&quot;/principals/&quot;, self)
</ins><span class="cx"> 
</span><del>-            return self._principalCollection
</del><ins>+#             return self._principalCollection
</ins><span class="cx"> 
</span><del>-        def setPrincipalCollection(self, coll):
-            # See principal.py line 237:  self.directory.principalCollection = self
-            pass
</del><ins>+#         def setPrincipalCollection(self, coll):
+#             # See principal.py line 237:  self.directory.principalCollection = self
+#             pass
</ins><span class="cx"> 
</span><del>-        principalCollection = property(getPrincipalCollection, setPrincipalCollection)
</del><ins>+#         principalCollection = property(getPrincipalCollection, setPrincipalCollection)
</ins><span class="cx"> 
</span><del>-        def calendarHomeForRecord(self, record):
-            principal = self.principalCollection.principalForRecord(record)
-            if principal:
-                try:
-                    return principal.calendarHome()
-                except AttributeError:
-                    pass
-            return None
</del><ins>+#         def calendarHomeForRecord(self, record):
+#             principal = self.principalCollection.principalForRecord(record)
+#             if principal:
+#                 try:
+#                     return principal.calendarHome()
+#                 except AttributeError:
+#                     pass
+#             return None
</ins><span class="cx"> 
</span><del>-        def calendarHomeForShortName(self, recordType, shortName):
-            principal = self.principalCollection.principalForShortName(recordType, shortName)
-            if principal:
-                return principal.calendarHome()
-            return None
</del><ins>+#         def calendarHomeForShortName(self, recordType, shortName):
+#             principal = self.principalCollection.principalForShortName(recordType, shortName)
+#             if principal:
+#                 return principal.calendarHome()
+#             return None
</ins><span class="cx"> 
</span><del>-        def principalForCalendarUserAddress(self, cua):
-            return self.principalCollection.principalForCalendarUserAddress(cua)
</del><ins>+#         def principalForCalendarUserAddress(self, cua):
+#             return self.principalCollection.principalForCalendarUserAddress(cua)
</ins><span class="cx"> 
</span><del>-        def principalForUID(self, uid):
-            return self.principalCollection.principalForUID(uid)
</del><ins>+#         def principalForUID(self, uid):
+#             return self.principalCollection.principalForUID(uid)
</ins><span class="cx"> 
</span><del>-    # Load augment/proxy db classes now
-    if config.AugmentService.type:
-        augmentClass = namedClass(config.AugmentService.type)
-        augmentService = augmentClass(**config.AugmentService.params)
-    else:
-        augmentService = None
</del><ins>+#     # Load augment/proxy db classes now
+#     if config.AugmentService.type:
+#         augmentClass = namedClass(config.AugmentService.type)
+#         augmentService = augmentClass(**config.AugmentService.params)
+#     else:
+#         augmentService = None
</ins><span class="cx"> 
</span><del>-    proxydbClass = namedClass(config.ProxyDBService.type)
-    calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
</del><ins>+#     proxydbClass = namedClass(config.ProxyDBService.type)
+#     calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
</ins><span class="cx"> 
</span><del>-    # Wait for directory service to become available
-    BaseDirectoryService = namedClass(config.DirectoryService.type)
-    config.DirectoryService.params.augmentService = augmentService
-    directory = BaseDirectoryService(config.DirectoryService.params)
-    while not directory.isAvailable():
-        sleep(5)
</del><ins>+#     # Wait for directory service to become available
+#     BaseDirectoryService = namedClass(config.DirectoryService.type)
+#     config.DirectoryService.params.augmentService = augmentService
+#     directory = BaseDirectoryService(config.DirectoryService.params)
+#     while not directory.isAvailable():
+#         sleep(5)
</ins><span class="cx"> 
</span><del>-    directories = [directory]
</del><ins>+#     directories = [directory]
</ins><span class="cx"> 
</span><del>-    if config.ResourceService.Enabled:
-        resourceClass = namedClass(config.ResourceService.type)
-        config.ResourceService.params.augmentService = augmentService
-        resourceDirectory = resourceClass(config.ResourceService.params)
-        resourceDirectory.realmName = directory.realmName
-        directories.append(resourceDirectory)
</del><ins>+#     if config.ResourceService.Enabled:
+#         resourceClass = namedClass(config.ResourceService.type)
+#         config.ResourceService.params.augmentService = augmentService
+#         resourceDirectory = resourceClass(config.ResourceService.params)
+#         resourceDirectory.realmName = directory.realmName
+#         directories.append(resourceDirectory)
</ins><span class="cx"> 
</span><del>-    aggregate = MyDirectoryService(directories, None)
-    aggregate.augmentService = augmentService
</del><ins>+#     aggregate = MyDirectoryService(directories, None)
+#     aggregate.augmentService = augmentService
</ins><span class="cx"> 
</span><del>-    #
-    # Wire up the resource hierarchy
-    #
-    principalCollection = aggregate.getPrincipalCollection()
-    root = RootResource(
-        config.DocumentRoot,
-        principalCollections=(principalCollection,),
-    )
-    root.putChild(&quot;principals&quot;, principalCollection)
</del><ins>+#     #
+#     # Wire up the resource hierarchy
+#     #
+#     principalCollection = aggregate.getPrincipalCollection()
+#     root = RootResource(
+#         config.DocumentRoot,
+#         principalCollections=(principalCollection,),
+#     )
+#     root.putChild(&quot;principals&quot;, principalCollection)
</ins><span class="cx"> 
</span><del>-    # Need a data store
-    _newStore = CommonDataStore(FilePath(config.DocumentRoot), None, aggregate, True, False)
</del><ins>+#     # Need a data store
+#     _newStore = CommonDataStore(FilePath(config.DocumentRoot), None, aggregate, True, False)
</ins><span class="cx"> 
</span><del>-    from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-    calendarCollection = DirectoryCalendarHomeProvisioningResource(
-        aggregate, &quot;/calendars/&quot;,
-        _newStore,
-    )
-    root.putChild(&quot;calendars&quot;, calendarCollection)
</del><ins>+#     from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+#     calendarCollection = DirectoryCalendarHomeProvisioningResource(
+#         aggregate, &quot;/calendars/&quot;,
+#         _newStore,
+#     )
+#     root.putChild(&quot;calendars&quot;, calendarCollection)
</ins><span class="cx"> 
</span><del>-    return aggregate
</del><ins>+#     return aggregate
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DummyDirectoryService (DirectoryService):
-    realmName = &quot;&quot;
-    baseGUID = &quot;51856FD4-5023-4890-94FE-4356C4AAC3E4&quot;
-    def recordTypes(self):
-        return ()
</del><ins>+# class DummyDirectoryService (DirectoryService):
+#     realmName = &quot;&quot;
+#     baseGUID = &quot;51856FD4-5023-4890-94FE-4356C4AAC3E4&quot;
+#     def recordTypes(self):
+#         return ()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def listRecords(self):
-        return ()
</del><ins>+#     def listRecords(self):
+#         return ()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def recordWithShortName(self):
-        return None
</del><ins>+#     def recordWithShortName(self):
+#         return None
</ins><span class="cx"> 
</span><del>-dummyDirectoryRecord = DirectoryRecord(
-    service=DummyDirectoryService(),
-    recordType=&quot;dummy&quot;,
-    guid=&quot;8EF0892F-7CB6-4B8E-B294-7C5A5321136A&quot;,
-    shortNames=(&quot;dummy&quot;,),
-    fullName=&quot;Dummy McDummerson&quot;,
-    firstName=&quot;Dummy&quot;,
-    lastName=&quot;McDummerson&quot;,
-)
</del><ins>+# dummyDirectoryRecord = DirectoryRecord(
+#     service=DummyDirectoryService(),
+#     recordType=&quot;dummy&quot;,
+#     guid=&quot;8EF0892F-7CB6-4B8E-B294-7C5A5321136A&quot;,
+#     shortNames=(&quot;dummy&quot;,),
+#     fullName=&quot;Dummy McDummerson&quot;,
+#     firstName=&quot;Dummy&quot;,
+#     lastName=&quot;McDummerson&quot;,
+# )
</ins><span class="cx"> 
</span><span class="cx"> class UsageError (StandardError):
</span><span class="cx">     pass
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2confauthaccountstestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/conf/auth/accounts-test.xml (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/conf/auth/accounts-test.xml        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/conf/auth/accounts-test.xml        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -18,172 +18,339 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
</span><span class="cx"> 
</span><del>-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;user&gt;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;admin&lt;/uid&gt;
</span><del>-    &lt;guid&gt;admin&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;admin&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;admin&lt;/password&gt;
</span><del>-    &lt;name&gt;Super User&lt;/name&gt;
-    &lt;first-name&gt;Super&lt;/first-name&gt;
-    &lt;last-name&gt;User&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Super User&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;apprentice&lt;/uid&gt;
</span><del>-    &lt;guid&gt;apprentice&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;apprentice&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;apprentice&lt;/password&gt;
</span><del>-    &lt;name&gt;Apprentice Super User&lt;/name&gt;
-    &lt;first-name&gt;Apprentice&lt;/first-name&gt;
-    &lt;last-name&gt;Super User&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Apprentice Super User&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;wsanchez&lt;/uid&gt;
</span><del>-    &lt;guid&gt;wsanchez&lt;/guid&gt;
-    &lt;email-address&gt;wsanchez@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;wsanchez&lt;/short-name&gt;
+    &lt;email&gt;wsanchez@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;test&lt;/password&gt;
</span><del>-    &lt;name&gt;Wilfredo Sanchez Vega&lt;/name&gt;
-    &lt;first-name&gt;Wilfredo&lt;/first-name&gt;
-    &lt;last-name&gt;Sanchez Vega&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Wilfredo Sanchez Vega&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;cdaboo&lt;/uid&gt;
</span><del>-    &lt;guid&gt;cdaboo&lt;/guid&gt;
-    &lt;email-address&gt;cdaboo@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;cdaboo&lt;/short-name&gt;
+    &lt;email&gt;cdaboo@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;test&lt;/password&gt;
</span><del>-    &lt;name&gt;Cyrus Daboo&lt;/name&gt;
-    &lt;first-name&gt;Cyrus&lt;/first-name&gt;
-    &lt;last-name&gt;Daboo&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;cyrus Daboo&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;sagen&lt;/uid&gt;
</span><del>-    &lt;guid&gt;sagen&lt;/guid&gt;
-    &lt;email-address&gt;sagen@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;sagen&lt;/short-name&gt;
+    &lt;email&gt;sagen@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;test&lt;/password&gt;
</span><del>-    &lt;name&gt;Morgen Sagen&lt;/name&gt;
-    &lt;first-name&gt;Morgen&lt;/first-name&gt;
-    &lt;last-name&gt;Sagen&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Morgen Sagen&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;dre&lt;/uid&gt;
</span><del>-    &lt;guid&gt;andre&lt;/guid&gt;
-    &lt;email-address&gt;dre@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;andre&lt;/short-name&gt;
+    &lt;email&gt;dre@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;test&lt;/password&gt;
</span><del>-    &lt;name&gt;Andre LaBranche&lt;/name&gt;
-    &lt;first-name&gt;Andre&lt;/first-name&gt;
-    &lt;last-name&gt;LaBranche&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Andre LaBranche&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;glyph&lt;/uid&gt;
</span><del>-    &lt;guid&gt;glyph&lt;/guid&gt;
-    &lt;email-address&gt;glyph@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;glyph&lt;/short-name&gt;
+    &lt;email&gt;glyph@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;test&lt;/password&gt;
</span><del>-    &lt;name&gt;Glyph Lefkowitz&lt;/name&gt;
-    &lt;first-name&gt;Glyph&lt;/first-name&gt;
-    &lt;last-name&gt;Lefkowitz&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Glyph Lefkowitz&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;i18nuser&lt;/uid&gt;
</span><del>-    &lt;guid&gt;i18nuser&lt;/guid&gt;
-    &lt;email-address&gt;i18nuser@example.com&lt;/email-address&gt;
</del><ins>+    &lt;short-name&gt;i18nuser&lt;/short-name&gt;
+    &lt;email&gt;i18nuser@example.com&lt;/email&gt;
</ins><span class="cx">     &lt;password&gt;i18nuser&lt;/password&gt;
</span><del>-    &lt;name&gt;まだ&lt;/name&gt;
-    &lt;first-name&gt;ま&lt;/first-name&gt;
-    &lt;last-name&gt;だ&lt;/last-name&gt;
-  &lt;/user&gt;
</del><ins>+    &lt;full-name&gt;まだ&lt;/full-name&gt;
+  &lt;/record&gt;
+
+  &lt;!-- twext.who xml doesn't (yet) support repeat
</ins><span class="cx">   &lt;user repeat=&quot;101&quot;&gt;
</span><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;uid&gt;User %02d&lt;/uid&gt;
</span><del>-    &lt;guid&gt;user%02d&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;user%02d&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;user%02d&lt;/password&gt;
</span><del>-    &lt;name&gt;User %02d&lt;/name&gt;
-    &lt;first-name&gt;User&lt;/first-name&gt;
-    &lt;last-name&gt;%02d&lt;/last-name&gt;
-    &lt;email-address&gt;user%02d@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
</del><ins>+    &lt;full-name&gt;User %02d&lt;/full-name&gt;
+    &lt;email&gt;user%02d@example.com&lt;/email&gt;
+  &lt;/record&gt;
</ins><span class="cx">   &lt;user repeat=&quot;10&quot;&gt;
</span><span class="cx">     &lt;uid&gt;public%02d&lt;/uid&gt;
</span><del>-    &lt;guid&gt;public%02d&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;public%02d&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;public%02d&lt;/password&gt;
</span><del>-    &lt;name&gt;Public %02d&lt;/name&gt;
-    &lt;first-name&gt;Public&lt;/first-name&gt;
-    &lt;last-name&gt;%02d&lt;/last-name&gt;
-  &lt;/user&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Public %02d&lt;/full-name&gt;
+  &lt;/record&gt;
+  --&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user01&lt;/short-name&gt;
+    &lt;uid&gt;user01&lt;/uid&gt;
+    &lt;password&gt;user01&lt;/password&gt;
+    &lt;full-name&gt;User 01&lt;/full-name&gt;
+    &lt;email&gt;user01@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user02&lt;/short-name&gt;
+    &lt;uid&gt;user02&lt;/uid&gt;
+    &lt;password&gt;user02&lt;/password&gt;
+    &lt;full-name&gt;User 02&lt;/full-name&gt;
+    &lt;email&gt;user02@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user03&lt;/short-name&gt;
+    &lt;uid&gt;user03&lt;/uid&gt;
+    &lt;password&gt;user03&lt;/password&gt;
+    &lt;full-name&gt;User 03&lt;/full-name&gt;
+    &lt;email&gt;user03@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user04&lt;/short-name&gt;
+    &lt;uid&gt;user04&lt;/uid&gt;
+    &lt;password&gt;user04&lt;/password&gt;
+    &lt;full-name&gt;User 04&lt;/full-name&gt;
+    &lt;email&gt;user04@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user05&lt;/short-name&gt;
+    &lt;uid&gt;user05&lt;/uid&gt;
+    &lt;password&gt;user05&lt;/password&gt;
+    &lt;full-name&gt;User 05&lt;/full-name&gt;
+    &lt;email&gt;user05@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user06&lt;/short-name&gt;
+    &lt;uid&gt;user06&lt;/uid&gt;
+    &lt;password&gt;user06&lt;/password&gt;
+    &lt;full-name&gt;User 06&lt;/full-name&gt;
+    &lt;email&gt;user06@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user07&lt;/short-name&gt;
+    &lt;uid&gt;user07&lt;/uid&gt;
+    &lt;password&gt;user07&lt;/password&gt;
+    &lt;full-name&gt;User 07&lt;/full-name&gt;
+    &lt;email&gt;user07@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user08&lt;/short-name&gt;
+    &lt;uid&gt;user08&lt;/uid&gt;
+    &lt;password&gt;user08&lt;/password&gt;
+    &lt;full-name&gt;User 08&lt;/full-name&gt;
+    &lt;email&gt;user08@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user09&lt;/short-name&gt;
+    &lt;uid&gt;user09&lt;/uid&gt;
+    &lt;password&gt;user09&lt;/password&gt;
+    &lt;full-name&gt;User 09&lt;/full-name&gt;
+    &lt;email&gt;user09@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user10&lt;/short-name&gt;
+    &lt;uid&gt;user10&lt;/uid&gt;
+    &lt;password&gt;user10&lt;/password&gt;
+    &lt;full-name&gt;User 10&lt;/full-name&gt;
+    &lt;email&gt;user10@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user11&lt;/short-name&gt;
+    &lt;uid&gt;user11&lt;/uid&gt;
+    &lt;password&gt;user11&lt;/password&gt;
+    &lt;full-name&gt;User 11&lt;/full-name&gt;
+    &lt;email&gt;user11@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user12&lt;/short-name&gt;
+    &lt;uid&gt;user12&lt;/uid&gt;
+    &lt;password&gt;user12&lt;/password&gt;
+    &lt;full-name&gt;User 12&lt;/full-name&gt;
+    &lt;email&gt;user12@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user13&lt;/short-name&gt;
+    &lt;uid&gt;user13&lt;/uid&gt;
+    &lt;password&gt;user13&lt;/password&gt;
+    &lt;full-name&gt;User 13&lt;/full-name&gt;
+    &lt;email&gt;user13@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user14&lt;/short-name&gt;
+    &lt;uid&gt;user14&lt;/uid&gt;
+    &lt;password&gt;user14&lt;/password&gt;
+    &lt;full-name&gt;User 14&lt;/full-name&gt;
+    &lt;email&gt;user14@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user15&lt;/short-name&gt;
+    &lt;uid&gt;user15&lt;/uid&gt;
+    &lt;password&gt;user15&lt;/password&gt;
+    &lt;full-name&gt;User 15&lt;/full-name&gt;
+    &lt;email&gt;user15@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user16&lt;/short-name&gt;
+    &lt;uid&gt;user16&lt;/uid&gt;
+    &lt;password&gt;user16&lt;/password&gt;
+    &lt;full-name&gt;User 16&lt;/full-name&gt;
+    &lt;email&gt;user16@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user17&lt;/short-name&gt;
+    &lt;uid&gt;user17&lt;/uid&gt;
+    &lt;password&gt;user17&lt;/password&gt;
+    &lt;full-name&gt;User 17&lt;/full-name&gt;
+    &lt;email&gt;user17@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user18&lt;/short-name&gt;
+    &lt;uid&gt;user18&lt;/uid&gt;
+    &lt;password&gt;user18&lt;/password&gt;
+    &lt;full-name&gt;User 18&lt;/full-name&gt;
+    &lt;email&gt;user18@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user19&lt;/short-name&gt;
+    &lt;uid&gt;user19&lt;/uid&gt;
+    &lt;password&gt;user19&lt;/password&gt;
+    &lt;full-name&gt;User 19&lt;/full-name&gt;
+    &lt;email&gt;user19@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user20&lt;/short-name&gt;
+    &lt;uid&gt;user20&lt;/uid&gt;
+    &lt;password&gt;user20&lt;/password&gt;
+    &lt;full-name&gt;User 20&lt;/full-name&gt;
+    &lt;email&gt;user20@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user21&lt;/short-name&gt;
+    &lt;uid&gt;user21&lt;/uid&gt;
+    &lt;password&gt;user21&lt;/password&gt;
+    &lt;full-name&gt;User 21&lt;/full-name&gt;
+    &lt;email&gt;user21@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user22&lt;/short-name&gt;
+    &lt;uid&gt;user22&lt;/uid&gt;
+    &lt;password&gt;user22&lt;/password&gt;
+    &lt;full-name&gt;User 22&lt;/full-name&gt;
+    &lt;email&gt;user22@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user23&lt;/short-name&gt;
+    &lt;uid&gt;user23&lt;/uid&gt;
+    &lt;password&gt;user23&lt;/password&gt;
+    &lt;full-name&gt;User 23&lt;/full-name&gt;
+    &lt;email&gt;user23@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user24&lt;/short-name&gt;
+    &lt;uid&gt;user24&lt;/uid&gt;
+    &lt;password&gt;user24&lt;/password&gt;
+    &lt;full-name&gt;User 24&lt;/full-name&gt;
+    &lt;email&gt;user24@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user25&lt;/short-name&gt;
+    &lt;uid&gt;user25&lt;/uid&gt;
+    &lt;password&gt;user25&lt;/password&gt;
+    &lt;full-name&gt;User 25&lt;/full-name&gt;
+    &lt;email&gt;user25@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;group&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;group01&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group01&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group01&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group01&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 01&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user01&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Group 01&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user01&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;group02&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group02&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group02&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group02&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 02&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user06&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user07&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Group 02&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user06&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user07&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;group03&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group03&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group03&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group03&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 03&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user08&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user09&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Group 03&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user08&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user09&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;group04&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group04&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group04&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group04&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 04&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;group02&lt;/member&gt;
-      &lt;member type=&quot;groups&quot;&gt;group03&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user10&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt; &lt;!-- delegategroup --&gt;
</del><ins>+    &lt;full-name&gt;Group 04&lt;/full-name&gt;
+      &lt;member-uid type=&quot;groups&quot;&gt;group02&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;groups&quot;&gt;group03&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user10&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt; &lt;!-- delegategroup --&gt;
</ins><span class="cx">     &lt;uid&gt;group05&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group05&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group05&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group05&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 05&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;group06&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user20&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt; &lt;!-- delegatesubgroup --&gt;
</del><ins>+    &lt;full-name&gt;Group 05&lt;/full-name&gt;
+      &lt;member-uid type=&quot;groups&quot;&gt;group06&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user20&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt; &lt;!-- delegatesubgroup --&gt;
</ins><span class="cx">     &lt;uid&gt;group06&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group06&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group06&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group06&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 06&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user21&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt; &lt;!-- readonlydelegategroup --&gt;
</del><ins>+    &lt;full-name&gt;Group 06&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user21&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt; &lt;!-- readonlydelegategroup --&gt;
</ins><span class="cx">     &lt;uid&gt;group07&lt;/uid&gt;
</span><del>-    &lt;guid&gt;group07&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;group07&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;group07&lt;/password&gt;
</span><del>-    &lt;name&gt;Group 07&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user22&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user23&lt;/member&gt;
-      &lt;member type=&quot;users&quot;&gt;user24&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Group 07&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user22&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user23&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user24&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;disabledgroup&lt;/uid&gt;
</span><del>-    &lt;guid&gt;disabledgroup&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;disabledgroup&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;disabledgroup&lt;/password&gt;
</span><del>-    &lt;name&gt;Disabled Group&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;user01&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-&lt;/accounts&gt;
</del><ins>+    &lt;full-name&gt;Disabled Group&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user01&lt;/member-uid&gt;
+  &lt;/record&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2contribperformanceloadtesttest_simpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/contrib/performance/loadtest/test_sim.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/contrib/performance/loadtest/test_sim.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/contrib/performance/loadtest/test_sim.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -24,32 +24,34 @@
</span><span class="cx"> from twisted.internet.defer import Deferred, succeed
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> 
</span><del>-from twistedcaldav.directory.directory import DirectoryRecord
-
</del><span class="cx"> from contrib.performance.stats import NormalDistribution
</span><span class="cx"> from contrib.performance.loadtest.ical import OS_X_10_6
</span><span class="cx"> from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
</span><span class="cx"> from contrib.performance.loadtest.population import (
</span><span class="cx">     SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
</span><del>-    ProfileType, SimpleStatistics)
</del><ins>+    ProfileType, SimpleStatistics
+)
</ins><span class="cx"> from contrib.performance.loadtest.sim import (
</span><del>-    Arrival, SimOptions, LoadSimulator, LagTrackingReactor)
</del><ins>+    Arrival, SimOptions, LoadSimulator, LagTrackingReactor,
+    _DirectoryRecord
+)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> VALID_CONFIG = {
</span><span class="cx">     'server': 'tcp:127.0.0.1:8008',
</span><span class="cx">     'webadmin': {
</span><span class="cx">         'enabled': True,
</span><span class="cx">         'HTTPPort': 8080,
</span><del>-        },
</del><ins>+    },
</ins><span class="cx">     'arrival': {
</span><span class="cx">         'factory': 'contrib.performance.loadtest.population.SmoothRampUp',
</span><span class="cx">         'params': {
</span><span class="cx">             'groups': 10,
</span><span class="cx">             'groupSize': 1,
</span><span class="cx">             'interval': 3,
</span><del>-            },
</del><span class="cx">         },
</span><del>-    }
</del><ins>+    },
+}
</ins><span class="cx"> 
</span><span class="cx"> VALID_CONFIG_PLIST = writePlistToString(VALID_CONFIG)
</span><span class="cx"> 
</span><span class="lines">@@ -104,8 +106,9 @@
</span><span class="cx">     realmName = 'stub'
</span><span class="cx"> 
</span><span class="cx">     def _user(self, name):
</span><del>-        record = DirectoryRecord(self, 'user', name, (name,))
-        record.password = 'password-' + name
</del><ins>+        password = 'password-' + name
+        email = name + &quot;@example.com&quot;
+        record = _DirectoryRecord(name, password, name, email)
</ins><span class="cx">         return record
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -119,10 +122,10 @@
</span><span class="cx">             [self._user('alice'), self._user('bob'), self._user('carol')],
</span><span class="cx">             Populator(None), None, None, 'http://example.org:1234/', None, None)
</span><span class="cx">         users = sorted([
</span><del>-                calsim._createUser(0)[0],
-                calsim._createUser(1)[0],
-                calsim._createUser(2)[0],
-                ])
</del><ins>+            calsim._createUser(0)[0],
+            calsim._createUser(1)[0],
+            calsim._createUser(2)[0],
+        ])
</ins><span class="cx">         self.assertEqual(['alice', 'bob', 'carol'], users)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -171,8 +174,9 @@
</span><span class="cx"> 
</span><span class="cx">         params = PopulationParameters()
</span><span class="cx">         params.addClient(1, ClientType(
</span><del>-                BrokenClient, {'runResult': clientRunResult},
-                [ProfileType(BrokenProfile, {'runResult': profileRunResult})]))
</del><ins>+            BrokenClient, {'runResult': clientRunResult},
+            [ProfileType(BrokenProfile, {'runResult': profileRunResult})])
+        )
</ins><span class="cx">         sim = CalendarClientSimulator(
</span><span class="cx">             [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/', None, None)
</span><span class="cx">         sim.add(1, 1)
</span><span class="lines">@@ -284,8 +288,9 @@
</span><span class="cx">         config[&quot;accounts&quot;] = {
</span><span class="cx">             &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
</span><span class="cx">             &quot;params&quot;: {
</span><del>-                &quot;path&quot;: accounts.path},
-            }
</del><ins>+                &quot;path&quot;: accounts.path
+            },
+        }
</ins><span class="cx">         configpath = FilePath(self.mktemp())
</span><span class="cx">         configpath.setContent(writePlistToString(config))
</span><span class="cx">         io = StringIO()
</span><span class="lines">@@ -312,8 +317,9 @@
</span><span class="cx">         config[&quot;accounts&quot;] = {
</span><span class="cx">             &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
</span><span class="cx">             &quot;params&quot;: {
</span><del>-                &quot;path&quot;: &quot;&quot;},
-            }
</del><ins>+                &quot;path&quot;: &quot;&quot;
+            },
+        }
</ins><span class="cx">         configpath = FilePath(self.mktemp())
</span><span class="cx">         configpath.setContent(writePlistToString(config))
</span><span class="cx">         sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
</span><span class="lines">@@ -406,8 +412,9 @@
</span><span class="cx">         section of the configuration file specified.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         config = FilePath(self.mktemp())
</span><del>-        config.setContent(writePlistToString({
-                    &quot;server&quot;: &quot;https://127.0.0.3:8432/&quot;}))
</del><ins>+        config.setContent(
+            writePlistToString({&quot;server&quot;: &quot;https://127.0.0.3:8432/&quot;})
+        )
</ins><span class="cx">         sim = LoadSimulator.fromCommandLine(['--config', config.path])
</span><span class="cx">         self.assertEquals(sim.server, &quot;https://127.0.0.3:8432/&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -418,16 +425,18 @@
</span><span class="cx">         [arrival] section of the configuration file specified.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         config = FilePath(self.mktemp())
</span><del>-        config.setContent(writePlistToString({
-                    &quot;arrival&quot;: {
-                        &quot;factory&quot;: &quot;contrib.performance.loadtest.population.SmoothRampUp&quot;,
-                        &quot;params&quot;: {
-                            &quot;groups&quot;: 10,
-                            &quot;groupSize&quot;: 1,
-                            &quot;interval&quot;: 3,
-                            },
-                        },
-                    }))
</del><ins>+        config.setContent(
+            writePlistToString({
+                &quot;arrival&quot;: {
+                    &quot;factory&quot;: &quot;contrib.performance.loadtest.population.SmoothRampUp&quot;,
+                    &quot;params&quot;: {
+                        &quot;groups&quot;: 10,
+                        &quot;groupSize&quot;: 1,
+                        &quot;interval&quot;: 3,
+                    },
+                },
+            })
+        )
</ins><span class="cx">         sim = LoadSimulator.fromCommandLine(['--config', config.path])
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             sim.arrival,
</span><span class="lines">@@ -461,11 +470,17 @@
</span><span class="cx">         section of the configuration file specified.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         config = FilePath(self.mktemp())
</span><del>-        config.setContent(writePlistToString({
-                    &quot;clients&quot;: [{
</del><ins>+        config.setContent(
+            writePlistToString(
+                {
+                    &quot;clients&quot;: [
+                        {
</ins><span class="cx">                             &quot;software&quot;: &quot;contrib.performance.loadtest.ical.OS_X_10_6&quot;,
</span><del>-                            &quot;params&quot;: {&quot;foo&quot;: &quot;bar&quot;},
-                            &quot;profiles&quot;: [{
</del><ins>+                            &quot;params&quot;: {
+                                &quot;foo&quot;: &quot;bar&quot;
+                            },
+                            &quot;profiles&quot;: [
+                                {
</ins><span class="cx">                                     &quot;params&quot;: {
</span><span class="cx">                                         &quot;interval&quot;: 25,
</span><span class="cx">                                         &quot;eventStartDistribution&quot;: {
</span><span class="lines">@@ -473,19 +488,38 @@
</span><span class="cx">                                             &quot;params&quot;: {
</span><span class="cx">                                                 &quot;mu&quot;: 123,
</span><span class="cx">                                                 &quot;sigma&quot;: 456,
</span><del>-                                                }}},
-                                    &quot;class&quot;: &quot;contrib.performance.loadtest.profiles.Eventer&quot;}],
</del><ins>+                                            }
+                                        }
+                                    },
+                                    &quot;class&quot;: &quot;contrib.performance.loadtest.profiles.Eventer&quot;
+                                }
+                            ],
</ins><span class="cx">                             &quot;weight&quot;: 3,
</span><del>-                            }]}))
</del><ins>+                        }
+                    ]
+                }
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">         sim = LoadSimulator.fromCommandLine(
</span><span class="cx">             ['--config', config.path, '--clients', config.path]
</span><span class="cx">         )
</span><span class="cx">         expectedParameters = PopulationParameters()
</span><span class="cx">         expectedParameters.addClient(
</span><del>-            3, ClientType(OS_X_10_6, {&quot;foo&quot;: &quot;bar&quot;}, [ProfileType(Eventer, {
</del><ins>+            3,
+            ClientType(
+                OS_X_10_6,
+                {&quot;foo&quot;: &quot;bar&quot;},
+                [
+                    ProfileType(
+                        Eventer, {
</ins><span class="cx">                             &quot;interval&quot;: 25,
</span><del>-                            &quot;eventStartDistribution&quot;: NormalDistribution(123, 456)})]))
</del><ins>+                            &quot;eventStartDistribution&quot;: NormalDistribution(123, 456)
+                        }
+                    )
+                ]
+            )
+        )
</ins><span class="cx">         self.assertEquals(sim.parameters, expectedParameters)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -512,9 +546,18 @@
</span><span class="cx">         configuration file are added to the logging system.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         config = FilePath(self.mktemp())
</span><del>-        config.setContent(writePlistToString({
-            &quot;observers&quot;: [{&quot;type&quot;:&quot;contrib.performance.loadtest.population.SimpleStatistics&quot;, &quot;params&quot;:{}, }, ]
-        }))
</del><ins>+        config.setContent(
+            writePlistToString(
+                {
+                    &quot;observers&quot;: [
+                        {
+                            &quot;type&quot;: &quot;contrib.performance.loadtest.population.SimpleStatistics&quot;,
+                            &quot;params&quot;: {},
+                        },
+                    ]
+                }
+            )
+        )
</ins><span class="cx">         sim = LoadSimulator.fromCommandLine(['--config', config.path])
</span><span class="cx">         self.assertEquals(len(sim.observers), 1)
</span><span class="cx">         self.assertIsInstance(sim.observers[0], SimpleStatistics)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/addressbook.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/addressbook.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/addressbook.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -34,7 +34,6 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.idirectory import IDirectoryService
</del><span class="cx"> 
</span><span class="cx"> from twistedcaldav.directory.common import CommonUIDProvisioningResource,\
</span><span class="cx">     uidsResourceName, CommonHomeTypeProvisioningResource
</span><span class="lines">@@ -58,7 +57,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryAddressBookProvisioningResource (
</del><ins>+class DirectoryAddressBookProvisioningResource(
</ins><span class="cx">     ReadOnlyResourceMixIn,
</span><span class="cx">     CalDAVComplianceMixIn,
</span><span class="cx">     DAVResourceWithChildrenMixin,
</span><span class="lines">@@ -77,9 +76,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryAddressBookHomeProvisioningResource (
-        DirectoryAddressBookProvisioningResource
-    ):
</del><ins>+class DirectoryAddressBookHomeProvisioningResource(
+    DirectoryAddressBookProvisioningResource
+):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Resource which provisions address book home collections as needed.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -104,8 +103,14 @@
</span><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        for recordType in [r.name for r in self.directory.recordTypes()]:
-            self.putChild(recordType, DirectoryAddressBookHomeTypeProvisioningResource(self, recordType))
</del><ins>+        # ...just &quot;users&quot; though.  If we iterate all of the directory's
+        # recordTypes, we also get the proxy sub principal types.
+        for recordTypeName in [
+            self.directory.recordTypeToOldName(r) for r in [
+                self.directory.recordType.user
+            ]
+        ]:
+            self.putChild(recordTypeName, DirectoryAddressBookHomeTypeProvisioningResource(self, r))
</ins><span class="cx"> 
</span><span class="cx">         self.putChild(uidsResourceName, DirectoryAddressBookHomeUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="lines">@@ -115,7 +120,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><del>-        return [r.name for r in self.directory.recordTypes()]
</del><ins>+        return [self.directory.recordTypeToOldName(r) for r in self.directory.recordTypes()]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalCollections(self):
</span><span class="lines">@@ -153,9 +158,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class DirectoryAddressBookHomeTypeProvisioningResource (
</span><del>-        CommonHomeTypeProvisioningResource,
-        DirectoryAddressBookProvisioningResource
-    ):
</del><ins>+    CommonHomeTypeProvisioningResource,
+    DirectoryAddressBookProvisioningResource
+):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Resource which provisions address book home collections of a specific
</span><span class="cx">     record type as needed.
</span><span class="lines">@@ -176,19 +181,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def url(self):
</span><del>-        return joinURL(self._parent.url(), self.recordType)
</del><ins>+        return joinURL(self._parent.url(), self.directory.recordTypeToOldName(self.recordType))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def listChildren(self):
</span><span class="cx">         if config.EnablePrincipalListings:
</span><ins>+            children = []
+            for record in (yield self.directory.listRecords(self.recordType)):
+                if record.enabledForAddressBooks:
+                    for shortName in record.shortNames:
+                        children.append(shortName)
</ins><span class="cx"> 
</span><del>-            def _recordShortnameExpand():
-                for record in self.directory.listRecords(self.recordType):
-                    if record.enabledForAddressBooks:
-                        for shortName in record.shortNames:
-                            yield shortName
-
-            return _recordShortnameExpand()
</del><ins>+            returnValue(children)
</ins><span class="cx">         else:
</span><span class="cx">             # Not a listable collection
</span><span class="cx">             raise HTTPError(responsecode.FORBIDDEN)
</span><span class="lines">@@ -224,9 +229,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class DirectoryAddressBookHomeUIDProvisioningResource (
</span><del>-        CommonUIDProvisioningResource,
-        DirectoryAddressBookProvisioningResource
-    ):
</del><ins>+    CommonUIDProvisioningResource,
+    DirectoryAddressBookProvisioningResource
+):
</ins><span class="cx"> 
</span><span class="cx">     homeResourceTypeName = 'addressbooks'
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryaggregatepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/aggregate.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/aggregate.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/aggregate.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,385 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Directory service implementation which aggregates multiple directory
-services.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;AggregateDirectoryService&quot;,
-    &quot;DuplicateRecordTypeError&quot;,
-]
-
-import itertools
-from twisted.cred.error import UnauthorizedLogin
-
-from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.directory import DirectoryService, DirectoryError
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-class AggregateDirectoryService(DirectoryService):
-    &quot;&quot;&quot;
-    L{IDirectoryService} implementation which aggregates multiple directory
-    services.
-
-    @ivar _recordTypes: A map of record types to L{IDirectoryService}s.
-    @type _recordTypes: L{dict} mapping L{bytes} to L{IDirectoryService}
-        provider.
-    &quot;&quot;&quot;
-    baseGUID = &quot;06FB225F-39E7-4D34-B1D1-29925F5E619B&quot;
-
-    def __init__(self, services, groupMembershipCache):
-        super(AggregateDirectoryService, self).__init__()
-
-        realmName = None
-        recordTypes = {}
-        self.groupMembershipCache = groupMembershipCache
-
-        for service in services:
-            service = IDirectoryService(service)
-
-            if service.realmName != realmName:
-                assert realmName is None, (
-                    &quot;Aggregated directory services must have the same realm name: %r != %r\nServices: %r&quot;
-                    % (service.realmName, realmName, services)
-                )
-                realmName = service.realmName
-
-            if not hasattr(service, &quot;recordTypePrefix&quot;):
-                service.recordTypePrefix = &quot;&quot;
-            prefix = service.recordTypePrefix
-
-            for recordType in (prefix + r for r in service.recordTypes()):
-                if recordType in recordTypes:
-                    raise DuplicateRecordTypeError(
-                        &quot;%r is in multiple services: %s, %s&quot;
-                        % (recordType, recordTypes[recordType], service)
-                    )
-                recordTypes[recordType] = service
-
-            service.aggregateService = self
-
-        self.realmName = realmName
-        self._recordTypes = recordTypes
-
-        # FIXME: This is a temporary workaround until new data store is in
-        # place.  During the purging of deprovisioned users' data, we need
-        # to be able to look up records by uid and shortName.  The purge
-        # tool sticks temporary fake records in here.
-        self._tmpRecords = {
-            &quot;uids&quot; : { },
-            &quot;shortNames&quot; : { },
-        }
-
-
-    def __repr__(self):
-        return &quot;&lt;%s (%s): %r&gt;&quot; % (self.__class__.__name__, self.realmName, self._recordTypes)
-
-
-    #
-    # Define calendarHomesCollection as a property so we can set it on contained services
-    #
-    def _getCalendarHomesCollection(self):
-        return self._calendarHomesCollection
-
-
-    def _setCalendarHomesCollection(self, value):
-        for service in self._recordTypes.values():
-            service.calendarHomesCollection = value
-        self._calendarHomesCollection = value
-
-    calendarHomesCollection = property(_getCalendarHomesCollection, _setCalendarHomesCollection)
-
-    #
-    # Define addressBookHomesCollection as a property so we can set it on contained services
-    #
-    def _getAddressBookHomesCollection(self):
-        return self._addressBookHomesCollection
-
-
-    def _setAddressBookHomesCollection(self, value):
-        for service in self._recordTypes.values():
-            service.addressBookHomesCollection = value
-        self._addressBookHomesCollection = value
-
-    addressBookHomesCollection = property(_getAddressBookHomesCollection, _setAddressBookHomesCollection)
-
-
-    def addService(self, service):
-        &quot;&quot;&quot;
-        Add another service to this aggregate.
-
-        @param service: the service to add
-        @type service: L{IDirectoryService}
-        &quot;&quot;&quot;
-        service = IDirectoryService(service)
-
-        if service.realmName != self.realmName:
-            assert self.realmName is None, (
-                &quot;Aggregated directory services must have the same realm name: %r != %r\nServices: %r&quot;
-                % (service.realmName, self.realmName, service)
-            )
-
-        if not hasattr(service, &quot;recordTypePrefix&quot;):
-            service.recordTypePrefix = &quot;&quot;
-        prefix = service.recordTypePrefix
-
-        for recordType in (prefix + r for r in service.recordTypes()):
-            if recordType in self._recordTypes:
-                raise DuplicateRecordTypeError(
-                    &quot;%r is in multiple services: %s, %s&quot;
-                    % (recordType, self.recordTypes[recordType], service)
-                )
-            self._recordTypes[recordType] = service
-
-        service.aggregateService = self
-
-
-    def recordTypes(self):
-        return set(self._recordTypes)
-
-
-    def listRecords(self, recordType):
-        records = self._query(&quot;listRecords&quot;, recordType)
-        if records is None:
-            return ()
-        else:
-            return records
-
-
-    def recordWithShortName(self, recordType, shortName):
-
-        # FIXME: These temporary records shouldn't be needed when we move
-        # to the new data store API.  They're currently needed when purging
-        # deprovisioned users' data.
-        record = self._tmpRecords[&quot;shortNames&quot;].get(shortName, None)
-        if record:
-            return record
-
-        return self._query(&quot;recordWithShortName&quot;, recordType, shortName)
-
-
-    def recordWithUID(self, uid):
-
-        # FIXME: These temporary records shouldn't be needed when we move
-        # to the new data store API.  They're currently needed when purging
-        # deprovisioned users' data.
-        record = self._tmpRecords[&quot;uids&quot;].get(uid, None)
-        if record:
-            return record
-
-        return self._queryAll(&quot;recordWithUID&quot;, uid)
-
-    recordWithGUID = recordWithUID
-
-    def recordWithAuthID(self, authID):
-        return self._queryAll(&quot;recordWithAuthID&quot;, authID)
-
-
-    def recordWithCalendarUserAddress(self, address):
-        return self._queryAll(&quot;recordWithCalendarUserAddress&quot;, address)
-
-
-    def recordWithCachedGroupsAlias(self, recordType, alias):
-        &quot;&quot;&quot;
-        @param recordType: the type of the record to look up.
-        @param alias: the cached-groups alias of the record to look up.
-        @type alias: C{str}
-
-        @return: a deferred L{IDirectoryRecord} with the given cached-groups
-            alias, or C{None} if no such record is found.
-        &quot;&quot;&quot;
-        service = self.serviceForRecordType(recordType)
-        return service.recordWithCachedGroupsAlias(recordType, alias)
-
-
-    @inlineCallbacks
-    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None):
-
-        if recordType:
-            services = (self.serviceForRecordType(recordType),)
-        else:
-            services = set(self._recordTypes.values())
-
-        generators = []
-        for service in services:
-            generator = (yield service.recordsMatchingFields(fields,
-                operand=operand, recordType=recordType))
-            generators.append(generator)
-
-        returnValue(itertools.chain(*generators))
-
-
-    @inlineCallbacks
-    def recordsMatchingTokens(self, tokens, context=None):
-        &quot;&quot;&quot;
-        Combine the results from the sub-services.
-
-        Each token is searched for within each record's full name and email
-        address; if each token is found within a record that record is returned
-        in the results.
-
-        If context is None, all record types are considered.  If context is
-        &quot;location&quot;, only locations are considered.  If context is &quot;attendee&quot;,
-        only users, groups, and resources are considered.
-
-        @param tokens: The tokens to search on
-        @type tokens: C{list} of C{str} (utf-8 bytes)
-
-        @param context: An indication of what the end user is searching for;
-            &quot;attendee&quot;, &quot;location&quot;, or None
-        @type context: C{str}
-
-        @return: a deferred sequence of L{IDirectoryRecord}s which match the
-            given tokens and optional context.
-        &quot;&quot;&quot;
-
-        services = set(self._recordTypes.values())
-
-        generators = []
-        for service in services:
-            generator = (yield service.recordsMatchingTokens(tokens,
-                context=context))
-            generators.append(generator)
-
-        returnValue(itertools.chain(*generators))
-
-
-    def getGroups(self, guids):
-        &quot;&quot;&quot;
-        Returns a set of group records for the list of guids passed in.  For
-        any group that also contains subgroups, those subgroups' records are
-        also returned, and so on.
-        &quot;&quot;&quot;
-        recordType = self.recordType_groups
-        service = self.serviceForRecordType(recordType)
-        return service.getGroups(guids)
-
-
-    def serviceForRecordType(self, recordType):
-        try:
-            return self._recordTypes[recordType]
-        except KeyError:
-            raise UnknownRecordTypeError(recordType)
-
-
-    def _query(self, query, recordType, *args):
-        try:
-            service = self.serviceForRecordType(recordType)
-        except UnknownRecordTypeError:
-            return None
-
-        return getattr(service, query)(
-            recordType[len(service.recordTypePrefix):],
-            *[a[len(service.recordTypePrefix):] for a in args]
-        )
-
-
-    def _queryAll(self, query, *args):
-        for service in self._recordTypes.values():
-            try:
-                record = getattr(service, query)(*args)
-            except UnknownRecordTypeError:
-                record = None
-            if record is not None:
-                return record
-        else:
-            return None
-
-
-    def flushCaches(self):
-        for service in self._recordTypes.values():
-            if hasattr(service, &quot;_initCaches&quot;):
-                service._initCaches()
-
-    userRecordTypes = [DirectoryService.recordType_users]
-
-    def requestAvatarId(self, credentials):
-
-        if credentials.authnPrincipal:
-            return credentials.authnPrincipal.record.service.requestAvatarId(credentials)
-
-        raise UnauthorizedLogin(&quot;No such user: %s&quot; % (credentials.credentials.username,))
-
-
-    def getResourceInfo(self):
-        results = []
-        for service in self._recordTypes.values():
-            for result in service.getResourceInfo():
-                if result:
-                    results.append(result)
-        return results
-
-
-    def getExternalProxyAssignments(self):
-        service = self.serviceForRecordType(self.recordType_locations)
-        return service.getExternalProxyAssignments()
-
-
-    def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
-        fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwargs):
-        service = self.serviceForRecordType(recordType)
-        return service.createRecord(recordType, guid=guid,
-            shortNames=shortNames, authIDs=authIDs, fullName=fullName,
-            firstName=firstName, lastName=lastName,
-            emailAddresses=emailAddresses, uid=uid, password=password, **kwargs)
-
-
-    def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
-        fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwargs):
-        service = self.serviceForRecordType(recordType)
-        return service.updateRecord(recordType, guid=guid,
-            shortNames=shortNames,
-            authIDs=authIDs, fullName=fullName, firstName=firstName,
-            lastName=lastName, emailAddresses=emailAddresses, uid=uid,
-            password=password, **kwargs)
-
-
-    def destroyRecord(self, recordType, guid=None):
-        service = self.serviceForRecordType(recordType)
-        return service.destroyRecord(recordType, guid=guid)
-
-
-    def setRealm(self, realmName):
-        &quot;&quot;&quot;
-        Set a new realm name for this and nested services
-        &quot;&quot;&quot;
-        self.realmName = realmName
-        for service in self._recordTypes.values():
-            service.setRealm(realmName)
-
-
-    def setPrincipalCollection(self, principalCollection):
-        &quot;&quot;&quot;
-        Set the principal service that the directory relies on for doing proxy tests.
-
-        @param principalService: the principal service.
-        @type principalService: L{DirectoryProvisioningResource}
-        &quot;&quot;&quot;
-        self.principalCollection = principalCollection
-        for service in self._recordTypes.values():
-            service.setPrincipalCollection(principalCollection)
-
-
-
-class DuplicateRecordTypeError(DirectoryError):
-    &quot;&quot;&quot;
-    Duplicate record type.
-    &quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryappleopendirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/appleopendirectory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/appleopendirectory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/appleopendirectory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,1584 +0,0 @@
</span><del>-# -*- test-case-name: twistedcaldav.directory.test.test_opendirectory -*-
-##
-# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Apple OpenDirectory directory service implementation.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;OpenDirectoryService&quot;,
-    &quot;OpenDirectoryInitError&quot;,
-]
-
-import sys
-import time
-from uuid import UUID
-
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.cred.credentials import UsernamePassword
-from txweb2.auth.digest import DigestedCredentials
-from twext.python.log import Logger
-
-from twistedcaldav.directory.cachingdirectory import CachingDirectoryService, \
-    CachingDirectoryRecord
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
-from twistedcaldav.directory.util import splitIntoBatches
-from twistedcaldav.directory.principal import cuAddressConverter
-
-from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
-
-
-
-class OpenDirectoryService(CachingDirectoryService):
-    &quot;&quot;&quot;
-    OpenDirectory implementation of L{IDirectoryService}.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    baseGUID = &quot;891F8321-ED02-424C-BA72-89C32F215C1E&quot;
-
-    def __repr__(self):
-        return &quot;&lt;%s %r: %r&gt;&quot; % (self.__class__.__name__, self.realmName, self.node)
-
-
-    def __init__(self, params, odModule=None):
-        &quot;&quot;&quot;
-        @param params: a dictionary containing the following keys:
-
-            - node: an OpenDirectory node name to bind to.
-
-            - restrictEnabledRecords: C{True} if a group in the directory is to
-              be used to determine which calendar users are enabled.
-
-            - restrictToGroup: C{str} guid or name of group used to restrict
-              enabled users.
-
-            - cacheTimeout: C{int} number of minutes before cache is
-              invalidated.
-
-            - negativeCache: C{False} cache the fact that a record wasn't found
-        &quot;&quot;&quot;
-        defaults = {
-            'node' : '/Search',
-            'restrictEnabledRecords' : False,
-            'restrictToGroup' : '',
-            'cacheTimeout' : 1, # Minutes
-            'batchSize' : 100, # for splitting up large queries
-            'negativeCaching' : False,
-            'recordTypes' : (
-                self.recordType_users,
-                self.recordType_groups,
-            ),
-            'augmentService' : None,
-            'groupMembershipCache' : None,
-        }
-        ignored = ('requireComputerRecord',)
-        params = self.getParams(params, defaults, ignored)
-
-        self._recordTypes = params['recordTypes']
-
-        super(OpenDirectoryService, self).__init__(params['cacheTimeout'],
-                                                   params['negativeCaching'])
-
-        if odModule is None:
-            odModule = opendirectory
-        self.odModule = odModule
-
-        try:
-            directory = self.odModule.odInit(params['node'])
-        except self.odModule.ODError, e:
-            self.log.error(&quot;OpenDirectory (node=%s) Initialization error: %s&quot; % (params['node'], e))
-            raise
-
-        self.augmentService = params['augmentService']
-        self.groupMembershipCache = params['groupMembershipCache']
-        self.realmName = params['node']
-        self.directory = directory
-        self.node = params['node']
-        self.restrictEnabledRecords = params['restrictEnabledRecords']
-        self.restrictToGroup = params['restrictToGroup']
-        self.batchSize = params['batchSize']
-        try:
-            UUID(self.restrictToGroup)
-        except:
-            self.restrictToGUID = False
-        else:
-            self.restrictToGUID = True
-        self.restrictedTimestamp = 0
-
-        # Set up the /Local/Default node if it's in the search path so we can
-        # send custom queries to it
-        self.localNode = None
-        try:
-            if self.node == &quot;/Search&quot;:
-                result = self.odModule.getNodeAttributes(self.directory, &quot;/Search&quot;,
-                    (dsattributes.kDS1AttrSearchPath,))
-                if &quot;/Local/Default&quot; in result[dsattributes.kDS1AttrSearchPath]:
-                    try:
-                        self.localNode = self.odModule.odInit(&quot;/Local/Default&quot;)
-                    except self.odModule.ODError, e:
-                        self.log.error(&quot;Failed to open /Local/Default): %s&quot; % (e,))
-        except AttributeError:
-            pass
-
-
-    @property
-    def restrictedGUIDs(self):
-        &quot;&quot;&quot;
-        Look up (and cache) the set of guids that are members of the
-        restrictToGroup.  If restrictToGroup is not set, return None to
-        indicate there are no group restrictions.
-        &quot;&quot;&quot;
-        if self.restrictEnabledRecords:
-            if time.time() - self.restrictedTimestamp &gt; self.cacheTimeout:
-                attributeToMatch = dsattributes.kDS1AttrGeneratedUID if self.restrictToGUID else dsattributes.kDSNAttrRecordName
-                valueToMatch = self.restrictToGroup
-                self.log.debug(&quot;Doing restricted group membership check&quot;)
-                self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                    self.directory,
-                    attributeToMatch,
-                    valueToMatch,
-                    dsattributes.eDSExact,
-                    False,
-                    dsattributes.kDSStdRecordTypeGroups,
-                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
-                ))
-                results = self.odModule.queryRecordsWithAttribute_list(
-                    self.directory,
-                    attributeToMatch,
-                    valueToMatch,
-                    dsattributes.eDSExact,
-                    False,
-                    dsattributes.kDSStdRecordTypeGroups,
-                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
-                )
-
-                if len(results) == 1:
-                    members = results[0][1].get(dsattributes.kDSNAttrGroupMembers, [])
-                    nestedGroups = results[0][1].get(dsattributes.kDSNAttrNestedGroups, [])
-                else:
-                    members = []
-                    nestedGroups = []
-                self._cachedRestrictedGUIDs = set(self._expandGroupMembership(members, nestedGroups, returnGroups=True))
-                self.log.debug(&quot;Got %d restricted group members&quot; % (len(self._cachedRestrictedGUIDs),))
-                self.restrictedTimestamp = time.time()
-            return self._cachedRestrictedGUIDs
-        else:
-            # No restrictions
-            return None
-
-
-    def __cmp__(self, other):
-        if not isinstance(other, DirectoryRecord):
-            return super(DirectoryRecord, self).__eq__(other)
-
-        for attr in (&quot;directory&quot;, &quot;node&quot;):
-            diff = cmp(getattr(self, attr), getattr(other, attr))
-            if diff != 0:
-                return diff
-        return 0
-
-
-    def __hash__(self):
-        h = hash(self.__class__.__name__)
-        for attr in (&quot;node&quot;,):
-            h = (h + hash(getattr(self, attr))) &amp; sys.maxint
-        return h
-
-
-    def _expandGroupMembership(self, members, nestedGroups, processedGUIDs=None, returnGroups=False):
-
-        if processedGUIDs is None:
-            processedGUIDs = set()
-
-        if isinstance(members, str):
-            members = [members]
-
-        if isinstance(nestedGroups, str):
-            nestedGroups = [nestedGroups]
-
-        for memberGUID in members:
-            if memberGUID not in processedGUIDs:
-                processedGUIDs.add(memberGUID)
-                yield memberGUID
-
-        for groupGUID in nestedGroups:
-            if groupGUID in processedGUIDs:
-                continue
-
-            self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                self.directory,
-                dsattributes.kDS1AttrGeneratedUID,
-                groupGUID,
-                dsattributes.eDSExact,
-                False,
-                dsattributes.kDSStdRecordTypeGroups,
-                [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups]
-            ))
-            result = self.odModule.queryRecordsWithAttribute_list(
-                self.directory,
-                dsattributes.kDS1AttrGeneratedUID,
-                groupGUID,
-                dsattributes.eDSExact,
-                False,
-                dsattributes.kDSStdRecordTypeGroups,
-                [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups]
-            )
-
-            if not result:
-                self.log.error(&quot;Couldn't find group %s when trying to expand nested groups.&quot;
-                             % (groupGUID,))
-                continue
-
-            group = result[0][1]
-
-            processedGUIDs.add(groupGUID)
-            if returnGroups:
-                yield groupGUID
-
-            for GUID in self._expandGroupMembership(
-                group.get(dsattributes.kDSNAttrGroupMembers, []),
-                group.get(dsattributes.kDSNAttrNestedGroups, []),
-                processedGUIDs,
-                returnGroups,
-            ):
-                yield GUID
-
-
-    def recordTypes(self):
-        return self._recordTypes
-
-
-    def listRecords(self, recordType):
-        &quot;&quot;&quot;
-        Retrieve all the records of recordType from the directory, but for
-        expediency don't index them or cache them locally, nor in memcached.
-        &quot;&quot;&quot;
-
-        records = []
-
-        attrs = [
-            dsattributes.kDS1AttrGeneratedUID,
-            dsattributes.kDSNAttrRecordName,
-            dsattributes.kDS1AttrDistinguishedName,
-        ]
-
-        if recordType == DirectoryService.recordType_users:
-            ODRecordType = self._toODRecordTypes[recordType]
-
-        elif recordType in (
-            DirectoryService.recordType_resources,
-            DirectoryService.recordType_locations,
-        ):
-            attrs.append(dsattributes.kDSNAttrResourceInfo)
-            ODRecordType = self._toODRecordTypes[recordType]
-
-        elif recordType == DirectoryService.recordType_groups:
-            attrs.append(dsattributes.kDSNAttrGroupMembers)
-            attrs.append(dsattributes.kDSNAttrNestedGroups)
-            ODRecordType = dsattributes.kDSStdRecordTypeGroups
-
-        self.log.debug(&quot;Querying OD for all %s records&quot; % (recordType,))
-        results = self.odModule.listAllRecordsWithAttributes_list(
-            self.directory, ODRecordType, attrs)
-        self.log.debug(&quot;Retrieved %d %s records&quot; % (len(results), recordType,))
-
-        for key, value in results:
-            recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-            if not recordGUID:
-                self.log.warn(&quot;Ignoring record missing GUID: %s %s&quot; %
-                    (key, value,))
-                continue
-
-            # Skip if group restriction is in place and guid is not
-            # a member (but don't skip any groups)
-            if (recordType != self.recordType_groups and
-                self.restrictedGUIDs is not None):
-                if str(recordGUID) not in self.restrictedGUIDs:
-                    continue
-
-            recordShortNames = self._uniqueTupleFromAttribute(
-                value.get(dsattributes.kDSNAttrRecordName))
-            recordFullName = value.get(
-                dsattributes.kDS1AttrDistinguishedName)
-
-            proxyGUIDs = ()
-            readOnlyProxyGUIDs = ()
-
-            if recordType in (
-                DirectoryService.recordType_resources,
-                DirectoryService.recordType_locations,
-            ):
-                resourceInfo = value.get(dsattributes.kDSNAttrResourceInfo)
-                if resourceInfo is not None:
-                    if type(resourceInfo) is not str:
-                        resourceInfo = resourceInfo[0]
-                    try:
-                        (
-                            _ignore_autoSchedule,
-                            proxy,
-                            readOnlyProxy
-                        ) = self.parseResourceInfo(
-                            resourceInfo,
-                            recordGUID,
-                            recordType,
-                            recordShortNames[0]
-                        )
-                    except ValueError:
-                        continue
-                    if proxy:
-                        proxyGUIDs = (proxy,)
-                    if readOnlyProxy:
-                        readOnlyProxyGUIDs = (readOnlyProxy,)
-
-            # Special case for groups, which have members.
-            if recordType == self.recordType_groups:
-                memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
-                if memberGUIDs is None:
-                    memberGUIDs = ()
-                elif type(memberGUIDs) is str:
-                    memberGUIDs = (memberGUIDs,)
-                nestedGUIDs = value.get(dsattributes.kDSNAttrNestedGroups)
-                if nestedGUIDs:
-                    if type(nestedGUIDs) is str:
-                        nestedGUIDs = (nestedGUIDs,)
-                    memberGUIDs += tuple(nestedGUIDs)
-                else:
-                    nestedGUIDs = ()
-            else:
-                memberGUIDs = ()
-                nestedGUIDs = ()
-
-            record = OpenDirectoryRecord(
-                service=self,
-                recordType=recordType,
-                guid=recordGUID,
-                nodeName=&quot;&quot;,
-                shortNames=recordShortNames,
-                authIDs=(),
-                fullName=recordFullName,
-                firstName=&quot;&quot;,
-                lastName=&quot;&quot;,
-                emailAddresses=&quot;&quot;,
-                memberGUIDs=memberGUIDs,
-                nestedGUIDs=nestedGUIDs,
-                extProxies=proxyGUIDs,
-                extReadOnlyProxies=readOnlyProxyGUIDs,
-            )
-
-            # (Copied from below)
-            # Look up augment information
-            # TODO: this needs to be deferred but for now we hard code
-            # the deferred result because we know it is completing
-            # immediately.
-            if self.augmentService is not None:
-                d = self.augmentService.getAugmentRecord(record.guid,
-                    recordType)
-                d.addCallback(lambda x: record.addAugmentInformation(x))
-            records.append(record)
-
-        self.log.debug(&quot;ListRecords returning %d %s records&quot; % (len(records),
-            recordType))
-
-        return records
-
-
-    def groupsForGUID(self, guid):
-
-        attrs = [
-            dsattributes.kDS1AttrGeneratedUID,
-        ]
-
-        recordType = dsattributes.kDSStdRecordTypeGroups
-
-        guids = set()
-
-        self.log.debug(&quot;Looking up which groups %s is a member of&quot; % (guid,))
-        try:
-            self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                self.directory,
-                dsattributes.kDSNAttrGroupMembers,
-                guid,
-                dsattributes.eDSExact,
-                False,
-                recordType,
-                attrs,
-            ))
-            results = self.odModule.queryRecordsWithAttribute_list(
-                self.directory,
-                dsattributes.kDSNAttrGroupMembers,
-                guid,
-                dsattributes.eDSExact,
-                False,
-                recordType,
-                attrs,
-            )
-        except self.odModule.ODError, ex:
-            self.log.error(&quot;OpenDirectory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-            raise
-
-        for (_ignore_recordShortName, value) in results:
-
-            # Now get useful record info.
-            recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-            if recordGUID:
-                guids.add(recordGUID)
-
-        try:
-            self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                self.directory,
-                dsattributes.kDSNAttrNestedGroups,
-                guid,
-                dsattributes.eDSExact,
-                False,
-                recordType,
-                attrs,
-            ))
-            results = self.odModule.queryRecordsWithAttribute_list(
-                self.directory,
-                dsattributes.kDSNAttrNestedGroups,
-                guid,
-                dsattributes.eDSExact,
-                False,
-                recordType,
-                attrs,
-            )
-        except self.odModule.ODError, ex:
-            self.log.error(&quot;OpenDirectory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-            raise
-
-        for (_ignore_recordShortName, value) in results:
-
-            # Now get useful record info.
-            recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-            if recordGUID:
-                guids.add(recordGUID)
-
-        self.log.debug(&quot;%s is a member of %d groups&quot; % (guid, len(guids)))
-
-        return guids
-
-    _ODFields = {
-        'fullName' : {
-            'odField' : dsattributes.kDS1AttrDistinguishedName,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-                dsattributes.kDSStdRecordTypeGroups,
-                dsattributes.kDSStdRecordTypeResources,
-                dsattributes.kDSStdRecordTypePlaces,
-            ]),
-        },
-        'firstName' : {
-            'odField' : dsattributes.kDS1AttrFirstName,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-            ]),
-        },
-        'lastName' : {
-            'odField' : dsattributes.kDS1AttrLastName,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-            ]),
-        },
-        'emailAddresses' : {
-            'odField' : dsattributes.kDSNAttrEMailAddress,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-                dsattributes.kDSStdRecordTypeGroups,
-            ]),
-        },
-        'recordName' : {
-            'odField' : dsattributes.kDSNAttrRecordName,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-                dsattributes.kDSStdRecordTypeGroups,
-                dsattributes.kDSStdRecordTypeResources,
-                dsattributes.kDSStdRecordTypePlaces,
-            ]),
-        },
-        'guid' : {
-            'odField' : dsattributes.kDS1AttrGeneratedUID,
-            'appliesTo' : set([
-                dsattributes.kDSStdRecordTypeUsers,
-                dsattributes.kDSStdRecordTypeGroups,
-                dsattributes.kDSStdRecordTypeResources,
-                dsattributes.kDSStdRecordTypePlaces,
-            ]),
-        },
-    }
-
-    _toODRecordTypes = {
-        DirectoryService.recordType_users :
-            dsattributes.kDSStdRecordTypeUsers,
-        DirectoryService.recordType_groups :
-            dsattributes.kDSStdRecordTypeGroups,
-        DirectoryService.recordType_resources :
-            dsattributes.kDSStdRecordTypeResources,
-        DirectoryService.recordType_locations :
-            dsattributes.kDSStdRecordTypePlaces,
-    }
-
-    _fromODRecordTypes = dict([(b, a) for a, b in _toODRecordTypes.iteritems()])
-
-    def _uniqueTupleFromAttribute(self, attribute):
-        if attribute:
-            if isinstance(attribute, str):
-                return (attribute,)
-            else:
-                s = set()
-                return tuple([(s.add(x), x)[1] for x in attribute if x not in s])
-        else:
-            return ()
-
-
-    def _setFromAttribute(self, attribute, lower=False):
-        if attribute:
-            if isinstance(attribute, str):
-                return set((attribute.lower() if lower else attribute,))
-            else:
-                return set([item.lower() if lower else item for item in attribute])
-        else:
-            return ()
-
-
-    def recordsMatchingTokens(self, tokens, context=None, lookupMethod=None):
-        &quot;&quot;&quot;
-        @param tokens: The tokens to search on
-        @type tokens: C{list} of C{str} (utf-8 bytes)
-        @param context: An indication of what the end user is searching
-            for; &quot;attendee&quot;, &quot;location&quot;, or None
-        @type context: C{str}
-        @return: a deferred sequence of L{IDirectoryRecord}s which
-            match the given tokens and optional context.
-
-        Each token is searched for within each record's full name and
-        email address; if each token is found within a record that
-        record is returned in the results.
-
-        If context is None, all record types are considered.  If
-        context is &quot;location&quot;, only locations are considered.  If
-        context is &quot;attendee&quot;, only users, groups, and resources
-        are considered.
-        &quot;&quot;&quot;
-
-        if lookupMethod is None:
-            lookupMethod = self.odModule.queryRecordsWithAttributes_list
-
-        def collectResults(results):
-            self.log.debug(&quot;Got back %d records from OD&quot; % (len(results),))
-            for _ignore_key, value in results:
-                # self.log.debug(&quot;OD result: {key} {value}&quot;, key=key, value=value)
-                try:
-                    recordNodeName = value.get(
-                        dsattributes.kDSNAttrMetaNodeLocation)
-                    recordShortNames = self._uniqueTupleFromAttribute(
-                        value.get(dsattributes.kDSNAttrRecordName))
-
-                    recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-
-                    recordType = value.get(dsattributes.kDSNAttrRecordType)
-                    if isinstance(recordType, list):
-                        recordType = recordType[0]
-                    if not recordType:
-                        continue
-                    recordType = self._fromODRecordTypes[recordType]
-
-                    # Skip if group restriction is in place and guid is not
-                    # a member (but don't skip any groups)
-                    if (recordType != self.recordType_groups and
-                        self.restrictedGUIDs is not None):
-                        if str(recordGUID) not in self.restrictedGUIDs:
-                            continue
-
-                    recordAuthIDs = self._setFromAttribute(
-                        value.get(dsattributes.kDSNAttrAltSecurityIdentities))
-                    recordFullName = value.get(
-                        dsattributes.kDS1AttrDistinguishedName)
-                    recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
-                    recordLastName = value.get(dsattributes.kDS1AttrLastName)
-                    recordEmailAddresses = self._setFromAttribute(
-                        value.get(dsattributes.kDSNAttrEMailAddress),
-                        lower=True)
-
-                    # Special case for groups, which have members.
-                    if recordType == self.recordType_groups:
-                        memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
-                        if memberGUIDs is None:
-                            memberGUIDs = ()
-                        elif type(memberGUIDs) is str:
-                            memberGUIDs = (memberGUIDs,)
-                        nestedGUIDs = value.get(dsattributes.kDSNAttrNestedGroups)
-                        if nestedGUIDs:
-                            if type(nestedGUIDs) is str:
-                                nestedGUIDs = (nestedGUIDs,)
-                            memberGUIDs += tuple(nestedGUIDs)
-                        else:
-                            nestedGUIDs = ()
-                    else:
-                        nestedGUIDs = ()
-                        memberGUIDs = ()
-
-                    # Create records but don't store them in our index or
-                    # send them to memcached, because these are transient,
-                    # existing only so we can create principal resource
-                    # objects that are used to generate the REPORT result.
-
-                    record = OpenDirectoryRecord(
-                        service=self,
-                        recordType=recordType,
-                        guid=recordGUID,
-                        nodeName=recordNodeName,
-                        shortNames=recordShortNames,
-                        authIDs=recordAuthIDs,
-                        fullName=recordFullName,
-                        firstName=recordFirstName,
-                        lastName=recordLastName,
-                        emailAddresses=recordEmailAddresses,
-                        memberGUIDs=memberGUIDs,
-                        nestedGUIDs=nestedGUIDs,
-                        extProxies=(),
-                        extReadOnlyProxies=(),
-                    )
-
-                    # (Copied from below)
-                    # Look up augment information
-                    # TODO: this needs to be deferred but for now we hard code
-                    # the deferred result because we know it is completing
-                    # immediately.
-                    if self.augmentService is not None:
-                        d = self.augmentService.getAugmentRecord(record.guid,
-                            recordType)
-                        d.addCallback(lambda x: record.addAugmentInformation(x))
-
-                    yield record
-
-                except KeyError:
-                    pass
-
-
-        def multiQuery(directory, queries, recordTypes, attrs):
-            byGUID = {}
-            sets = []
-
-            caseInsensitive = True
-            for compound in queries:
-                compound = compound.generate()
-
-                try:
-                    startTime = time.time()
-                    queryResults = lookupMethod(
-                        directory,
-                        compound,
-                        caseInsensitive,
-                        recordTypes,
-                        attrs,
-                    )
-                    totalTime = time.time() - startTime
-
-                    newSet = set()
-                    for recordName, data in queryResults:
-                        guid = data.get(dsattributes.kDS1AttrGeneratedUID, None)
-                        if guid:
-                            byGUID[guid] = (recordName, data)
-                            newSet.add(guid)
-
-                    self.log.debug(&quot;Attendee OD query: Types %s, Query %s, %.2f sec, %d results&quot; %
-                        (recordTypes, compound, totalTime, len(queryResults)))
-                    sets.append(newSet)
-
-                except self.odModule.ODError, e:
-                    self.log.error(&quot;Ignoring OD Error: %d %s&quot; %
-                        (e.message[1], e.message[0]))
-                    continue
-
-            results = []
-            for guid in set.intersection(*sets):
-                recordName, data = byGUID.get(guid, None)
-                if data is not None:
-                    results.append((data[dsattributes.kDSNAttrRecordName], data))
-            return results
-
-        localQueries = buildLocalQueriesFromTokens(tokens, self._ODFields)
-        nestedQuery = buildNestedQueryFromTokens(tokens, self._ODFields)
-
-        # Starting with the record types corresponding to the context...
-        recordTypes = self.recordTypesForSearchContext(context)
-        # ...limit to the types this service supports...
-        recordTypes = [r for r in recordTypes if r in self.recordTypes()]
-        # ...and map those to OD representations...
-        recordTypes = [self._toODRecordTypes[r] for r in recordTypes]
-
-        if recordTypes:
-            # Perform the complex/nested query.  If there was more than one
-            # token, this won't match anything in /Local, therefore we run
-            # the un-nested queries below and AND the results ourselves in
-            # multiQuery.
-            results = multiQuery(
-                self.directory,
-                [nestedQuery],
-                recordTypes,
-                [
-                    dsattributes.kDS1AttrGeneratedUID,
-                    dsattributes.kDSNAttrRecordName,
-                    dsattributes.kDSNAttrAltSecurityIdentities,
-                    dsattributes.kDSNAttrRecordType,
-                    dsattributes.kDS1AttrDistinguishedName,
-                    dsattributes.kDS1AttrFirstName,
-                    dsattributes.kDS1AttrLastName,
-                    dsattributes.kDSNAttrEMailAddress,
-                    dsattributes.kDSNAttrMetaNodeLocation,
-                    dsattributes.kDSNAttrGroupMembers,
-                    dsattributes.kDSNAttrNestedGroups,
-                ]
-            )
-            if self.localNode is not None and len(tokens) &gt; 1:
-                # /Local is in our search path and the complex query above
-                # would not have matched anything in /Local.  So now run
-                # the un-nested queries.
-                results.extend(
-                    multiQuery(
-                        self.localNode,
-                        localQueries,
-                        recordTypes,
-                        [
-                            dsattributes.kDS1AttrGeneratedUID,
-                            dsattributes.kDSNAttrRecordName,
-                            dsattributes.kDSNAttrAltSecurityIdentities,
-                            dsattributes.kDSNAttrRecordType,
-                            dsattributes.kDS1AttrDistinguishedName,
-                            dsattributes.kDS1AttrFirstName,
-                            dsattributes.kDS1AttrLastName,
-                            dsattributes.kDSNAttrEMailAddress,
-                            dsattributes.kDSNAttrMetaNodeLocation,
-                            dsattributes.kDSNAttrGroupMembers,
-                            dsattributes.kDSNAttrNestedGroups,
-                        ]
-                    )
-                )
-            return succeed(collectResults(results))
-        else:
-            return succeed([])
-
-
-    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None,
-        lookupMethod=None):
-
-        if lookupMethod is None:
-            lookupMethod = self.odModule.queryRecordsWithAttribute_list
-
-        # Note that OD applies case-sensitivity globally across the entire
-        # query, not per expression, so the current code uses whatever is
-        # specified in the last field in the fields list
-
-        def collectResults(results):
-            self.log.debug(&quot;Got back %d records from OD&quot; % (len(results),))
-            for _ignore_key, value in results:
-                # self.log.debug(&quot;OD result: {key} {value}&quot;, key=key, value=value)
-                try:
-                    recordNodeName = value.get(
-                        dsattributes.kDSNAttrMetaNodeLocation)
-                    recordShortNames = self._uniqueTupleFromAttribute(
-                        value.get(dsattributes.kDSNAttrRecordName))
-
-                    recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-
-                    recordType = value.get(dsattributes.kDSNAttrRecordType)
-                    if isinstance(recordType, list):
-                        recordType = recordType[0]
-                    if not recordType:
-                        continue
-                    recordType = self._fromODRecordTypes[recordType]
-
-                    # Skip if group restriction is in place and guid is not
-                    # a member (but don't skip any groups)
-                    if (recordType != self.recordType_groups and
-                        self.restrictedGUIDs is not None):
-                        if str(recordGUID) not in self.restrictedGUIDs:
-                            continue
-
-                    recordAuthIDs = self._setFromAttribute(
-                        value.get(dsattributes.kDSNAttrAltSecurityIdentities))
-                    recordFullName = value.get(
-                        dsattributes.kDS1AttrDistinguishedName)
-                    recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
-                    recordLastName = value.get(dsattributes.kDS1AttrLastName)
-                    recordEmailAddresses = self._setFromAttribute(
-                        value.get(dsattributes.kDSNAttrEMailAddress),
-                        lower=True)
-
-                    # Special case for groups, which have members.
-                    if recordType == self.recordType_groups:
-                        memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
-                        if memberGUIDs is None:
-                            memberGUIDs = ()
-                        elif type(memberGUIDs) is str:
-                            memberGUIDs = (memberGUIDs,)
-                        nestedGUIDs = value.get(dsattributes.kDSNAttrNestedGroups)
-                        if nestedGUIDs:
-                            if type(nestedGUIDs) is str:
-                                nestedGUIDs = (nestedGUIDs,)
-                            memberGUIDs += tuple(nestedGUIDs)
-                        else:
-                            nestedGUIDs = ()
-                    else:
-                        nestedGUIDs = ()
-                        memberGUIDs = ()
-
-                    # Create records but don't store them in our index or
-                    # send them to memcached, because these are transient,
-                    # existing only so we can create principal resource
-                    # objects that are used to generate the REPORT result.
-
-                    record = OpenDirectoryRecord(
-                        service=self,
-                        recordType=recordType,
-                        guid=recordGUID,
-                        nodeName=recordNodeName,
-                        shortNames=recordShortNames,
-                        authIDs=recordAuthIDs,
-                        fullName=recordFullName,
-                        firstName=recordFirstName,
-                        lastName=recordLastName,
-                        emailAddresses=recordEmailAddresses,
-                        memberGUIDs=memberGUIDs,
-                        nestedGUIDs=nestedGUIDs,
-                        extProxies=(),
-                        extReadOnlyProxies=(),
-                    )
-
-                    # (Copied from below)
-                    # Look up augment information
-                    # TODO: this needs to be deferred but for now we hard code
-                    # the deferred result because we know it is completing
-                    # immediately.
-                    if self.augmentService is not None:
-                        d = self.augmentService.getAugmentRecord(record.guid,
-                            recordType)
-                        d.addCallback(lambda x: record.addAugmentInformation(x))
-
-                    yield record
-
-                except KeyError:
-                    pass
-
-
-        def multiQuery(directory, queries, attrs, operand):
-            byGUID = {}
-            sets = []
-
-            for query, recordTypes in queries.iteritems():
-                ODField, value, caseless, matchType = query
-                if matchType == &quot;starts-with&quot;:
-                    comparison = dsattributes.eDSStartsWith
-                elif matchType == &quot;contains&quot;:
-                    comparison = dsattributes.eDSContains
-                else:
-                    comparison = dsattributes.eDSExact
-
-                self.log.debug(&quot;Calling OD: Types %s, Field %s, Value %s, Match %s, Caseless %s&quot; %
-                    (recordTypes, ODField, value, matchType, caseless))
-
-                try:
-                    queryResults = lookupMethod(
-                        directory,
-                        ODField,
-                        value,
-                        comparison,
-                        caseless,
-                        recordTypes,
-                        attrs,
-                    )
-
-                    if operand == dsquery.expression.OR:
-                        for recordName, data in queryResults:
-                            guid = data.get(dsattributes.kDS1AttrGeneratedUID, None)
-                            if guid:
-                                byGUID[guid] = (recordName, data)
-                    else: # AND
-                        newSet = set()
-                        for recordName, data in queryResults:
-                            guid = data.get(dsattributes.kDS1AttrGeneratedUID, None)
-                            if guid:
-                                byGUID[guid] = (recordName, data)
-                                newSet.add(guid)
-
-                        sets.append(newSet)
-
-                except self.odModule.ODError, e:
-                    self.log.error(&quot;Ignoring OD Error: %d %s&quot; %
-                        (e.message[1], e.message[0]))
-                    continue
-
-            if operand == dsquery.expression.OR:
-                return byGUID.values()
-
-            else:
-                results = []
-                for guid in set.intersection(*sets):
-                    recordName, data = byGUID.get(guid, None)
-                    if data is not None:
-                        results.append((data[dsattributes.kDSNAttrRecordName], data))
-                return results
-
-        operand = (dsquery.expression.OR if operand == &quot;or&quot;
-            else dsquery.expression.AND)
-
-        if recordType is None:
-            # The client is looking for records in any of the four types
-            recordTypes = set(self._toODRecordTypes.values())
-        else:
-            # The client is after only one recordType
-            recordTypes = [self._toODRecordTypes[recordType]]
-
-        queries = buildQueries(recordTypes, fields, self._ODFields)
-
-        results = multiQuery(
-            self.directory,
-            queries,
-            [
-                dsattributes.kDS1AttrGeneratedUID,
-                dsattributes.kDSNAttrRecordName,
-                dsattributes.kDSNAttrAltSecurityIdentities,
-                dsattributes.kDSNAttrRecordType,
-                dsattributes.kDS1AttrDistinguishedName,
-                dsattributes.kDS1AttrFirstName,
-                dsattributes.kDS1AttrLastName,
-                dsattributes.kDSNAttrEMailAddress,
-                dsattributes.kDSNAttrMetaNodeLocation,
-                dsattributes.kDSNAttrGroupMembers,
-                dsattributes.kDSNAttrNestedGroups,
-            ],
-            operand
-        )
-        return succeed(collectResults(results))
-
-
-    def queryDirectory(self, recordTypes, indexType, indexKey,
-        lookupMethod=None):
-
-        if lookupMethod is None:
-            lookupMethod = self.odModule.queryRecordsWithAttribute_list
-
-        origIndexKey = indexKey
-        if indexType == self.INDEX_TYPE_CUA:
-            # The directory doesn't contain CUAs, so we need to convert
-            # the CUA to the appropriate field name and value:
-            queryattr, indexKey = cuAddressConverter(indexKey)
-            # queryattr will be one of:
-            # guid, emailAddresses, or recordName
-            # ...which will need to be mapped to DS
-            queryattr = self._ODFields[queryattr]['odField']
-
-        else:
-            queryattr = {
-                self.INDEX_TYPE_SHORTNAME : dsattributes.kDSNAttrRecordName,
-                self.INDEX_TYPE_GUID      : dsattributes.kDS1AttrGeneratedUID,
-                self.INDEX_TYPE_AUTHID    : dsattributes.kDSNAttrAltSecurityIdentities,
-            }.get(indexType)
-            assert queryattr is not None, &quot;Invalid type for record faulting query&quot;
-        # Make all OD queries case insensitive
-        caseInsensitive = True
-
-        results = []
-        for recordType in recordTypes:
-
-            attrs = [
-                dsattributes.kDS1AttrGeneratedUID,
-                dsattributes.kDSNAttrRecordName,
-                dsattributes.kDSNAttrAltSecurityIdentities,
-                dsattributes.kDSNAttrRecordType,
-                dsattributes.kDS1AttrDistinguishedName,
-                dsattributes.kDS1AttrFirstName,
-                dsattributes.kDS1AttrLastName,
-                dsattributes.kDSNAttrEMailAddress,
-                dsattributes.kDSNAttrMetaNodeLocation,
-            ]
-
-            if recordType == DirectoryService.recordType_users:
-                listRecordTypes = [self._toODRecordTypes[recordType]]
-
-            elif recordType in (
-                DirectoryService.recordType_resources,
-                DirectoryService.recordType_locations,
-            ):
-                if queryattr == dsattributes.kDSNAttrEMailAddress:
-                    continue
-
-                listRecordTypes = [self._toODRecordTypes[recordType]]
-
-            elif recordType == DirectoryService.recordType_groups:
-
-                if queryattr == dsattributes.kDSNAttrEMailAddress:
-                    continue
-
-                listRecordTypes = [dsattributes.kDSStdRecordTypeGroups]
-                attrs.append(dsattributes.kDSNAttrGroupMembers)
-                attrs.append(dsattributes.kDSNAttrNestedGroups)
-
-            else:
-                raise UnknownRecordTypeError(&quot;Unknown OpenDirectory record type: %s&quot; % (recordType))
-
-            # Because we're getting transient OD error -14987, try 3 times:
-            for _ignore in xrange(3):
-                try:
-                    self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                        self.directory,
-                        queryattr,
-                        indexKey,
-                        dsattributes.eDSExact,
-                        caseInsensitive,
-                        listRecordTypes,
-                        attrs,
-                    ))
-                    lookedUp = lookupMethod(
-                            self.directory,
-                            queryattr,
-                            indexKey,
-                            dsattributes.eDSExact,
-                            caseInsensitive,
-                            listRecordTypes,
-                            attrs,
-                        )
-                    results.extend(lookedUp)
-
-                except self.odModule.ODError, ex:
-                    if ex.message[1] == -14987:
-                        # Fall through and retry
-                        self.log.error(&quot;OpenDirectory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-                    elif ex.message[1] == -14140 or ex.message[1] == -14200:
-                        # Unsupported attribute on record - don't fail
-                        return
-                    else:
-                        self.log.error(&quot;OpenDirectory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-                        raise
-                else:
-                    # Success, so break the retry loop
-                    break
-
-        self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list matched records: %s&quot; % (len(results),))
-
-        enabledRecords = []
-        disabledRecords = []
-
-        for (recordShortName, value) in results:
-
-            # Now get useful record info.
-            recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-            recordShortNames = self._uniqueTupleFromAttribute(value.get(dsattributes.kDSNAttrRecordName))
-            recordType = value.get(dsattributes.kDSNAttrRecordType)
-            if isinstance(recordType, list):
-                recordType = recordType[0]
-            recordAuthIDs = self._setFromAttribute(value.get(dsattributes.kDSNAttrAltSecurityIdentities))
-            recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
-            recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
-            recordLastName = value.get(dsattributes.kDS1AttrLastName)
-            recordEmailAddresses = self._setFromAttribute(value.get(dsattributes.kDSNAttrEMailAddress), lower=True)
-            recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
-
-            if not recordType:
-                self.log.debug(&quot;Record (unknown)%s in node %s has no recordType; ignoring.&quot;
-                               % (recordShortName, recordNodeName))
-                continue
-
-            recordType = self._fromODRecordTypes[recordType]
-
-            if not recordGUID:
-                self.log.debug(&quot;Record (%s)%s in node %s has no GUID; ignoring.&quot;
-                               % (recordType, recordShortName, recordNodeName))
-                continue
-
-            if recordGUID.lower().startswith(&quot;ffffeeee-dddd-cccc-bbbb-aaaa&quot;):
-                self.log.debug(&quot;Ignoring system record (%s)%s in node %s.&quot;
-                               % (recordType, recordShortName, recordNodeName))
-                continue
-
-            # If restrictToGroup is in effect, all guids which are not a member
-            # of that group are disabled (overriding the augments db).
-            if (self.restrictedGUIDs is not None):
-                unrestricted = recordGUID in self.restrictedGUIDs
-            else:
-                unrestricted = True
-
-            # Special case for groups, which have members.
-            if recordType == self.recordType_groups:
-                memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
-                if memberGUIDs is None:
-                    memberGUIDs = ()
-                elif type(memberGUIDs) is str:
-                    memberGUIDs = (memberGUIDs,)
-                nestedGUIDs = value.get(dsattributes.kDSNAttrNestedGroups)
-                if nestedGUIDs:
-                    if type(nestedGUIDs) is str:
-                        nestedGUIDs = (nestedGUIDs,)
-                    memberGUIDs += tuple(nestedGUIDs)
-                else:
-                    nestedGUIDs = ()
-            else:
-                memberGUIDs = ()
-                nestedGUIDs = ()
-
-            # Special case for resources and locations
-            autoSchedule = False
-            proxyGUIDs = ()
-            readOnlyProxyGUIDs = ()
-            if recordType in (DirectoryService.recordType_resources, DirectoryService.recordType_locations):
-                resourceInfo = value.get(dsattributes.kDSNAttrResourceInfo)
-                if resourceInfo is not None:
-                    if type(resourceInfo) is not str:
-                        resourceInfo = resourceInfo[0]
-                    try:
-                        autoSchedule, proxy, read_only_proxy = self.parseResourceInfo(resourceInfo, recordGUID, recordType, recordShortName)
-                    except ValueError:
-                        continue
-                    if proxy:
-                        proxyGUIDs = (proxy,)
-                    if read_only_proxy:
-                        readOnlyProxyGUIDs = (read_only_proxy,)
-
-            record = OpenDirectoryRecord(
-                service=self,
-                recordType=recordType,
-                guid=recordGUID,
-                nodeName=recordNodeName,
-                shortNames=recordShortNames,
-                authIDs=recordAuthIDs,
-                fullName=recordFullName,
-                firstName=recordFirstName,
-                lastName=recordLastName,
-                emailAddresses=recordEmailAddresses,
-                memberGUIDs=memberGUIDs,
-                nestedGUIDs=nestedGUIDs,
-                extProxies=proxyGUIDs,
-                extReadOnlyProxies=readOnlyProxyGUIDs,
-            )
-
-            # Look up augment information
-            # TODO: this needs to be deferred but for now we hard code the deferred result because
-            # we know it is completing immediately.
-            if self.augmentService is not None:
-                d = self.augmentService.getAugmentRecord(record.guid,
-                    recordType)
-                d.addCallback(lambda x: record.addAugmentInformation(x))
-
-            # Override based on ResourceInfo
-            if autoSchedule:
-                record.autoSchedule = True
-
-            if not unrestricted:
-                self.log.debug(&quot;%s is not enabled because it's not a member of group: %s&quot; % (recordGUID, self.restrictToGroup))
-                record.enabledForCalendaring = False
-                record.enabledForAddressBooks = False
-
-            record.applySACLs()
-
-            if record.enabledForCalendaring:
-                enabledRecords.append(record)
-            else:
-                disabledRecords.append(record)
-
-        record = None
-        if len(enabledRecords) == 1:
-            record = enabledRecords[0]
-        elif len(enabledRecords) == 0 and len(disabledRecords) == 1:
-            record = disabledRecords[0]
-        elif indexType == self.INDEX_TYPE_GUID and len(enabledRecords) &gt; 1:
-            self.log.error(&quot;Duplicate records found for GUID %s:&quot; % (indexKey,))
-            for duplicateRecord in enabledRecords:
-                self.log.error(&quot;Duplicate: %s&quot; % (&quot;, &quot;.join(duplicateRecord.shortNames)))
-
-        if record:
-            if isinstance(origIndexKey, unicode):
-                origIndexKey = origIndexKey.encode(&quot;utf-8&quot;)
-            self.log.debug(&quot;Storing (%s %s) %s in internal cache&quot; % (indexType, origIndexKey, record))
-
-            self.recordCacheForType(recordType).addRecord(record, indexType, origIndexKey)
-
-
-    def getResourceInfo(self):
-        &quot;&quot;&quot;
-        Resource information including proxy assignments for resource and
-        locations, as well as auto-schedule settings, used to live in the
-        directory.  This method fetches old resource info for migration
-        purposes.
-        &quot;&quot;&quot;
-        attrs = [
-            dsattributes.kDS1AttrGeneratedUID,
-            dsattributes.kDSNAttrResourceInfo,
-        ]
-
-        for recordType in (dsattributes.kDSStdRecordTypePlaces, dsattributes.kDSStdRecordTypeResources):
-            try:
-                self.log.debug(&quot;opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)&quot; % (
-                    self.directory,
-                    recordType,
-                    attrs,
-                ))
-                results = self.odModule.listAllRecordsWithAttributes_list(
-                    self.directory,
-                    recordType,
-                    attrs,
-                )
-            except self.odModule.ODError, ex:
-                self.log.error(&quot;OpenDirectory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-                raise
-
-            for (recordShortName, value) in results:
-                recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
-                resourceInfo = value.get(dsattributes.kDSNAttrResourceInfo)
-                if resourceInfo is not None:
-                    if type(resourceInfo) is not str:
-                        resourceInfo = resourceInfo[0]
-                    try:
-                        autoSchedule, proxy, readOnlyProxy = self.parseResourceInfo(resourceInfo,
-                            recordGUID, recordType, recordShortName)
-                    except ValueError:
-                        continue
-                    yield recordGUID, autoSchedule, proxy, readOnlyProxy
-
-
-    def isAvailable(self):
-        &quot;&quot;&quot;
-        Returns True if all configured directory nodes are accessible, False otherwise
-        &quot;&quot;&quot;
-
-        if self.node == &quot;/Search&quot;:
-            result = self.odModule.getNodeAttributes(self.directory, &quot;/Search&quot;,
-                (dsattributes.kDS1AttrSearchPath,))
-            nodes = result[dsattributes.kDS1AttrSearchPath]
-        else:
-            nodes = [self.node]
-
-        try:
-            for node in nodes:
-                self.odModule.getNodeAttributes(self.directory, node, [dsattributes.kDSNAttrNodePath])
-        except self.odModule.ODError:
-            self.log.warn(&quot;OpenDirectory Node %s not available&quot; % (node,))
-            return False
-
-        return True
-
-
-    @inlineCallbacks
-    def getGroups(self, guids):
-        &quot;&quot;&quot;
-        Returns a set of group records for the list of guids passed in.  For
-        any group that also contains subgroups, those subgroups' records are
-        also returned, and so on.
-        &quot;&quot;&quot;
-
-        recordsByGUID = {}
-        valuesToFetch = guids
-
-        loop = 1
-        while valuesToFetch:
-            self.log.debug(&quot;getGroups loop %d&quot; % (loop,))
-
-            results = []
-
-            for batch in splitIntoBatches(valuesToFetch, self.batchSize):
-                fields = []
-                for value in batch:
-                    fields.append([&quot;guid&quot;, value, False, &quot;equals&quot;])
-                self.log.debug(&quot;getGroups fetching batch of %d&quot; %
-                    (len(fields),))
-                result = list((yield self.recordsMatchingFields(fields,
-                    recordType=self.recordType_groups)))
-                results.extend(result)
-                self.log.debug(&quot;getGroups got back batch of %d for subtotal of %d&quot; %
-                    (len(result), len(results)))
-
-            # Reset values for next iteration
-            valuesToFetch = set()
-
-            for record in results:
-                guid = record.guid
-                if guid not in recordsByGUID:
-                    recordsByGUID[guid] = record
-
-                # record.nestedGUIDs() contains the sub groups of this group
-                for memberGUID in record.nestedGUIDs():
-                    if memberGUID not in recordsByGUID:
-                        self.log.debug(&quot;getGroups group %s contains group %s&quot; %
-                            (record.guid, memberGUID))
-                        valuesToFetch.add(memberGUID)
-
-            loop += 1
-
-        returnValue(recordsByGUID.values())
-
-
-
-def buildQueries(recordTypes, fields, mapping):
-    &quot;&quot;&quot;
-    Determine how many queries need to be performed in order to work around opendirectory
-    quirks, where searching on fields that don't apply to a given recordType returns incorrect
-    results (either none, or all records).
-    &quot;&quot;&quot;
-
-    queries = {}
-    for recordType in recordTypes:
-        for field, value, caseless, matchType in fields:
-            if field in mapping:
-                if recordType in mapping[field]['appliesTo']:
-                    ODField = mapping[field]['odField']
-                    key = (ODField, value, caseless, matchType)
-                    queries.setdefault(key, []).append(recordType)
-
-    return queries
-
-
-
-def buildLocalQueriesFromTokens(tokens, mapping):
-    &quot;&quot;&quot;
-    OD /Local doesn't support nested complex queries, so create a list of
-    complex queries that will be ANDed together in recordsMatchingTokens()
-
-    @param tokens: The tokens to search on
-    @type tokens: C{list} of C{str}
-    @param mapping: The mapping of DirectoryRecord attributes to OD attributes
-    @type mapping: C{dict}
-    @return: A list of expression objects
-    @type: C{list}
-    &quot;&quot;&quot;
-
-    if len(tokens) == 0:
-        return None
-
-    fields = [
-        (&quot;fullName&quot;, dsattributes.eDSContains),
-        (&quot;emailAddresses&quot;, dsattributes.eDSStartsWith),
-    ]
-
-    results = []
-    for token in tokens:
-        queries = []
-        for field, comparison in fields:
-            ODField = mapping[field]['odField']
-            query = dsquery.match(ODField, token, comparison)
-            queries.append(query)
-        results.append(dsquery.expression(dsquery.expression.OR, queries))
-    return results
-
-
-
-def buildNestedQueryFromTokens(tokens, mapping):
-    &quot;&quot;&quot;
-    Build a DS query espression such that all the tokens must appear in either
-    the fullName (anywhere), emailAddresses (at the beginning) or record name
-    (at the beginning).
-
-    @param tokens: The tokens to search on
-    @type tokens: C{list} of C{str}
-    @param mapping: The mapping of DirectoryRecord attributes to OD attributes
-    @type mapping: C{dict}
-    @return: The nested expression object
-    @type: dsquery.expression
-    &quot;&quot;&quot;
-
-    if len(tokens) == 0:
-        return None
-
-    fields = [
-        (&quot;fullName&quot;, dsattributes.eDSContains),
-        (&quot;emailAddresses&quot;, dsattributes.eDSStartsWith),
-        (&quot;recordName&quot;, dsattributes.eDSStartsWith),
-    ]
-
-    outer = []
-    for token in tokens:
-        inner = []
-        for field, comparison in fields:
-            ODField = mapping[field]['odField']
-            query = dsquery.match(ODField, token, comparison)
-            inner.append(query)
-        outer.append(dsquery.expression(dsquery.expression.OR, inner))
-    return dsquery.expression(dsquery.expression.AND, outer)
-
-
-
-class OpenDirectoryRecord(CachingDirectoryRecord):
-    &quot;&quot;&quot;
-    OpenDirectory implementation of L{IDirectoryRecord}.
-    &quot;&quot;&quot;
-    def __init__(
-        self, service, recordType, guid, nodeName, shortNames, authIDs,
-        fullName, firstName, lastName, emailAddresses, memberGUIDs, nestedGUIDs,
-        extProxies, extReadOnlyProxies,
-    ):
-        super(OpenDirectoryRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            guid=guid,
-            shortNames=shortNames,
-            authIDs=authIDs,
-            fullName=fullName,
-            firstName=firstName,
-            lastName=lastName,
-            emailAddresses=emailAddresses,
-            extProxies=extProxies,
-            extReadOnlyProxies=extReadOnlyProxies,
-        )
-        self.nodeName = nodeName
-
-        self._memberGUIDs = tuple(memberGUIDs)
-        self._nestedGUIDs = tuple(nestedGUIDs)
-        self._groupMembershipGUIDs = None
-
-
-    def __repr__(self):
-        if self.service.realmName == self.nodeName:
-            location = self.nodeName
-        else:
-            location = &quot;%s-&gt;%s&quot; % (self.service.realmName, self.nodeName)
-
-        return &quot;&lt;%s[%s@%s(%s)] %s(%s) %r&gt;&quot; % (
-            self.__class__.__name__,
-            self.recordType,
-            self.service.guid,
-            location,
-            self.guid,
-            &quot;,&quot;.join(self.shortNames),
-            self.fullName
-        )
-
-
-    def members(self):
-        if self.recordType != self.service.recordType_groups:
-            return
-
-        for guid in self._memberGUIDs:
-            userRecord = self.service.recordWithGUID(guid)
-            if userRecord is not None:
-                yield userRecord
-
-
-    def groups(self):
-        if self._groupMembershipGUIDs is None:
-            self._groupMembershipGUIDs = self.service.groupsForGUID(self.guid)
-
-        for guid in self._groupMembershipGUIDs:
-            record = self.service.recordWithGUID(guid)
-            if record:
-                yield record
-
-
-    def memberGUIDs(self):
-        return set(self._memberGUIDs)
-
-
-    def nestedGUIDs(self):
-        return set(self._nestedGUIDs)
-
-
-    def verifyCredentials(self, credentials):
-        if isinstance(credentials, UsernamePassword):
-            # Check cached password
-            try:
-                if credentials.password == self.password:
-                    return True
-            except AttributeError:
-                pass
-
-            # Check with directory services
-            try:
-                if self.service.odModule.authenticateUserBasic(self.service.directory, self.nodeName, self.shortNames[0], credentials.password):
-                    # Cache the password to avoid future DS queries
-                    self.password = credentials.password
-                    return True
-            except self.service.odModule.ODError, e:
-                self.log.error(&quot;OpenDirectory (node=%s) error while performing basic authentication for user %s: %s&quot;
-                            % (self.service.realmName, self.shortNames[0], e))
-
-            return False
-
-        elif isinstance(credentials, DigestedCredentials):
-            #
-            # We need a special format for the &quot;challenge&quot; and &quot;response&quot; strings passed into OpenDirectory, as it is
-            # picky about exactly what it receives.
-            #
-            try:
-                if &quot;algorithm&quot; not in credentials.fields:
-                    credentials.fields[&quot;algorithm&quot;] = &quot;md5&quot;
-                challenge = 'Digest realm=&quot;%(realm)s&quot;, nonce=&quot;%(nonce)s&quot;, algorithm=%(algorithm)s' % credentials.fields
-                response = (
-                    'Digest username=&quot;%(username)s&quot;, '
-                    'realm=&quot;%(realm)s&quot;, '
-                    'nonce=&quot;%(nonce)s&quot;, '
-                    'uri=&quot;%(uri)s&quot;, '
-                    'response=&quot;%(response)s&quot;,'
-                    'algorithm=%(algorithm)s'
-                ) % credentials.fields
-            except KeyError, e:
-                self.log.error(
-                    &quot;OpenDirectory (node=%s) error while performing digest authentication for user %s: &quot;
-                    &quot;missing digest response field: %s in: %s&quot;
-                    % (self.service.realmName, self.shortNames[0], e, credentials.fields)
-                )
-                return False
-
-            try:
-                if self.digestcache[credentials.fields[&quot;uri&quot;]] == response:
-                    return True
-            except (AttributeError, KeyError):
-                pass
-
-            try:
-                if self.service.odModule.authenticateUserDigest(
-                    self.service.directory,
-                    self.nodeName,
-                    self.shortNames[0],
-                    challenge,
-                    response,
-                    credentials.method
-                ):
-                    try:
-                        cache = self.digestcache
-                    except AttributeError:
-                        cache = self.digestcache = {}
-
-                    cache[credentials.fields[&quot;uri&quot;]] = response
-
-                    return True
-                else:
-                    self.log.debug(
-&quot;&quot;&quot;OpenDirectory digest authentication failed with:
-    Nodename:  %s
-    Username:  %s
-    Challenge: %s
-    Response:  %s
-    Method:    %s
-&quot;&quot;&quot; % (self.nodeName, self.shortNames[0], challenge, response,
-       credentials.method))
-
-            except self.service.odModule.ODError, e:
-                self.log.error(
-                    &quot;OpenDirectory (node=%s) error while performing digest authentication for user %s: %s&quot;
-                    % (self.service.realmName, self.shortNames[0], e)
-                )
-                return False
-
-            return False
-
-        return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
-
-
-
-class OpenDirectoryInitError(DirectoryError):
-    &quot;&quot;&quot;
-    OpenDirectory initialization error.
-    &quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycachingdirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/cachingdirectory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/cachingdirectory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/cachingdirectory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,473 +0,0 @@
</span><del>-# -*- test-case-name: twistedcaldav.directory.test.test_cachedirectory -*-
-##
-# Copyright (c) 2009-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Caching directory service implementation.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;CachingDirectoryService&quot;,
-    &quot;CachingDirectoryRecord&quot;,
-    &quot;DictRecordTypeCache&quot;,
-]
-
-
-import time
-
-import base64
-
-from twext.python.log import Logger
-
-from twistedcaldav.config import config
-from twistedcaldav.memcacheclient import ClientFactory, MemcacheError
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError, UnknownRecordTypeError
-from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
-from twistedcaldav.directory.util import normalizeUUID
-
-
-class RecordTypeCache(object):
-    &quot;&quot;&quot;
-    Abstract class for a record type cache. We will likely have dict and memcache implementations of this.
-    &quot;&quot;&quot;
-
-    def __init__(self, directoryService, recordType):
-
-        self.directoryService = directoryService
-        self.recordType = recordType
-
-
-    def addRecord(self, record, indexType, indexKey, useMemcache=True,
-        neverExpire=False):
-        raise NotImplementedError()
-
-
-    def removeRecord(self, record):
-        raise NotImplementedError()
-
-
-    def findRecord(self, indexType, indexKey):
-        raise NotImplementedError()
-
-
-
-class DictRecordTypeCache(RecordTypeCache):
-    &quot;&quot;&quot;
-    Cache implementation using a dict, and uses memcached to share records
-    with other instances.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    def __init__(self, directoryService, recordType):
-
-        super(DictRecordTypeCache, self).__init__(directoryService, recordType)
-        self.records = set()
-        self.recordsIndexedBy = {
-            CachingDirectoryService.INDEX_TYPE_GUID     : {},
-            CachingDirectoryService.INDEX_TYPE_SHORTNAME: {},
-            CachingDirectoryService.INDEX_TYPE_CUA    : {},
-            CachingDirectoryService.INDEX_TYPE_AUTHID   : {},
-        }
-        self.directoryService = directoryService
-        self.lastPurgedTime = time.time()
-
-
-    def addRecord(self, record, indexType, indexKey, useMemcache=True,
-        neverExpire=False):
-
-        useMemcache = useMemcache and config.Memcached.Pools.Default.ClientEnabled
-        if neverExpire:
-            record.neverExpire()
-
-        self.records.add(record)
-
-        # Also index/cache on guid
-        indexTypes = [(indexType, indexKey)]
-        if indexType != CachingDirectoryService.INDEX_TYPE_GUID:
-            indexTypes.append((CachingDirectoryService.INDEX_TYPE_GUID,
-                record.guid))
-
-        for indexType, indexKey in indexTypes:
-            self.recordsIndexedBy[indexType][indexKey] = record
-            if useMemcache:
-                key = self.directoryService.generateMemcacheKey(indexType, indexKey,
-                    record.recordType)
-                self.log.debug(&quot;Memcache: storing %s&quot; % (key,))
-                try:
-                    self.directoryService.memcacheSet(key, record)
-                except DirectoryMemcacheError:
-                    self.log.error(&quot;Memcache: failed to store %s&quot; % (key,))
-                    pass
-
-
-    def removeRecord(self, record):
-        if record in self.records:
-            self.records.remove(record)
-            self.log.debug(&quot;Removed record %s&quot; % (record.guid,))
-            for indexType in self.directoryService.indexTypes():
-                try:
-                    indexData = getattr(record, CachingDirectoryService.indexTypeToRecordAttribute[indexType])
-                except AttributeError:
-                    continue
-                if isinstance(indexData, basestring):
-                    indexData = [indexData]
-                for item in indexData:
-                    try:
-                        del self.recordsIndexedBy[indexType][item]
-                    except KeyError:
-                        pass
-
-
-    def findRecord(self, indexType, indexKey):
-        self.purgeExpiredRecords()
-        return self.recordsIndexedBy[indexType].get(indexKey)
-
-
-    def purgeExpiredRecords(self):
-        &quot;&quot;&quot;
-        Scan the cached records and remove any that have expired.
-        Does nothing if we've scanned within the past cacheTimeout seconds.
-        &quot;&quot;&quot;
-        if time.time() - self.lastPurgedTime &gt; self.directoryService.cacheTimeout:
-            for record in list(self.records):
-                if record.isExpired():
-                    self.removeRecord(record)
-            self.lastPurgedTime = time.time()
-
-
-
-class CachingDirectoryService(DirectoryService):
-    &quot;&quot;&quot;
-    Caching Directory implementation of L{IDirectoryService}.
-
-    This is class must be overridden to provide a concrete implementation.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    INDEX_TYPE_GUID = &quot;guid&quot;
-    INDEX_TYPE_SHORTNAME = &quot;shortname&quot;
-    INDEX_TYPE_CUA = &quot;cua&quot;
-    INDEX_TYPE_AUTHID = &quot;authid&quot;
-
-    indexTypeToRecordAttribute = {
-        &quot;guid&quot;     : &quot;guid&quot;,
-        &quot;shortname&quot;: &quot;shortNames&quot;,
-        &quot;cua&quot;      : &quot;calendarUserAddresses&quot;,
-        &quot;authid&quot;   : &quot;authIDs&quot;,
-    }
-
-    def __init__(
-        self,
-        cacheTimeout=1,
-        negativeCaching=False,
-        cacheClass=DictRecordTypeCache,
-    ):
-        &quot;&quot;&quot;
-        @param cacheTimeout: C{int} number of minutes before cache is invalidated.
-        &quot;&quot;&quot;
-
-        self.cacheTimeout = cacheTimeout * 60
-        self.negativeCaching = negativeCaching
-
-        self.cacheClass = cacheClass
-        self._initCaches()
-
-        super(CachingDirectoryService, self).__init__()
-
-
-    def _getMemcacheClient(self, refresh=False):
-        if refresh or not hasattr(self, &quot;memcacheClient&quot;):
-            self.memcacheClient = ClientFactory.getClient(['%s:%s' %
-                (config.Memcached.Pools.Default.BindAddress, config.Memcached.Pools.Default.Port)],
-                debug=0, pickleProtocol=2)
-        return self.memcacheClient
-
-
-    def memcacheSet(self, key, record):
-
-        hideService = isinstance(record, DirectoryRecord)
-
-        try:
-            if hideService:
-                record.service = None # so we don't pickle service
-
-            key = base64.b64encode(key)
-            if not self._getMemcacheClient().set(key, record, time=self.cacheTimeout):
-                self.log.error(&quot;Could not write to memcache, retrying&quot;)
-                if not self._getMemcacheClient(refresh=True).set(
-                    key, record,
-                    time=self.cacheTimeout
-                ):
-                    self.log.error(&quot;Could not write to memcache again, giving up&quot;)
-                    del self.memcacheClient
-                    raise DirectoryMemcacheError(&quot;Failed to write to memcache&quot;)
-        finally:
-            if hideService:
-                record.service = self
-
-
-    def memcacheGet(self, key):
-
-        key = base64.b64encode(key)
-        try:
-            record = self._getMemcacheClient().get(key)
-            if record is not None and isinstance(record, DirectoryRecord):
-                record.service = self
-        except MemcacheError:
-            self.log.error(&quot;Could not read from memcache, retrying&quot;)
-            try:
-                record = self._getMemcacheClient(refresh=True).get(key)
-                if record is not None and isinstance(record, DirectoryRecord):
-                    record.service = self
-            except MemcacheError:
-                self.log.error(&quot;Could not read from memcache again, giving up&quot;)
-                del self.memcacheClient
-                raise DirectoryMemcacheError(&quot;Failed to read from memcache&quot;)
-        return record
-
-
-    def generateMemcacheKey(self, indexType, indexKey, recordType):
-        &quot;&quot;&quot;
-        Return a key that can be used to store/retrieve a record in memcache.
-        if short-name is the indexType the recordType be encoded into the key.
-
-        @param indexType: one of the indexTypes( ) values
-        @type indexType: C{str}
-        @param indexKey: the value being indexed
-        @type indexKey: C{str}
-        @param recordType: the type of record being cached
-        @type recordType: C{str}
-        @return: a memcache key comprised of the passed-in values and the directory
-            service's baseGUID
-        @rtype: C{str}
-        &quot;&quot;&quot;
-        keyVersion = 2
-        if indexType == CachingDirectoryService.INDEX_TYPE_SHORTNAME:
-            return &quot;dir|v%d|%s|%s|%s|%s&quot; % (keyVersion, self.baseGUID, recordType,
-                indexType, indexKey)
-        else:
-            return &quot;dir|v%d|%s|%s|%s&quot; % (keyVersion, self.baseGUID, indexType,
-                indexKey)
-
-
-    def _initCaches(self):
-        self._recordCaches = dict([
-            (recordType, self.cacheClass(self, recordType))
-            for recordType in self.recordTypes()
-        ])
-
-        self._disabledKeys = dict([(indexType, dict()) for indexType in self.indexTypes()])
-
-
-    def indexTypes(self):
-
-        return (
-            CachingDirectoryService.INDEX_TYPE_GUID,
-            CachingDirectoryService.INDEX_TYPE_SHORTNAME,
-            CachingDirectoryService.INDEX_TYPE_CUA,
-            CachingDirectoryService.INDEX_TYPE_AUTHID,
-        )
-
-
-    def recordCacheForType(self, recordType):
-        try:
-            return self._recordCaches[recordType]
-        except KeyError:
-            raise UnknownRecordTypeError(recordType)
-
-
-    def listRecords(self, recordType):
-        return self.recordCacheForType(recordType).records
-
-
-    def recordWithShortName(self, recordType, shortName):
-        return self._lookupRecord((recordType,), CachingDirectoryService.INDEX_TYPE_SHORTNAME, shortName)
-
-
-    def recordWithCalendarUserAddress(self, address):
-        address = normalizeCUAddr(address)
-        record = None
-        if address.startswith(&quot;mailto:&quot;):
-            record = self._lookupRecord(None, CachingDirectoryService.INDEX_TYPE_CUA, address)
-            return record if record and record.enabledForCalendaring else None
-        else:
-            return DirectoryService.recordWithCalendarUserAddress(self, address)
-
-
-    def recordWithAuthID(self, authID):
-        return self._lookupRecord(None, CachingDirectoryService.INDEX_TYPE_AUTHID, authID)
-
-
-    def recordWithGUID(self, guid):
-        guid = normalizeUUID(guid)
-        return self._lookupRecord(None, CachingDirectoryService.INDEX_TYPE_GUID, guid)
-
-    recordWithUID = recordWithGUID
-
-    def _lookupRecord(self, recordTypes, indexType, indexKey):
-
-        if recordTypes is None:
-            recordTypes = self.recordTypes()
-        else:
-            # Only use recordTypes this service supports:
-            supportedRecordTypes = self.recordTypes()
-            recordTypes = [t for t in recordTypes if t in supportedRecordTypes]
-            if not recordTypes:
-                return None
-
-        def lookup():
-            for recordType in recordTypes:
-                record = self.recordCacheForType(recordType).findRecord(indexType, indexKey)
-
-                if record:
-                    if record.isExpired():
-                        self.recordCacheForType(recordType).removeRecord(record)
-                        return None
-                    else:
-                        return record
-            else:
-                return None
-
-        record = lookup()
-        if record:
-            return record
-
-        if self.negativeCaching:
-
-            # Check negative cache (take cache entry timeout into account)
-            try:
-                disabledTime = self._disabledKeys[indexType][indexKey]
-                if time.time() - disabledTime &lt; self.cacheTimeout:
-                    return None
-            except KeyError:
-                pass
-
-        # Check memcache
-        if config.Memcached.Pools.Default.ClientEnabled:
-
-            # The only time the recordType arg matters is when indexType is
-            # short-name, and in that case recordTypes will contain exactly
-            # one recordType, so using recordTypes[0] here is always safe:
-            key = self.generateMemcacheKey(indexType, indexKey, recordTypes[0])
-
-            self.log.debug(&quot;Memcache: checking %s&quot; % (key,))
-
-            try:
-                record = self.memcacheGet(key)
-            except DirectoryMemcacheError:
-                self.log.error(&quot;Memcache: failed to get %s&quot; % (key,))
-                record = None
-
-            if record is None:
-                self.log.debug(&quot;Memcache: miss %s&quot; % (key,))
-            else:
-                self.log.debug(&quot;Memcache: hit %s&quot; % (key,))
-                self.recordCacheForType(record.recordType).addRecord(record, indexType, indexKey, useMemcache=False)
-                return record
-
-            if self.negativeCaching:
-
-                # Check negative memcache
-                try:
-                    val = self.memcacheGet(&quot;-%s&quot; % (key,))
-                except DirectoryMemcacheError:
-                    self.log.error(&quot;Memcache: failed to get -%s&quot; % (key,))
-                    val = None
-                if val == 1:
-                    self.log.debug(&quot;Memcache: negative %s&quot; % (key,))
-                    self._disabledKeys[indexType][indexKey] = time.time()
-                    return None
-
-        # Try query
-        self.log.debug(&quot;Faulting record for attribute '%s' with value '%s'&quot; % (indexType, indexKey,))
-        self.queryDirectory(recordTypes, indexType, indexKey)
-
-        # Now try again from cache
-        record = lookup()
-        if record:
-            self.log.debug(&quot;Found record for attribute '%s' with value '%s'&quot; % (indexType, indexKey,))
-            return record
-
-        if self.negativeCaching:
-
-            # Add to negative cache with timestamp
-            self.log.debug(&quot;Failed to fault record for attribute '%s' with value '%s'&quot; % (indexType, indexKey,))
-            self._disabledKeys[indexType][indexKey] = time.time()
-
-            if config.Memcached.Pools.Default.ClientEnabled:
-                self.log.debug(&quot;Memcache: storing (negative) %s&quot; % (key,))
-                try:
-                    self.memcacheSet(&quot;-%s&quot; % (key,), 1)
-                except DirectoryMemcacheError:
-                    self.log.error(&quot;Memcache: failed to set -%s&quot; % (key,))
-                    pass
-
-        return None
-
-
-    def queryDirectory(self, recordTypes, indexType, indexKey):
-        raise NotImplementedError()
-
-
-
-class CachingDirectoryRecord(DirectoryRecord):
-
-    def __init__(
-        self, service, recordType, guid,
-        shortNames=(), authIDs=set(),
-        fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, **kwargs
-    ):
-        super(CachingDirectoryRecord, self).__init__(
-            service,
-            recordType,
-            guid,
-            shortNames=shortNames,
-            authIDs=authIDs,
-            fullName=fullName,
-            firstName=firstName,
-            lastName=lastName,
-            emailAddresses=emailAddresses,
-            uid=uid,
-            **kwargs
-        )
-
-        self.cachedTime = time.time()
-
-
-    def neverExpire(self):
-        self.cachedTime = 0
-
-
-    def isExpired(self):
-        &quot;&quot;&quot;
-        Returns True if this record was created more than cacheTimeout
-        seconds ago
-        &quot;&quot;&quot;
-        if (
-            self.cachedTime != 0 and
-            time.time() - self.cachedTime &gt; self.service.cacheTimeout
-        ):
-            return True
-        else:
-            return False
-
-
-
-class DirectoryMemcacheError(DirectoryError):
-    &quot;&quot;&quot;
-    Error communicating with memcached.
-    &quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendar.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendar.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendar.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -35,7 +35,6 @@
</span><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.idirectory import IDirectoryService
</del><span class="cx"> from twistedcaldav.directory.common import uidsResourceName, \
</span><span class="cx">     CommonUIDProvisioningResource, CommonHomeTypeProvisioningResource
</span><span class="cx"> 
</span><span class="lines">@@ -48,7 +47,10 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> # FIXME: copied from resource.py to avoid circular dependency
</span><ins>+
+
</ins><span class="cx"> class CalDAVComplianceMixIn(object):
</span><span class="cx">     def davComplianceClasses(self):
</span><span class="cx">         return (
</span><span class="lines">@@ -102,9 +104,14 @@
</span><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        # MOVE2WHO
-        for name, recordType in [(r.name + &quot;s&quot;, r) for r in self.directory.recordTypes()]:
-            self.putChild(name, DirectoryCalendarHomeTypeProvisioningResource(self, name, recordType))
</del><ins>+        # ...just &quot;users&quot; though.  If we iterate all of the directory's
+        # recordTypes, we also get the proxy sub principal types.
+        for recordTypeName in [
+            self.directory.recordTypeToOldName(r) for r in [
+                self.directory.recordType.user
+            ]
+        ]:
+            self.putChild(recordTypeName, DirectoryCalendarHomeTypeProvisioningResource(self, recordTypeName, r))
</ins><span class="cx"> 
</span><span class="cx">         self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="lines">@@ -114,8 +121,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><del>-        # MOVE2WHO
-        return [r.name + &quot;s&quot; for r in self.directory.recordTypes()]
</del><ins>+        return [self.directory.recordTypeToOldName(r) for r in self.directory.recordTypes()]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalCollections(self):
</span><span class="lines">@@ -153,9 +159,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class DirectoryCalendarHomeTypeProvisioningResource(
</span><del>-        CommonHomeTypeProvisioningResource,
-        DirectoryCalendarProvisioningResource
-    ):
</del><ins>+    CommonHomeTypeProvisioningResource,
+    DirectoryCalendarProvisioningResource
+):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Resource which provisions calendar home collections of a specific
</span><span class="cx">     record type as needed.
</span><span class="lines">@@ -181,16 +187,15 @@
</span><span class="cx">         return joinURL(self._parent.url(), self.name)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def listChildren(self):
</span><span class="cx">         if config.EnablePrincipalListings:
</span><del>-
-            def _recordShortnameExpand():
-                for record in self.directory.listRecords(self.recordType):
-                    if record.enabledForCalendaring:
-                        for shortName in record.shortNames:
-                            yield shortName
-
-            return _recordShortnameExpand()
</del><ins>+            children = []
+            for record in (yield self.directory.listRecords(self.recordType)):
+                if record.enabledForCalendaring:
+                    for shortName in record.shortNames:
+                        children.append(shortName)
+            returnValue(children)
</ins><span class="cx">         else:
</span><span class="cx">             # Not a listable collection
</span><span class="cx">             raise HTTPError(responsecode.FORBIDDEN)
</span><span class="lines">@@ -226,9 +231,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class DirectoryCalendarHomeUIDProvisioningResource (
</span><del>-        CommonUIDProvisioningResource,
-        DirectoryCalendarProvisioningResource
-    ):
</del><ins>+    CommonUIDProvisioningResource,
+    DirectoryCalendarProvisioningResource
+):
</ins><span class="cx"> 
</span><span class="cx">     homeResourceTypeName = 'calendars'
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycalendaruserproxypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendaruserproxy.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendaruserproxy.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/calendaruserproxy.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -40,10 +40,11 @@
</span><span class="cx"> from twistedcaldav.database import (
</span><span class="cx">     AbstractADBAPIDatabase, ADBAPISqliteMixin, ADBAPIPostgreSQLMixin
</span><span class="cx"> )
</span><del>-from twistedcaldav.directory.principal import formatLink
-from twistedcaldav.directory.principal import formatLinks
-from twistedcaldav.directory.principal import formatPrincipals
</del><span class="cx"> from twistedcaldav.directory.util import normalizeUUID
</span><ins>+from twistedcaldav.directory.util import (
+    formatLink, formatLinks, formatPrincipals
+)
+
</ins><span class="cx"> from twistedcaldav.extensions import (
</span><span class="cx">     DAVPrincipalResource, DAVResourceWithChildrenMixin
</span><span class="cx"> )
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorycommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/common.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/common.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/common.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -68,7 +68,7 @@
</span><span class="cx">         name = record.uid
</span><span class="cx"> 
</span><span class="cx">         if record is None:
</span><del>-            log.debug(&quot;No directory record with GUID %r&quot; % (name,))
</del><ins>+            log.debug(&quot;No directory record with UID %r&quot; % (name,))
</ins><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><span class="cx">         # MOVE2WHO
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorydirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/directory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/directory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/directory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,1510 +0,0 @@
</span><del>-# -*- test-case-name: twistedcaldav.directory.test -*-
-##
-# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-
-&quot;&quot;&quot;
-Generic directory service classes.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;DirectoryService&quot;,
-    &quot;DirectoryRecord&quot;,
-    &quot;DirectoryError&quot;,
-    &quot;DirectoryConfigurationError&quot;,
-    &quot;UnknownRecordTypeError&quot;,
-    &quot;GroupMembershipCacheUpdater&quot;,
-]
-
-from plistlib import readPlistFromString
-
-from twext.python.log import Logger
-from txweb2.dav.auth import IPrincipalCredentials
-from txweb2.dav.util import joinURL
-
-from twisted.cred.checkers import ICredentialsChecker
-from twisted.cred.error import UnauthorizedLogin
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.python.filepath import FilePath
-
-from twistedcaldav.config import config
-from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
-from twistedcaldav.directory.util import uuidFromName, normalizeUUID
-from twistedcaldav.memcacher import Memcacher
-from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
-
-from txdav.caldav.icalendardirectoryservice import ICalendarStoreDirectoryService, \
-    ICalendarStoreDirectoryRecord
-
-from xml.parsers.expat import ExpatError
-
-from zope.interface import implements
-
-import cPickle as pickle
-import datetime
-import grp
-import itertools
-import os
-import pwd
-import sys
-import types
-from urllib import unquote
-
-log = Logger()
-
-
-class DirectoryService(object):
-    implements(IDirectoryService, ICalendarStoreDirectoryService, ICredentialsChecker)
-
-    log = Logger()
-
-    ##
-    # IDirectoryService
-    ##
-
-    realmName = None
-
-    recordType_users = &quot;users&quot;
-    recordType_people = &quot;people&quot;
-    recordType_groups = &quot;groups&quot;
-    recordType_locations = &quot;locations&quot;
-    recordType_resources = &quot;resources&quot;
-    recordType_addresses = &quot;addresses&quot;
-
-    searchContext_location = &quot;location&quot;
-    searchContext_resource = &quot;resource&quot;
-    searchContext_user = &quot;user&quot;
-    searchContext_group = &quot;group&quot;
-    searchContext_attendee = &quot;attendee&quot;
-
-    aggregateService = None
-
-    def _generatedGUID(self):
-        if not hasattr(self, &quot;_guid&quot;):
-            realmName = self.realmName
-
-            assert self.baseGUID, &quot;Class %s must provide a baseGUID attribute&quot; % (self.__class__.__name__,)
-
-            if realmName is None:
-                self.log.error(&quot;Directory service %s has no realm name or GUID; generated service GUID will not be unique.&quot; % (self,))
-                realmName = &quot;&quot;
-            else:
-                self.log.info(&quot;Directory service %s has no GUID; generating service GUID from realm name.&quot; % (self,))
-
-            self._guid = uuidFromName(self.baseGUID, realmName)
-
-        return self._guid
-
-    baseGUID = None
-    guid = property(_generatedGUID)
-
-    # Needed by twistedcaldav.directorybackedaddressbook
-    liveQuery = False
-
-    def setRealm(self, realmName):
-        self.realmName = realmName
-
-
-    def available(self):
-        &quot;&quot;&quot;
-        By default, the directory is available.  This may return a boolean or a
-        Deferred which fires a boolean.
-
-        A return value of &quot;False&quot; means that the directory is currently
-        unavailable due to the service starting up.
-        &quot;&quot;&quot;
-        return True
-    # end directorybackedaddressbook requirements
-
-    ##
-    # ICredentialsChecker
-    ##
-
-    # For ICredentialsChecker
-    credentialInterfaces = (IPrincipalCredentials,)
-
-    def requestAvatarId(self, credentials):
-        credentials = IPrincipalCredentials(credentials)
-
-        # FIXME: ?
-        # We were checking if principal is enabled; seems unnecessary in current
-        # implementation because you shouldn't have a principal object for a
-        # disabled directory principal.
-
-        if credentials.authnPrincipal is None:
-            raise UnauthorizedLogin(&quot;No such user: %s&quot; % (credentials.credentials.username,))
-
-        # See if record is enabledForLogin
-        if not credentials.authnPrincipal.record.isLoginEnabled():
-            raise UnauthorizedLogin(&quot;User not allowed to log in: %s&quot; %
-                (credentials.credentials.username,))
-
-        # Handle Kerberos as a separate behavior
-        try:
-            from twistedcaldav.authkerb import NegotiateCredentials
-        except ImportError:
-            NegotiateCredentials = None
-
-        if NegotiateCredentials and isinstance(credentials.credentials,
-                                               NegotiateCredentials):
-            # If we get here with Kerberos, then authentication has already succeeded
-            return (
-                credentials.authnPrincipal.principalURL(),
-                credentials.authzPrincipal.principalURL(),
-                credentials.authnPrincipal,
-                credentials.authzPrincipal,
-            )
-        else:
-            if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
-                return (
-                    credentials.authnPrincipal.principalURL(),
-                    credentials.authzPrincipal.principalURL(),
-                    credentials.authnPrincipal,
-                    credentials.authzPrincipal,
-                )
-            else:
-                raise UnauthorizedLogin(&quot;Incorrect credentials for %s&quot; % (credentials.credentials.username,))
-
-
-    def recordTypes(self):
-        raise NotImplementedError(&quot;Subclass must implement recordTypes()&quot;)
-
-
-    def listRecords(self, recordType):
-        raise NotImplementedError(&quot;Subclass must implement listRecords()&quot;)
-
-
-    def recordWithShortName(self, recordType, shortName):
-        for record in self.listRecords(recordType):
-            if shortName in record.shortNames:
-                return record
-        return None
-
-
-    def recordWithUID(self, uid):
-        uid = normalizeUUID(uid)
-        for record in self.allRecords():
-            if record.uid == uid:
-                return record
-        return None
-
-
-    def recordWithGUID(self, guid):
-        guid = normalizeUUID(guid)
-        for record in self.allRecords():
-            if record.guid == guid:
-                return record
-        return None
-
-
-    def recordWithAuthID(self, authID):
-        for record in self.allRecords():
-            if authID in record.authIDs:
-                return record
-        return None
-
-
-    def recordWithCalendarUserAddress(self, address):
-        address = normalizeCUAddr(address)
-        record = None
-        if address.startswith(&quot;urn:uuid:&quot;):
-            guid = address[9:]
-            record = self.recordWithGUID(guid)
-        elif address.startswith(&quot;mailto:&quot;):
-            for record in self.allRecords():
-                if address[7:] in record.emailAddresses:
-                    break
-            else:
-                return None
-        elif address.startswith(&quot;/principals/&quot;):
-            parts = map(unquote, address.split(&quot;/&quot;))
-            if len(parts) == 4:
-                if parts[2] == &quot;__uids__&quot;:
-                    guid = parts[3]
-                    record = self.recordWithGUID(guid)
-                else:
-                    record = self.recordWithShortName(parts[2], parts[3])
-
-        return record if record and record.hasCalendars else None
-
-
-    def recordWithCachedGroupsAlias(self, recordType, alias):
-        &quot;&quot;&quot;
-        @param recordType: the type of the record to look up.
-        @param alias: the cached-groups alias of the record to look up.
-        @type alias: C{str}
-
-        @return: a deferred L{IDirectoryRecord} with the given cached-groups
-            alias, or C{None} if no such record is found.
-        &quot;&quot;&quot;
-        # The default implementation uses guid
-        return succeed(self.recordWithGUID(alias))
-
-
-    def allRecords(self):
-        for recordType in self.recordTypes():
-            for record in self.listRecords(recordType):
-                yield record
-
-
-    def recordsMatchingFieldsWithCUType(self, fields, operand=&quot;or&quot;,
-        cuType=None):
-        if cuType:
-            recordType = DirectoryRecord.fromCUType(cuType)
-        else:
-            recordType = None
-
-        return self.recordsMatchingFields(fields, operand=operand,
-            recordType=recordType)
-
-
-    def recordTypesForSearchContext(self, context):
-        &quot;&quot;&quot;
-        Map calendarserver-principal-search REPORT context value to applicable record types
-
-        @param context: The context value to map
-        @type context: C{str}
-        @returns: The list of record types the context maps to
-        @rtype: C{list} of C{str}
-        &quot;&quot;&quot;
-        if context == self.searchContext_location:
-            recordTypes = [self.recordType_locations]
-        elif context == self.searchContext_resource:
-            recordTypes = [self.recordType_resources]
-        elif context == self.searchContext_user:
-            recordTypes = [self.recordType_users]
-        elif context == self.searchContext_group:
-            recordTypes = [self.recordType_groups]
-        elif context == self.searchContext_attendee:
-            recordTypes = [self.recordType_users, self.recordType_groups,
-                self.recordType_resources]
-        else:
-            recordTypes = list(self.recordTypes())
-        return recordTypes
-
-
-    def recordsMatchingTokens(self, tokens, context=None):
-        &quot;&quot;&quot;
-        @param tokens: The tokens to search on
-        @type tokens: C{list} of C{str} (utf-8 bytes)
-        @param context: An indication of what the end user is searching
-            for; &quot;attendee&quot;, &quot;location&quot;, or None
-        @type context: C{str}
-        @return: a deferred sequence of L{IDirectoryRecord}s which
-            match the given tokens and optional context.
-
-        Each token is searched for within each record's full name and
-        email address; if each token is found within a record that
-        record is returned in the results.
-
-        If context is None, all record types are considered.  If
-        context is &quot;location&quot;, only locations are considered.  If
-        context is &quot;attendee&quot;, only users, groups, and resources
-        are considered.
-        &quot;&quot;&quot;
-
-        # Default, bruteforce method; override with one optimized for each
-        # service
-
-        def fieldMatches(fieldValue, value):
-            if fieldValue is None:
-                return False
-            elif type(fieldValue) in types.StringTypes:
-                fieldValue = (fieldValue,)
-
-            for testValue in fieldValue:
-                testValue = testValue.lower()
-                value = value.lower()
-
-                try:
-                    testValue.index(value)
-                    return True
-                except ValueError:
-                    pass
-
-            return False
-
-        def recordMatches(record):
-            for token in tokens:
-                for fieldName in [&quot;fullName&quot;, &quot;emailAddresses&quot;]:
-                    try:
-                        fieldValue = getattr(record, fieldName)
-                        if fieldMatches(fieldValue, token):
-                            break
-                    except AttributeError:
-                        # No value
-                        pass
-                else:
-                    return False
-            return True
-
-
-        def yieldMatches(recordTypes):
-            try:
-                for recordType in [r for r in recordTypes if r in self.recordTypes()]:
-                    for record in self.listRecords(recordType):
-                        if recordMatches(record):
-                            yield record
-
-            except UnknownRecordTypeError:
-                # Skip this service since it doesn't understand this record type
-                pass
-
-        recordTypes = self.recordTypesForSearchContext(context)
-        return succeed(yieldMatches(recordTypes))
-
-
-    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None):
-        # Default, bruteforce method; override with one optimized for each
-        # service
-
-        def fieldMatches(fieldValue, value, caseless, matchType):
-            if fieldValue is None:
-                return False
-            elif type(fieldValue) in types.StringTypes:
-                fieldValue = (fieldValue,)
-
-            for testValue in fieldValue:
-                if caseless:
-                    testValue = testValue.lower()
-                    value = value.lower()
-
-                if matchType == 'starts-with':
-                    if testValue.startswith(value):
-                        return True
-                elif matchType == 'contains':
-                    try:
-                        testValue.index(value)
-                        return True
-                    except ValueError:
-                        pass
-                else: # exact
-                    if testValue == value:
-                        return True
-
-            return False
-
-        def recordMatches(record):
-            if operand == &quot;and&quot;:
-                for fieldName, value, caseless, matchType in fields:
-                    try:
-                        fieldValue = getattr(record, fieldName)
-                        if not fieldMatches(fieldValue, value, caseless,
-                            matchType):
-                            return False
-                    except AttributeError:
-                        # No property =&gt; no match
-                        return False
-                # we hit on every property
-                return True
-            else: # &quot;or&quot;
-                for fieldName, value, caseless, matchType in fields:
-                    try:
-                        fieldValue = getattr(record, fieldName)
-                        if fieldMatches(fieldValue, value, caseless,
-                            matchType):
-                            return True
-                    except AttributeError:
-                        # No value
-                        pass
-                # we didn't hit any
-                return False
-
-        def yieldMatches(recordType):
-            try:
-                if recordType is None:
-                    recordTypes = list(self.recordTypes())
-                else:
-                    recordTypes = (recordType,)
-
-                for recordType in recordTypes:
-                    for record in self.listRecords(recordType):
-                        if recordMatches(record):
-                            yield record
-
-            except UnknownRecordTypeError:
-                # Skip this service since it doesn't understand this record type
-                pass
-
-        return succeed(yieldMatches(recordType))
-
-
-    def getGroups(self, guids):
-        &quot;&quot;&quot;
-        This implementation returns all groups, not just the ones specified
-        by guids
-        &quot;&quot;&quot;
-        return succeed(self.listRecords(self.recordType_groups))
-
-
-    def getResourceInfo(self):
-        return ()
-
-
-    def isAvailable(self):
-        return True
-
-
-    def getParams(self, params, defaults, ignore=None):
-        &quot;&quot;&quot; Checks configuration parameters for unexpected/ignored keys, and
-            applies default values. &quot;&quot;&quot;
-
-        keys = set(params.keys())
-
-        result = {}
-        for key in defaults.iterkeys():
-            if key in params:
-                result[key] = params[key]
-                keys.remove(key)
-            else:
-                result[key] = defaults[key]
-
-        if ignore:
-            for key in ignore:
-                if key in params:
-                    self.log.warn(&quot;Ignoring obsolete directory service parameter: %s&quot; % (key,))
-                    keys.remove(key)
-
-        if keys:
-            raise DirectoryConfigurationError(&quot;Invalid directory service parameter(s): %s&quot; % (&quot;, &quot;.join(list(keys)),))
-        return result
-
-
-    def parseResourceInfo(self, plist, guid, recordType, shortname):
-        &quot;&quot;&quot;
-        Parse ResourceInfo plist and extract information that the server needs.
-
-        @param plist: the plist that is the attribute value.
-        @type plist: str
-        @param guid: the directory GUID of the record being parsed.
-        @type guid: str
-        @param shortname: the record shortname of the record being parsed.
-        @type shortname: str
-        @return: a C{tuple} of C{bool} for auto-accept, C{str} for proxy GUID, C{str} for read-only proxy GUID.
-        &quot;&quot;&quot;
-        try:
-            plist = readPlistFromString(plist)
-            wpframework = plist.get(&quot;com.apple.WhitePagesFramework&quot;, {})
-            autoaccept = wpframework.get(&quot;AutoAcceptsInvitation&quot;, False)
-            proxy = wpframework.get(&quot;CalendaringDelegate&quot;, None)
-            read_only_proxy = wpframework.get(&quot;ReadOnlyCalendaringDelegate&quot;, None)
-            autoAcceptGroup = wpframework.get(&quot;AutoAcceptGroup&quot;, &quot;&quot;)
-        except (ExpatError, AttributeError), e:
-            self.log.error(
-                &quot;Failed to parse ResourceInfo attribute of record (%s)%s (guid=%s): %s\n%s&quot; %
-                (recordType, shortname, guid, e, plist,)
-            )
-            raise ValueError(&quot;Invalid ResourceInfo&quot;)
-
-        return (autoaccept, proxy, read_only_proxy, autoAcceptGroup)
-
-
-    def getExternalProxyAssignments(self):
-        &quot;&quot;&quot;
-        Retrieve proxy assignments for locations and resources from the
-        directory and return a list of (principalUID, ([memberUIDs)) tuples,
-        suitable for passing to proxyDB.setGroupMembers( )
-
-        This generic implementation fetches all locations and resources.
-        More specialized implementations can perform whatever operation is
-        most efficient for their particular directory service.
-        &quot;&quot;&quot;
-        assignments = []
-
-        resources = itertools.chain(
-            self.listRecords(self.recordType_locations),
-            self.listRecords(self.recordType_resources)
-        )
-        for record in resources:
-            guid = record.guid
-            if record.hasCalendars:
-                assignments.append((&quot;%s#calendar-proxy-write&quot; % (guid,),
-                                   record.externalProxies()))
-                assignments.append((&quot;%s#calendar-proxy-read&quot; % (guid,),
-                                   record.externalReadOnlyProxies()))
-
-        return assignments
-
-
-    def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
-        fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwargs):
-        &quot;&quot;&quot;
-        Create/persist a directory record based on the given values
-        &quot;&quot;&quot;
-        raise NotImplementedError(&quot;Subclass must implement createRecord&quot;)
-
-
-    def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
-        fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-        uid=None, password=None, **kwargs):
-        &quot;&quot;&quot;
-        Update/persist a directory record based on the given values
-        &quot;&quot;&quot;
-        raise NotImplementedError(&quot;Subclass must implement updateRecord&quot;)
-
-
-    def destroyRecord(self, recordType, guid=None):
-        &quot;&quot;&quot;
-        Remove a directory record from the directory
-        &quot;&quot;&quot;
-        raise NotImplementedError(&quot;Subclass must implement destroyRecord&quot;)
-
-
-    def createRecords(self, data):
-        &quot;&quot;&quot;
-        Create directory records in bulk
-        &quot;&quot;&quot;
-        raise NotImplementedError(&quot;Subclass must implement createRecords&quot;)
-
-
-    def setPrincipalCollection(self, principalCollection):
-        &quot;&quot;&quot;
-        Set the principal service that the directory relies on for doing proxy tests.
-
-        @param principalService: the principal service.
-        @type principalService: L{DirectoryProvisioningResource}
-        &quot;&quot;&quot;
-        self.principalCollection = principalCollection
-
-
-    def isProxyFor(self, test, other):
-        &quot;&quot;&quot;
-        Test whether one record is a calendar user proxy for the specified record.
-
-        @param test: record to test
-        @type test: L{DirectoryRecord}
-        @param other: record to check against
-        @type other: L{DirectoryRecord}
-
-        @return: C{True} if test is a proxy of other.
-        @rtype: C{bool}
-        &quot;&quot;&quot;
-        return self.principalCollection.isProxyFor(test, other)
-
-
-
-class GroupMembershipCache(Memcacher):
-    &quot;&quot;&quot;
-    Caches group membership information
-
-    This cache is periodically updated by a side car so that worker processes
-    never have to ask the directory service directly for group membership
-    information.
-
-    Keys in this cache are:
-
-    &quot;groups-for:&lt;GUID&gt;&quot; : comma-separated list of groups that GUID is a member
-    of.  Note that when using LDAP, the key for this is an LDAP DN.
-
-    &quot;group-cacher-populated&quot; : contains a datestamp indicating the most recent
-    population.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    def __init__(self, namespace, pickle=True, no_invalidation=False,
-        key_normalization=True, expireSeconds=0, lockSeconds=60):
-
-        super(GroupMembershipCache, self).__init__(namespace, pickle=pickle,
-            no_invalidation=no_invalidation,
-            key_normalization=key_normalization)
-
-        self.expireSeconds = expireSeconds
-        self.lockSeconds = lockSeconds
-
-
-    def setGroupsFor(self, guid, memberships):
-        self.log.debug(&quot;set groups-for %s : %s&quot; % (guid, memberships))
-        return self.set(&quot;groups-for:%s&quot; %
-            (str(guid)), memberships,
-            expireTime=self.expireSeconds)
-
-
-    def getGroupsFor(self, guid):
-        self.log.debug(&quot;get groups-for %s&quot; % (guid,))
-        def _value(value):
-            if value:
-                return value
-            else:
-                return set()
-        d = self.get(&quot;groups-for:%s&quot; % (str(guid),))
-        d.addCallback(_value)
-        return d
-
-
-    def deleteGroupsFor(self, guid):
-        self.log.debug(&quot;delete groups-for %s&quot; % (guid,))
-        return self.delete(&quot;groups-for:%s&quot; % (str(guid),))
-
-
-    def setPopulatedMarker(self):
-        self.log.debug(&quot;set group-cacher-populated&quot;)
-        return self.set(&quot;group-cacher-populated&quot;, str(datetime.datetime.now()))
-
-
-    @inlineCallbacks
-    def isPopulated(self):
-        self.log.debug(&quot;is group-cacher-populated&quot;)
-        value = (yield self.get(&quot;group-cacher-populated&quot;))
-        returnValue(value is not None)
-
-
-    def acquireLock(self):
-        &quot;&quot;&quot;
-        Acquire a memcached lock named group-cacher-lock
-
-        return: Deferred firing True if successful, False if someone already has
-            the lock
-        &quot;&quot;&quot;
-        self.log.debug(&quot;add group-cacher-lock&quot;)
-        return self.add(&quot;group-cacher-lock&quot;, &quot;1&quot;, expireTime=self.lockSeconds)
-
-
-    def extendLock(self):
-        &quot;&quot;&quot;
-        Update the expiration time of the memcached lock
-        Return: Deferred firing True if successful, False otherwise
-        &quot;&quot;&quot;
-        self.log.debug(&quot;extend group-cacher-lock&quot;)
-        return self.set(&quot;group-cacher-lock&quot;, &quot;1&quot;, expireTime=self.lockSeconds)
-
-
-    def releaseLock(self):
-        &quot;&quot;&quot;
-        Release the memcached lock
-        Return: Deferred firing True if successful, False otherwise
-        &quot;&quot;&quot;
-        self.log.debug(&quot;delete group-cacher-lock&quot;)
-        return self.delete(&quot;group-cacher-lock&quot;)
-
-
-
-class GroupMembershipCacheUpdater(object):
-    &quot;&quot;&quot;
-    Responsible for updating memcached with group memberships.  This will run
-    in a sidecar.  There are two sources of proxy data to pull from: the local
-    proxy database, and the location/resource info in the directory system.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    def __init__(self, proxyDB, directory, updateSeconds, expireSeconds,
-        lockSeconds, cache=None, namespace=None, useExternalProxies=False,
-        externalProxiesSource=None):
-        self.proxyDB = proxyDB
-        self.directory = directory
-        self.updateSeconds = updateSeconds
-        self.useExternalProxies = useExternalProxies
-        if useExternalProxies and externalProxiesSource is None:
-            externalProxiesSource = self.directory.getExternalProxyAssignments
-        self.externalProxiesSource = externalProxiesSource
-
-        if cache is None:
-            assert namespace is not None, &quot;namespace must be specified if GroupMembershipCache is not provided&quot;
-            cache = GroupMembershipCache(namespace, expireSeconds=expireSeconds,
-                lockSeconds=lockSeconds)
-        self.cache = cache
-
-
-    @inlineCallbacks
-    def getGroups(self, guids=None):
-        &quot;&quot;&quot;
-        Retrieve all groups and their member info (but don't actually fault in
-        the records of the members), and return two dictionaries.  The first
-        contains group records; the keys for this dictionary are the identifiers
-        used by the directory service to specify members.  In OpenDirectory
-        these would be guids, but in LDAP these could be DNs, or some other
-        attribute.  This attribute can be retrieved from a record using
-        record.cachedGroupsAlias().
-        The second dictionary returned maps that member attribute back to the
-        corresponding guid.  These dictionaries are used to reverse-index the
-        groups that users are in by expandedMembers().
-
-        @param guids: if provided, retrieve only the groups corresponding to
-            these guids (including their sub groups)
-        @type guids: list of guid strings
-        &quot;&quot;&quot;
-        groups = {}
-        aliases = {}
-
-        if guids is None: # get all group guids
-            records = self.directory.listRecords(self.directory.recordType_groups)
-        else: # get only the ones we know have been delegated to
-            records = (yield self.directory.getGroups(guids))
-
-        for record in records:
-            alias = record.cachedGroupsAlias()
-            groups[alias] = record.memberGUIDs()
-            aliases[record.guid] = alias
-
-        returnValue((groups, aliases))
-
-
-    def expandedMembers(self, groups, guid, members=None, seen=None):
-        &quot;&quot;&quot;
-        Return the complete, flattened set of members of a group, including
-        all sub-groups, based on the group hierarchy described in the
-        groups dictionary.
-        &quot;&quot;&quot;
-        if members is None:
-            members = set()
-        if seen is None:
-            seen = set()
-
-        if guid not in seen:
-            seen.add(guid)
-            for member in groups[guid]:
-                members.add(member)
-                if member in groups: # it's a group then
-                    self.expandedMembers(groups, member, members=members,
-                                         seen=seen)
-        return members
-
-
-    @inlineCallbacks
-    def updateCache(self, fast=False):
-        &quot;&quot;&quot;
-        Iterate the proxy database to retrieve all the principals who have been
-        delegated to.  Fault these principals in.  For any of these principals
-        that are groups, expand the members of that group and store those in
-        the cache
-
-        If fast=True, we're in quick-start mode, used only by the master process
-        to start servicing requests as soon as possible.  In this mode we look
-        for DataRoot/memberships_cache which is a pickle of a dictionary whose
-        keys are guids (except when using LDAP where the keys will be DNs), and
-        the values are lists of group guids.  If the cache file does not exist
-        we switch to fast=False.
-
-        The return value is mainly used for unit tests; it's a tuple containing
-        the (possibly modified) value for fast, and the number of members loaded
-        into the cache (which can be zero if fast=True and isPopulated(), or
-        fast=False and the cache is locked by someone else).
-
-        The pickled snapshot file is a dict whose keys represent a record and
-        the values are the guids of the groups that record is a member of.  The
-        keys are normally guids except in the case of a directory system like LDAP
-        where there can be a different attribute used for referring to members,
-        such as a DN.
-        &quot;&quot;&quot;
-
-        # TODO: add memcached eviction protection
-
-        useLock = True
-
-        # See if anyone has completely populated the group membership cache
-        isPopulated = (yield self.cache.isPopulated())
-
-        if fast:
-            # We're in quick-start mode.  Check first to see if someone has
-            # populated the membership cache, and if so, return immediately
-            if isPopulated:
-                self.log.info(&quot;Group membership cache is already populated&quot;)
-                returnValue((fast, 0, 0))
-
-            # We don't care what others are doing right now, we need to update
-            useLock = False
-
-        self.log.info(&quot;Updating group membership cache&quot;)
-
-        dataRoot = FilePath(config.DataRoot)
-        membershipsCacheFile = dataRoot.child(&quot;memberships_cache&quot;)
-        extProxyCacheFile = dataRoot.child(&quot;external_proxy_cache&quot;)
-
-        if not membershipsCacheFile.exists():
-            self.log.info(&quot;Group membership snapshot file does not yet exist&quot;)
-            fast = False
-            previousMembers = {}
-            callGroupsChanged = False
-        else:
-            self.log.info(&quot;Group membership snapshot file exists: %s&quot; %
-                (membershipsCacheFile.path,))
-            callGroupsChanged = True
-            try:
-                previousMembers = pickle.loads(membershipsCacheFile.getContent())
-            except:
-                self.log.warn(&quot;Could not parse snapshot; will regenerate cache&quot;)
-                fast = False
-                previousMembers = {}
-                callGroupsChanged = False
-
-        if useLock:
-            self.log.info(&quot;Attempting to acquire group membership cache lock&quot;)
-            acquiredLock = (yield self.cache.acquireLock())
-            if not acquiredLock:
-                self.log.info(&quot;Group membership cache lock held by another process&quot;)
-                returnValue((fast, 0, 0))
-            self.log.info(&quot;Acquired lock&quot;)
-
-        if not fast and self.useExternalProxies:
-
-            # Load in cached copy of external proxies so we can diff against them
-            previousAssignments = []
-            if extProxyCacheFile.exists():
-                self.log.info(&quot;External proxies snapshot file exists: %s&quot; %
-                    (extProxyCacheFile.path,))
-                try:
-                    previousAssignments = pickle.loads(extProxyCacheFile.getContent())
-                except:
-                    self.log.warn(&quot;Could not parse external proxies snapshot&quot;)
-                    previousAssignments = []
-
-            if useLock:
-                yield self.cache.extendLock()
-
-            self.log.info(&quot;Retrieving proxy assignments from directory&quot;)
-            assignments = self.externalProxiesSource()
-            self.log.info(&quot;%d proxy assignments retrieved from directory&quot; %
-                (len(assignments),))
-
-            if useLock:
-                yield self.cache.extendLock()
-
-            changed, removed = diffAssignments(previousAssignments, assignments)
-            # changed is the list of proxy assignments (either new or updates).
-            # removed is the list of principals who used to have an external
-            #   delegate but don't anymore.
-
-            # populate proxy DB from external resource info
-            if changed:
-                self.log.info(&quot;Updating proxy assignments&quot;)
-                assignmentCount = 0
-                totalNumAssignments = len(changed)
-                currentAssignmentNum = 0
-                for principalUID, members in changed:
-                    currentAssignmentNum += 1
-                    if currentAssignmentNum % 1000 == 0:
-                        self.log.info(&quot;...proxy assignment %d of %d&quot; % (currentAssignmentNum,
-                            totalNumAssignments))
-                    try:
-                        current = (yield self.proxyDB.getMembers(principalUID))
-                        if members != current:
-                            assignmentCount += 1
-                            yield self.proxyDB.setGroupMembers(principalUID, members)
-                    except Exception, e:
-                        self.log.error(&quot;Unable to update proxy assignment: principal=%s, members=%s, error=%s&quot; % (principalUID, members, e))
-                self.log.info(&quot;Updated %d assignment%s in proxy database&quot; %
-                    (assignmentCount, &quot;&quot; if assignmentCount == 1 else &quot;s&quot;))
-
-            if removed:
-                self.log.info(&quot;Deleting proxy assignments&quot;)
-                assignmentCount = 0
-                totalNumAssignments = len(removed)
-                currentAssignmentNum = 0
-                for principalUID in removed:
-                    currentAssignmentNum += 1
-                    if currentAssignmentNum % 1000 == 0:
-                        self.log.info(&quot;...proxy assignment %d of %d&quot; % (currentAssignmentNum,
-                            totalNumAssignments))
-                    try:
-                        assignmentCount += 1
-                        yield self.proxyDB.setGroupMembers(principalUID, [])
-                    except Exception, e:
-                        self.log.error(&quot;Unable to remove proxy assignment: principal=%s, members=%s, error=%s&quot; % (principalUID, members, e))
-                self.log.info(&quot;Removed %d assignment%s from proxy database&quot; %
-                    (assignmentCount, &quot;&quot; if assignmentCount == 1 else &quot;s&quot;))
-
-            # Store external proxy snapshot
-            self.log.info(&quot;Taking snapshot of external proxies to %s&quot; %
-                (extProxyCacheFile.path,))
-            extProxyCacheFile.setContent(pickle.dumps(assignments))
-
-        if fast:
-            # If there is an on-disk snapshot of the membership information,
-            # load that and put into memcached, bypassing the faulting in of
-            # any records, so that the server can start up quickly.
-
-            self.log.info(&quot;Loading group memberships from snapshot&quot;)
-            members = pickle.loads(membershipsCacheFile.getContent())
-
-        else:
-            # Fetch the group hierarchy from the directory, fetch the list
-            # of delegated-to guids, intersect those and build a dictionary
-            # containing which delegated-to groups a user is a member of
-
-            self.log.info(&quot;Retrieving list of all proxies&quot;)
-            # This is always a set of guids:
-            # MOVE2WHO
-            delegatedGUIDs = set() # set((yield self.proxyDB.getAllMembers()))
-            self.log.info(&quot;There are %d proxies&quot; % (len(delegatedGUIDs),))
-            self.log.info(&quot;Retrieving group hierarchy from directory&quot;)
-
-            # &quot;groups&quot; maps a group to its members; the keys and values consist
-            # of whatever directory attribute is used to refer to members.  The
-            # attribute value comes from record.cachedGroupsAlias().
-            # &quot;aliases&quot; maps the record.cachedGroupsAlias() value for a group
-            # back to the group's guid.
-            groups, aliases = (yield self.getGroups(guids=delegatedGUIDs))
-            groupGUIDs = set(aliases.keys())
-            self.log.info(&quot;%d groups retrieved from the directory&quot; %
-                (len(groupGUIDs),))
-
-            delegatedGUIDs = delegatedGUIDs.intersection(groupGUIDs)
-            self.log.info(&quot;%d groups are proxies&quot; % (len(delegatedGUIDs),))
-
-            # Reverse index the group membership from cache
-            members = {}
-            for groupGUID in delegatedGUIDs:
-                groupMembers = self.expandedMembers(groups, aliases[groupGUID])
-                # groupMembers is in cachedGroupsAlias() format
-                for member in groupMembers:
-                    memberships = members.setdefault(member, set())
-                    memberships.add(groupGUID)
-
-            self.log.info(&quot;There are %d users delegated-to via groups&quot; %
-                (len(members),))
-
-            # Store snapshot
-            self.log.info(&quot;Taking snapshot of group memberships to %s&quot; %
-                (membershipsCacheFile.path,))
-            membershipsCacheFile.setContent(pickle.dumps(members))
-
-            # Update ownership
-            uid = gid = -1
-            if config.UserName:
-                uid = pwd.getpwnam(config.UserName).pw_uid
-            if config.GroupName:
-                gid = grp.getgrnam(config.GroupName).gr_gid
-            os.chown(membershipsCacheFile.path, uid, gid)
-            if extProxyCacheFile.exists():
-                os.chown(extProxyCacheFile.path, uid, gid)
-
-        self.log.info(&quot;Storing %d group memberships in memcached&quot; %
-                       (len(members),))
-        changedMembers = set()
-        totalNumMembers = len(members)
-        currentMemberNum = 0
-        for member, groups in members.iteritems():
-            currentMemberNum += 1
-            if currentMemberNum % 1000 == 0:
-                self.log.info(&quot;...membership %d of %d&quot; % (currentMemberNum,
-                    totalNumMembers))
-            # self.log.debug(&quot;%s is in %s&quot; % (member, groups))
-            yield self.cache.setGroupsFor(member, groups)
-            if groups != previousMembers.get(member, None):
-                # This principal has had a change in group membership
-                # so invalidate the PROPFIND response cache
-                changedMembers.add(member)
-            try:
-                # Remove from previousMembers; anything still left in
-                # previousMembers when this loop is done will be
-                # deleted from cache (since only members that were
-                # previously in delegated-to groups but are no longer
-                # would still be in previousMembers)
-                del previousMembers[member]
-            except KeyError:
-                pass
-
-        # Remove entries for principals that no longer are in delegated-to
-        # groups
-        for member, groups in previousMembers.iteritems():
-            yield self.cache.deleteGroupsFor(member)
-            changedMembers.add(member)
-
-        # For principals whose group membership has changed, call groupsChanged()
-        if callGroupsChanged and not fast and hasattr(self.directory, &quot;principalCollection&quot;):
-            for member in changedMembers:
-                record = yield self.directory.recordWithCachedGroupsAlias(
-                    self.directory.recordType_users, member)
-                if record is not None:
-                    principal = self.directory.principalCollection.principalForRecord(record)
-                    if principal is not None:
-                        self.log.debug(&quot;Group membership changed for %s (%s)&quot; %
-                            (record.shortNames[0], record.guid,))
-                        if hasattr(principal, &quot;groupsChanged&quot;):
-                            yield principal.groupsChanged()
-
-        yield self.cache.setPopulatedMarker()
-
-        if useLock:
-            self.log.info(&quot;Releasing lock&quot;)
-            yield self.cache.releaseLock()
-
-        self.log.info(&quot;Group memberships cache updated&quot;)
-
-        returnValue((fast, len(members), len(changedMembers)))
-
-
-
-def diffAssignments(old, new):
-    &quot;&quot;&quot;
-    Compare two proxy assignment lists and return their differences in the form of
-    two lists -- one for added/updated assignments, and one for removed assignments.
-    @param old: list of (group, set(members)) tuples
-    @type old: C{list}
-    @param new: list of (group, set(members)) tuples
-    @type new: C{list}
-    @return: Tuple of two lists; the first list contains tuples of (proxy-principal,
-        set(members)), and represents all the new or updated assignments.  The
-        second list contains all the proxy-principals which used to have a delegate
-        but don't anymore.
-    &quot;&quot;&quot;
-    old = dict(old)
-    new = dict(new)
-    changed = []
-    removed = []
-    for key in old.iterkeys():
-        if key not in new:
-            removed.append(key)
-        else:
-            if old[key] != new[key]:
-                changed.append((key, new[key]))
-    for key in new.iterkeys():
-        if key not in old:
-            changed.append((key, new[key]))
-    return changed, removed
-
-
-
-class DirectoryRecord(object):
-    log = Logger()
-
-    implements(IDirectoryRecord, ICalendarStoreDirectoryRecord)
-
-    def __repr__(self):
-        return &quot;&lt;%s[%s@%s(%s)] %s(%s) %r @ %s&gt;&quot; % (
-            self.__class__.__name__,
-            self.recordType,
-            self.service.guid,
-            self.service.realmName,
-            self.guid,
-            &quot;,&quot;.join(self.shortNames),
-            self.fullName,
-            self.serverURI(),
-        )
-
-
-    def __init__(
-        self, service, recordType, guid=None,
-        shortNames=(), authIDs=set(), fullName=None,
-        firstName=None, lastName=None, emailAddresses=set(),
-        calendarUserAddresses=set(),
-        autoSchedule=False, autoScheduleMode=None,
-        autoAcceptGroup=&quot;&quot;,
-        enabledForCalendaring=None,
-        enabledForAddressBooks=None,
-        uid=None,
-        enabledForLogin=True,
-        extProxies=(), extReadOnlyProxies=(),
-        **kwargs
-    ):
-        assert service.realmName is not None
-        assert recordType
-        assert shortNames and isinstance(shortNames, tuple)
-
-        guid = normalizeUUID(guid)
-
-        if uid is None:
-            uid = guid
-
-        if fullName is None:
-            fullName = &quot;&quot;
-
-        self.service = service
-        self.recordType = recordType
-        self.guid = guid
-        self.uid = uid
-        self.enabled = False
-        self.serverID = &quot;&quot;
-        self.shortNames = shortNames
-        self.authIDs = authIDs
-        self.fullName = fullName
-        self.firstName = firstName
-        self.lastName = lastName
-        self.emailAddresses = emailAddresses
-        self.enabledForCalendaring = enabledForCalendaring
-        self.autoSchedule = autoSchedule
-        self.autoScheduleMode = autoScheduleMode
-        self.autoAcceptGroup = autoAcceptGroup
-        self.enabledForAddressBooks = enabledForAddressBooks
-        self.enabledForLogin = enabledForLogin
-        self.extProxies = extProxies
-        self.extReadOnlyProxies = extReadOnlyProxies
-        self.extras = kwargs
-
-
-    def get_calendarUserAddresses(self):
-        &quot;&quot;&quot;
-        Dynamically construct a calendarUserAddresses attribute which describes
-        this L{DirectoryRecord}.
-
-        @see: L{IDirectoryRecord.calendarUserAddresses}.
-        &quot;&quot;&quot;
-        if not self.enabledForCalendaring:
-            return frozenset()
-        cuas = set(
-            [&quot;mailto:%s&quot; % (emailAddress,)
-             for emailAddress in self.emailAddresses]
-        )
-        if self.guid:
-            cuas.add(&quot;urn:uuid:%s&quot; % (self.guid,))
-            cuas.add(joinURL(&quot;/principals&quot;, &quot;__uids__&quot;, self.guid) + &quot;/&quot;)
-        for shortName in self.shortNames:
-            cuas.add(joinURL(&quot;/principals&quot;, self.recordType, shortName,) + &quot;/&quot;)
-
-        return frozenset(cuas)
-
-    calendarUserAddresses = property(get_calendarUserAddresses)
-
-    def __cmp__(self, other):
-        if not isinstance(other, DirectoryRecord):
-            return NotImplemented
-
-        for attr in (&quot;service&quot;, &quot;recordType&quot;, &quot;shortNames&quot;, &quot;guid&quot;):
-            diff = cmp(getattr(self, attr), getattr(other, attr))
-            if diff != 0:
-                return diff
-        return 0
-
-
-    def __hash__(self):
-        h = hash(self.__class__.__name__)
-        for attr in (&quot;service&quot;, &quot;recordType&quot;, &quot;shortNames&quot;, &quot;guid&quot;,
-                     &quot;enabled&quot;, &quot;enabledForCalendaring&quot;):
-            h = (h + hash(getattr(self, attr))) &amp; sys.maxint
-
-        return h
-
-
-    def cacheToken(self):
-        &quot;&quot;&quot;
-        Generate a token that can be uniquely used to identify the state of this record for use
-        in a cache.
-        &quot;&quot;&quot;
-        return hash((
-            self.__class__.__name__,
-            self.service.realmName,
-            self.recordType,
-            self.shortNames,
-            self.guid,
-            self.enabled,
-            self.enabledForCalendaring,
-        ))
-
-
-    def addAugmentInformation(self, augment):
-
-        if augment:
-            self.enabled = augment.enabled
-            self.serverID = augment.serverID
-            self.enabledForCalendaring = augment.enabledForCalendaring
-            self.enabledForAddressBooks = augment.enabledForAddressBooks
-            self.autoSchedule = augment.autoSchedule
-            self.autoScheduleMode = augment.autoScheduleMode
-            self.autoAcceptGroup = augment.autoAcceptGroup
-            self.enabledForLogin = augment.enabledForLogin
-
-            if (self.enabledForCalendaring or self.enabledForAddressBooks) and self.recordType == self.service.recordType_groups:
-                self.enabledForCalendaring = False
-                self.enabledForAddressBooks = False
-
-                # For augment records cloned from the Default augment record,
-                # don't emit this message:
-                if not augment.clonedFromDefault:
-                    self.log.error(&quot;Group '%s(%s)' cannot be enabled for calendaring or address books&quot; % (self.guid, self.shortNames[0],))
-
-        else:
-            # Groups are by default always enabled
-            self.enabled = (self.recordType == self.service.recordType_groups)
-            self.serverID = &quot;&quot;
-            self.enabledForCalendaring = False
-            self.enabledForAddressBooks = False
-            self.enabledForLogin = False
-
-
-    def applySACLs(self):
-        &quot;&quot;&quot;
-        Disable calendaring and addressbooks as dictated by SACLs
-        &quot;&quot;&quot;
-
-        if config.EnableSACLs and self.CheckSACL:
-            username = self.shortNames[0]
-            if self.CheckSACL(username, &quot;calendar&quot;) != 0:
-                self.log.debug(&quot;%s is not enabled for calendaring due to SACL&quot;
-                               % (username,))
-                self.enabledForCalendaring = False
-            if self.CheckSACL(username, &quot;addressbook&quot;) != 0:
-                self.log.debug(&quot;%s is not enabled for addressbooks due to SACL&quot;
-                               % (username,))
-                self.enabledForAddressBooks = False
-
-
-    def displayName(self):
-        return self.fullName if self.fullName else self.shortNames[0]
-
-
-    def isLoginEnabled(self):
-        &quot;&quot;&quot;
-        Returns True if the user should be allowed to log in, based on the
-        enabledForLogin attribute, which is currently controlled by the
-        DirectoryService implementation.
-        &quot;&quot;&quot;
-        return self.enabledForLogin
-
-
-    def members(self):
-        return ()
-
-
-    def expandedMembers(self, members=None, seen=None):
-        &quot;&quot;&quot;
-        Return the complete, flattened set of members of a group, including
-        all sub-groups.
-        &quot;&quot;&quot;
-        if members is None:
-            members = set()
-        if seen is None:
-            seen = set()
-
-        if self not in seen:
-            seen.add(self)
-            for member in self.members():
-                members.add(member)
-                if member.recordType == self.service.recordType_groups:
-                    member.expandedMembers(members=members, seen=seen)
-
-        return members
-
-
-    def groups(self):
-        return ()
-
-
-    def cachedGroups(self):
-        &quot;&quot;&quot;
-        Return the set of groups (guids) this record is a member of, based on
-        the data cached by cacheGroupMembership( )
-        &quot;&quot;&quot;
-        return self.service.groupMembershipCache.getGroupsFor(self.cachedGroupsAlias())
-
-
-    def cachedGroupsAlias(self):
-        &quot;&quot;&quot;
-        The GroupMembershipCache uses keys based on this value.  Normally it's
-        a record's guid but in a directory system like LDAP which can use a
-        different attribute to refer to group members, we need to be able to
-        look up an entry in the GroupMembershipCache by that attribute.
-        Subclasses which don't use record.guid to look up group membership
-        should override this method.
-        &quot;&quot;&quot;
-        return self.guid
-
-
-    def externalProxies(self):
-        &quot;&quot;&quot;
-        Return the set of proxies defined in the directory service, as opposed
-        to assignments in the proxy DB itself.
-        &quot;&quot;&quot;
-        return set(self.extProxies)
-
-
-    def externalReadOnlyProxies(self):
-        &quot;&quot;&quot;
-        Return the set of read-only proxies defined in the directory service,
-        as opposed to assignments in the proxy DB itself.
-        &quot;&quot;&quot;
-        return set(self.extReadOnlyProxies)
-
-
-    def memberGUIDs(self):
-        &quot;&quot;&quot;
-        Return the set of GUIDs that are members of this group
-        &quot;&quot;&quot;
-        return set()
-
-
-    def verifyCredentials(self, credentials):
-        return False
-
-
-    def calendarsEnabled(self):
-        return config.EnableCalDAV and self.enabledForCalendaring
-
-
-    def canonicalCalendarUserAddress(self):
-        &quot;&quot;&quot;
-            Return a CUA for this principal, preferring in this order:
-            urn:uuid: form
-            mailto: form
-            first in calendarUserAddresses list
-        &quot;&quot;&quot;
-
-        cua = &quot;&quot;
-        for candidate in self.calendarUserAddresses:
-            # Pick the first one, but urn:uuid: and mailto: can override
-            if not cua:
-                cua = candidate
-            # But always immediately choose the urn:uuid: form
-            if candidate.startswith(&quot;urn:uuid:&quot;):
-                cua = candidate
-                break
-            # Prefer mailto: if no urn:uuid:
-            elif candidate.startswith(&quot;mailto:&quot;):
-                cua = candidate
-        return cua
-
-
-    def enabledAsOrganizer(self):
-        if self.recordType == DirectoryService.recordType_users:
-            return True
-        elif self.recordType == DirectoryService.recordType_groups:
-            return config.Scheduling.Options.AllowGroupAsOrganizer
-        elif self.recordType == DirectoryService.recordType_locations:
-            return config.Scheduling.Options.AllowLocationAsOrganizer
-        elif self.recordType == DirectoryService.recordType_resources:
-            return config.Scheduling.Options.AllowResourceAsOrganizer
-        else:
-            return False
-
-    # Mapping from directory record.recordType to RFC2445 CUTYPE values
-    _cuTypes = {
-        'users' : 'INDIVIDUAL',
-        'groups' : 'GROUP',
-        'resources' : 'RESOURCE',
-        'locations' : 'ROOM',
-    }
-
-    def getCUType(self):
-        return self._cuTypes.get(self.recordType, &quot;UNKNOWN&quot;)
-
-
-    @classmethod
-    def fromCUType(cls, cuType):
-        for key, val in cls._cuTypes.iteritems():
-            if val == cuType:
-                return key
-        return None
-
-
-    def canAutoSchedule(self, organizer):
-        if config.Scheduling.Options.AutoSchedule.Enabled:
-            if (config.Scheduling.Options.AutoSchedule.Always or
-                self.autoSchedule or
-                self.autoAcceptFromOrganizer(organizer)):
-                if (self.getCUType() != &quot;INDIVIDUAL&quot; or
-                    config.Scheduling.Options.AutoSchedule.AllowUsers):
-                    return True
-        return False
-
-
-    def getAutoScheduleMode(self, organizer):
-        autoScheduleMode = self.autoScheduleMode
-        if self.autoAcceptFromOrganizer(organizer):
-            autoScheduleMode = &quot;automatic&quot;
-        return autoScheduleMode
-
-
-    def autoAcceptFromOrganizer(self, organizer):
-        if organizer is not None and self.autoAcceptGroup is not None:
-            service = self.service.aggregateService or self.service
-            organizerRecord = service.recordWithCalendarUserAddress(organizer)
-            if organizerRecord is not None:
-                if organizerRecord.guid in self.autoAcceptMembers():
-                    return True
-        return False
-
-
-    def serverURI(self):
-        &quot;&quot;&quot;
-        URL of the server hosting this record. Return None if hosted on this server.
-        &quot;&quot;&quot;
-        if config.Servers.Enabled and self.serverID:
-            return Servers.getServerURIById(self.serverID)
-        else:
-            return None
-
-
-    def server(self):
-        &quot;&quot;&quot;
-        Server hosting this record. Return None if hosted on this server.
-        &quot;&quot;&quot;
-        if config.Servers.Enabled and self.serverID:
-            return Servers.getServerById(self.serverID)
-        else:
-            return None
-
-
-    def thisServer(self):
-        s = self.server()
-        return s.thisServer if s is not None else True
-
-
-    def autoAcceptMembers(self):
-        &quot;&quot;&quot;
-        Return the list of GUIDs for which this record will automatically accept
-        invites from (assuming no conflicts).  This list is based on the group
-        assigned to record.autoAcceptGroup.  Cache the expanded group membership
-        within the record.
-
-        @return: the list of members of the autoAcceptGroup, or an empty list if
-            not assigned
-        @rtype: C{list} of GUID C{str}
-        &quot;&quot;&quot;
-        if not hasattr(self, &quot;_cachedAutoAcceptMembers&quot;):
-            self._cachedAutoAcceptMembers = []
-            if self.autoAcceptGroup:
-                service = self.service.aggregateService or self.service
-                groupRecord = service.recordWithGUID(self.autoAcceptGroup)
-                if groupRecord is not None:
-                    self._cachedAutoAcceptMembers = [m.guid for m in groupRecord.expandedMembers()]
-
-        return self._cachedAutoAcceptMembers
-
-
-    def isProxyFor(self, other):
-        &quot;&quot;&quot;
-        Test whether the record is a calendar user proxy for the specified record.
-
-        @param other: record to test
-        @type other: L{DirectoryRecord}
-
-        @return: C{True} if it is a proxy.
-        @rtype: C{bool}
-        &quot;&quot;&quot;
-        return self.service.isProxyFor(self, other)
-
-
-
-class DirectoryError(RuntimeError):
-    &quot;&quot;&quot;
-    Generic directory error.
-    &quot;&quot;&quot;
-
-
-
-class DirectoryConfigurationError(DirectoryError):
-    &quot;&quot;&quot;
-    Invalid directory configuration.
-    &quot;&quot;&quot;
-
-
-
-class UnknownRecordTypeError(DirectoryError):
-    &quot;&quot;&quot;
-    Unknown directory record type.
-    &quot;&quot;&quot;
-    def __init__(self, recordType):
-        DirectoryError.__init__(self, &quot;Invalid record type: %s&quot; % (recordType,))
-        self.recordType = recordType
-
-
-# So CheckSACL will be parameterized
-# We do this after DirectoryRecord is defined
-try:
-    from calendarserver.platform.darwin._sacl import CheckSACL
-    DirectoryRecord.CheckSACL = CheckSACL
-except ImportError:
-    DirectoryRecord.CheckSACL = None
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryldapdirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/ldapdirectory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/ldapdirectory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/ldapdirectory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,2034 +0,0 @@
</span><del>-##
-# Copyright (c) 2008-2009 Aymeric Augustin. All rights reserved.
-# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-LDAP directory service implementation.  Supports principal-property-search
-and restrictToGroup features.
-
-The following attributes from standard schemas are used:
-* Core (RFC 4519):
-    . cn | commonName
-    . givenName
-    . member (if not using NIS groups)
-    . ou
-    . sn | surname
-    . uid | userid (if using NIS groups)
-* COSINE (RFC 4524):
-    . mail
-* InetOrgPerson (RFC 2798):
-    . displayName (if cn is unavailable)
-* NIS (RFC):
-    . gecos (if cn is unavailable)
-    . memberUid (if using NIS groups)
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;LdapDirectoryService&quot;,
-]
-
-import ldap.async
-from ldap.filter import escape_filter_chars as ldapEsc
-
-try:
-    # Note: PAM support is currently untested
-    import PAM
-    pamAvailable = True
-except ImportError:
-    pamAvailable = False
-
-import time
-from twisted.cred.credentials import UsernamePassword
-from twistedcaldav.directory.cachingdirectory import (
-    CachingDirectoryService, CachingDirectoryRecord
-)
-from twistedcaldav.directory.directory import DirectoryConfigurationError
-from twistedcaldav.directory.augment import AugmentRecord
-from twistedcaldav.directory.util import splitIntoBatches, normalizeUUID
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.internet.threads import deferToThread
-from twext.python.log import Logger
-from txweb2.http import HTTPError, StatusResponse
-from txweb2 import responsecode
-
-
-
-class LdapDirectoryService(CachingDirectoryService):
-    &quot;&quot;&quot;
-    LDAP based implementation of L{IDirectoryService}.
-    &quot;&quot;&quot;
-    log = Logger()
-
-    baseGUID = &quot;5A871574-0C86-44EE-B11B-B9440C3DC4DD&quot;
-
-    def __repr__(self):
-        return &quot;&lt;%s %r: %r&gt;&quot; % (
-            self.__class__.__name__, self.realmName, self.uri
-        )
-
-
-    def __init__(self, params):
-        &quot;&quot;&quot;
-        @param params: a dictionary containing the following keys:
-            cacheTimeout, realmName, uri, tls, tlsCACertFile, tlsCACertDir,
-            tlsRequireCert, credentials, rdnSchema, groupSchema, resourceSchema
-            poddingSchema
-        &quot;&quot;&quot;
-
-        defaults = {
-            &quot;augmentService&quot;: None,
-            &quot;groupMembershipCache&quot;: None,
-            &quot;cacheTimeout&quot;: 1,  # Minutes
-            &quot;negativeCaching&quot;: False,
-            &quot;warningThresholdSeconds&quot;: 3,
-            &quot;batchSize&quot;: 500,  # for splitting up large queries
-            &quot;requestTimeoutSeconds&quot;: 10,
-            &quot;requestResultsLimit&quot;: 200,
-            &quot;optimizeMultiName&quot;: False,
-            &quot;queryLocationsImplicitly&quot;: True,
-            &quot;restrictEnabledRecords&quot;: False,
-            &quot;restrictToGroup&quot;: &quot;&quot;,
-            &quot;recordTypes&quot;: (&quot;users&quot;, &quot;groups&quot;),
-            &quot;uri&quot;: &quot;ldap://localhost/&quot;,
-            &quot;tls&quot;: False,
-            &quot;tlsCACertFile&quot;: None,
-            &quot;tlsCACertDir&quot;: None,
-            &quot;tlsRequireCert&quot;: None,  # never, allow, try, demand, hard
-            &quot;credentials&quot;: {
-                &quot;dn&quot;: None,
-                &quot;password&quot;: None,
-            },
-            &quot;authMethod&quot;: &quot;LDAP&quot;,
-            &quot;rdnSchema&quot;: {
-                &quot;base&quot;: &quot;dc=example,dc=com&quot;,
-                &quot;guidAttr&quot;: &quot;entryUUID&quot;,
-                &quot;users&quot;: {
-                    &quot;rdn&quot;: &quot;ou=People&quot;,
-                    &quot;filter&quot;: None,  # additional filter for this type
-                    &quot;loginEnabledAttr&quot;: &quot;&quot;,  # attribute controlling login
-                    &quot;loginEnabledValue&quot;: &quot;yes&quot;,  # &quot;True&quot; value of above attribute
-                    &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling enabledForCalendaring
-                    &quot;calendarEnabledValue&quot;: &quot;yes&quot;,  # &quot;True&quot; value of above attribute
-                    &quot;mapping&quot;: {  # maps internal record names to LDAP
-                        &quot;recordName&quot;: &quot;uid&quot;,
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;],  # multiple LDAP fields supported
-                        &quot;firstName&quot;: &quot;givenName&quot;,
-                        &quot;lastName&quot;: &quot;sn&quot;,
-                    },
-                },
-                &quot;groups&quot;: {
-                    &quot;rdn&quot;: &quot;ou=Group&quot;,
-                    &quot;filter&quot;: None,  # additional filter for this type
-                    &quot;mapping&quot;: {  # maps internal record names to LDAP
-                        &quot;recordName&quot;: &quot;cn&quot;,
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;],  # multiple LDAP fields supported
-                        &quot;firstName&quot;: &quot;givenName&quot;,
-                        &quot;lastName&quot;: &quot;sn&quot;,
-                    },
-                },
-                &quot;locations&quot;: {
-                    &quot;rdn&quot;: &quot;ou=Places&quot;,
-                    &quot;filter&quot;: None,  # additional filter for this type
-                    &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling enabledForCalendaring
-                    &quot;calendarEnabledValue&quot;: &quot;yes&quot;,  # &quot;True&quot; value of above attribute
-                    &quot;associatedAddressAttr&quot;: &quot;&quot;,
-                    &quot;mapping&quot;: {  # maps internal record names to LDAP
-                        &quot;recordName&quot;: &quot;cn&quot;,
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;],  # multiple LDAP fields supported
-                    },
-                },
-                &quot;resources&quot;: {
-                    &quot;rdn&quot;: &quot;ou=Resources&quot;,
-                    &quot;filter&quot;: None,  # additional filter for this type
-                    &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling enabledForCalendaring
-                    &quot;calendarEnabledValue&quot;: &quot;yes&quot;,  # &quot;True&quot; value of above attribute
-                    &quot;mapping&quot;: {  # maps internal record names to LDAP
-                        &quot;recordName&quot;: &quot;cn&quot;,
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;],  # multiple LDAP fields supported
-                    },
-                },
-                &quot;addresses&quot;: {
-                    &quot;rdn&quot;: &quot;ou=Buildings&quot;,
-                    &quot;filter&quot;: None,  # additional filter for this type
-                    &quot;streetAddressAttr&quot;: &quot;&quot;,
-                    &quot;geoAttr&quot;: &quot;&quot;,
-                    &quot;mapping&quot;: {  # maps internal record names to LDAP
-                        &quot;recordName&quot;: &quot;cn&quot;,
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                    },
-                },
-            },
-            &quot;groupSchema&quot;: {
-                &quot;membersAttr&quot;: &quot;member&quot;,  # how members are specified
-                &quot;nestedGroupsAttr&quot;: None,  # how nested groups are specified
-                &quot;memberIdAttr&quot;: None,  # which attribute the above refer to (None means use DN)
-            },
-            &quot;resourceSchema&quot;: {
-                # Either set this attribute to retrieve the plist version
-                # of resource-info, as in a Leopard OD server, or...
-                &quot;resourceInfoAttr&quot;: None,
-                # ...set the above to None and instead specify these
-                # individually:
-                &quot;autoScheduleAttr&quot;: None,
-                &quot;autoScheduleEnabledValue&quot;: &quot;yes&quot;,
-                &quot;proxyAttr&quot;: None,  # list of GUIDs
-                &quot;readOnlyProxyAttr&quot;: None,  # list of GUIDs
-                &quot;autoAcceptGroupAttr&quot;: None,  # single group GUID
-            },
-            &quot;poddingSchema&quot;: {
-                &quot;serverIdAttr&quot;: None,  # maps to augments server-id
-            },
-        }
-        ignored = None
-        params = self.getParams(params, defaults, ignored)
-
-        self._recordTypes = params[&quot;recordTypes&quot;]
-
-        super(LdapDirectoryService, self).__init__(params[&quot;cacheTimeout&quot;],
-                                                   params[&quot;negativeCaching&quot;])
-
-        self.warningThresholdSeconds = params[&quot;warningThresholdSeconds&quot;]
-        self.batchSize = params[&quot;batchSize&quot;]
-        self.requestTimeoutSeconds = params[&quot;requestTimeoutSeconds&quot;]
-        self.requestResultsLimit = params[&quot;requestResultsLimit&quot;]
-        self.optimizeMultiName = params[&quot;optimizeMultiName&quot;]
-        if self.batchSize &gt; self.requestResultsLimit:
-            self.batchSize = self.requestResultsLimit
-        self.queryLocationsImplicitly = params[&quot;queryLocationsImplicitly&quot;]
-        self.augmentService = params[&quot;augmentService&quot;]
-        self.groupMembershipCache = params[&quot;groupMembershipCache&quot;]
-        self.realmName = params[&quot;uri&quot;]
-        self.uri = params[&quot;uri&quot;]
-        self.tls = params[&quot;tls&quot;]
-        self.tlsCACertFile = params[&quot;tlsCACertFile&quot;]
-        self.tlsCACertDir = params[&quot;tlsCACertDir&quot;]
-        self.tlsRequireCert = params[&quot;tlsRequireCert&quot;]
-        self.credentials = params[&quot;credentials&quot;]
-        self.authMethod = params[&quot;authMethod&quot;]
-        self.rdnSchema = params[&quot;rdnSchema&quot;]
-        self.groupSchema = params[&quot;groupSchema&quot;]
-        self.resourceSchema = params[&quot;resourceSchema&quot;]
-        self.poddingSchema = params[&quot;poddingSchema&quot;]
-
-        self.base = ldap.dn.str2dn(self.rdnSchema[&quot;base&quot;])
-
-        # Certain attributes (such as entryUUID) may be hidden and not
-        # returned by default when queried for all attributes. Therefore it is
-        # necessary to explicitly pass all the possible attributes list
-        # for ldap searches.  Dynamically build the attribute list based on
-        # config.
-        attrSet = set()
-
-        if self.rdnSchema[&quot;guidAttr&quot;]:
-            attrSet.add(self.rdnSchema[&quot;guidAttr&quot;])
-        for recordType in self.recordTypes():
-            if self.rdnSchema[recordType][&quot;attr&quot;]:
-                attrSet.add(self.rdnSchema[recordType][&quot;attr&quot;])
-            for n in (&quot;calendarEnabledAttr&quot;, &quot;associatedAddressAttr&quot;,
-                      &quot;streetAddressAttr&quot;, &quot;geoAttr&quot;):
-                if self.rdnSchema[recordType].get(n, False):
-                    attrSet.add(self.rdnSchema[recordType][n])
-            for attrList in self.rdnSchema[recordType][&quot;mapping&quot;].values():
-                if attrList:
-                    # Since emailAddresses can map to multiple LDAP fields,
-                    # support either string or list
-                    if isinstance(attrList, str):
-                        attrList = [attrList]
-                    for attr in attrList:
-                        attrSet.add(attr)
-            # Also put the guidAttr attribute into the mappings for each type
-            # so recordsMatchingFields can query on guid
-            self.rdnSchema[recordType][&quot;mapping&quot;][&quot;guid&quot;] = self.rdnSchema[&quot;guidAttr&quot;]
-            # Also put the memberIdAttr attribute into the mappings for each type
-            # so recordsMatchingFields can query on memberIdAttr
-            self.rdnSchema[recordType][&quot;mapping&quot;][&quot;memberIdAttr&quot;] = self.groupSchema[&quot;memberIdAttr&quot;]
-        if self.groupSchema[&quot;membersAttr&quot;]:
-            attrSet.add(self.groupSchema[&quot;membersAttr&quot;])
-        if self.groupSchema[&quot;nestedGroupsAttr&quot;]:
-            attrSet.add(self.groupSchema[&quot;nestedGroupsAttr&quot;])
-        if self.groupSchema[&quot;memberIdAttr&quot;]:
-            attrSet.add(self.groupSchema[&quot;memberIdAttr&quot;])
-        if self.rdnSchema[&quot;users&quot;][&quot;loginEnabledAttr&quot;]:
-            attrSet.add(self.rdnSchema[&quot;users&quot;][&quot;loginEnabledAttr&quot;])
-        if self.resourceSchema[&quot;resourceInfoAttr&quot;]:
-            attrSet.add(self.resourceSchema[&quot;resourceInfoAttr&quot;])
-        if self.resourceSchema[&quot;autoScheduleAttr&quot;]:
-            attrSet.add(self.resourceSchema[&quot;autoScheduleAttr&quot;])
-        if self.resourceSchema[&quot;autoAcceptGroupAttr&quot;]:
-            attrSet.add(self.resourceSchema[&quot;autoAcceptGroupAttr&quot;])
-        if self.resourceSchema[&quot;proxyAttr&quot;]:
-            attrSet.add(self.resourceSchema[&quot;proxyAttr&quot;])
-        if self.resourceSchema[&quot;readOnlyProxyAttr&quot;]:
-            attrSet.add(self.resourceSchema[&quot;readOnlyProxyAttr&quot;])
-        if self.poddingSchema[&quot;serverIdAttr&quot;]:
-            attrSet.add(self.poddingSchema[&quot;serverIdAttr&quot;])
-        self.attrlist = list(attrSet)
-
-        self.typeDNs = {}
-        for recordType in self.recordTypes():
-            self.typeDNs[recordType] = ldap.dn.str2dn(
-                self.rdnSchema[recordType][&quot;rdn&quot;].lower()
-            ) + self.base
-
-        self.ldap = None
-
-        # Separate LDAP connection used solely for authenticating clients
-        self.authLDAP = None
-
-        # Restricting access by directory group
-        self.restrictEnabledRecords = params['restrictEnabledRecords']
-        self.restrictToGroup = params['restrictToGroup']
-        self.restrictedTimestamp = 0
-
-
-    def recordTypes(self):
-        return self._recordTypes
-
-
-    def listRecords(self, recordType):
-
-        # Build base for this record Type
-        base = self.typeDNs[recordType]
-
-        # Build filter
-        filterstr = &quot;(!(objectClass=organizationalUnit))&quot;
-        typeFilter = self.rdnSchema[recordType].get(&quot;filter&quot;, &quot;&quot;)
-        if typeFilter:
-            filterstr = &quot;(&amp;%s%s)&quot; % (filterstr, typeFilter)
-
-        # Query the LDAP server
-        self.log.debug(
-            &quot;Querying ldap for records matching base {base} and &quot;
-            &quot;filter {filter} for attributes {attrs}.&quot;,
-            base=ldap.dn.dn2str(base), filter=filterstr,
-            attrs=self.attrlist
-        )
-
-        # This takes a while, so if you don't want to have a &quot;long request&quot;
-        # warning logged, use this instead of timedSearch:
-        # results = self.ldap.search_s(ldap.dn.dn2str(base),
-        #     ldap.SCOPE_SUBTREE, filterstr=filterstr, attrlist=self.attrlist)
-        results = self.timedSearch(
-            ldap.dn.dn2str(base), ldap.SCOPE_SUBTREE,
-            filterstr=filterstr, attrlist=self.attrlist
-        )
-
-        records = []
-        numMissingGuids = 0
-        guidAttr = self.rdnSchema[&quot;guidAttr&quot;]
-        for dn, attrs in results:
-            dn = normalizeDNstr(dn)
-
-            unrestricted = self.isAllowedByRestrictToGroup(dn, attrs)
-
-            try:
-                record = self._ldapResultToRecord(dn, attrs, recordType)
-                # self.log.debug(&quot;Got LDAP record {record}&quot;, record=record)
-            except MissingGuidException:
-                numMissingGuids += 1
-                continue
-
-            if not unrestricted:
-                self.log.debug(
-                    &quot;{dn} is not enabled because it's not a member of group: &quot;
-                    &quot;{group}&quot;, dn=dn, group=self.restrictToGroup
-                )
-                record.enabledForCalendaring = False
-                record.enabledForAddressBooks = False
-
-            records.append(record)
-
-        if numMissingGuids:
-            self.log.info(
-                &quot;{num} {recordType} records are missing {attr}&quot;,
-                num=numMissingGuids, recordType=recordType, attr=guidAttr
-            )
-
-        return records
-
-
-    @inlineCallbacks
-    def recordWithCachedGroupsAlias(self, recordType, alias):
-        &quot;&quot;&quot;
-        @param recordType: the type of the record to look up.
-        @param alias: the cached-groups alias of the record to look up.
-        @type alias: C{str}
-
-        @return: a deferred L{IDirectoryRecord} with the given cached-groups
-            alias, or C{None} if no such record is found.
-        &quot;&quot;&quot;
-        memberIdAttr = self.groupSchema[&quot;memberIdAttr&quot;]
-        attributeToSearch = &quot;memberIdAttr&quot; if memberIdAttr else &quot;dn&quot;
-
-        fields = [[attributeToSearch, alias, False, &quot;equals&quot;]]
-        results = yield self.recordsMatchingFields(
-            fields, recordType=recordType
-        )
-        if results:
-            returnValue(results[0])
-        else:
-            returnValue(None)
-
-
-    def getExternalProxyAssignments(self):
-        &quot;&quot;&quot;
-        Retrieve proxy assignments for locations and resources from the
-        directory and return a list of (principalUID, ([memberUIDs)) tuples,
-        suitable for passing to proxyDB.setGroupMembers( )
-        &quot;&quot;&quot;
-        assignments = []
-
-        guidAttr = self.rdnSchema[&quot;guidAttr&quot;]
-        readAttr = self.resourceSchema[&quot;readOnlyProxyAttr&quot;]
-        writeAttr = self.resourceSchema[&quot;proxyAttr&quot;]
-        if not (guidAttr and readAttr and writeAttr):
-            self.log.error(
-                &quot;LDAP configuration requires guidAttr, proxyAttr, and &quot;
-                &quot;readOnlyProxyAttr in order to use external proxy assignments &quot;
-                &quot;efficiently; falling back to slower method&quot;
-            )
-            # Fall back to the less-specialized version
-            return super(
-                LdapDirectoryService, self
-            ).getExternalProxyAssignments()
-
-        # Build filter
-        filterstr = &quot;(|(%s=*)(%s=*))&quot; % (readAttr, writeAttr)
-        # ...taking into account only calendar-enabled records
-        enabledAttr = self.rdnSchema[&quot;locations&quot;][&quot;calendarEnabledAttr&quot;]
-        enabledValue = self.rdnSchema[&quot;locations&quot;][&quot;calendarEnabledValue&quot;]
-        if enabledAttr and enabledValue:
-            filterstr = &quot;(&amp;(%s=%s)%s)&quot; % (enabledAttr, enabledValue, filterstr)
-
-        attrlist = [guidAttr, readAttr, writeAttr]
-
-        # Query the LDAP server
-        self.log.debug(
-            &quot;Querying ldap for records matching base {base} and filter &quot;
-            &quot;{filter} for attributes {attrs}.&quot;,
-            base=ldap.dn.dn2str(self.base), filter=filterstr,
-            attrs=attrlist
-        )
-
-        results = self.timedSearch(ldap.dn.dn2str(self.base),
-                                   ldap.SCOPE_SUBTREE, filterstr=filterstr,
-                                   attrlist=attrlist)
-
-        for dn, attrs in results:
-            dn = normalizeDNstr(dn)
-            guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-            if guid:
-                guid = normalizeUUID(guid)
-                readDelegate = self._getUniqueLdapAttribute(attrs, readAttr)
-                if readDelegate:
-                    readDelegate = normalizeUUID(readDelegate)
-                    assignments.append(
-                        (&quot;%s#calendar-proxy-read&quot; % (guid,), [readDelegate])
-                    )
-                writeDelegate = self._getUniqueLdapAttribute(attrs, writeAttr)
-                if writeDelegate:
-                    writeDelegate = normalizeUUID(writeDelegate)
-                    assignments.append(
-                        (&quot;%s#calendar-proxy-write&quot; % (guid,), [writeDelegate])
-                    )
-
-        return assignments
-
-
-    def getLDAPConnection(self):
-        if self.ldap is None:
-            self.log.info(&quot;Connecting to LDAP {uri}&quot;, uri=repr(self.uri))
-            self.ldap = self.createLDAPConnection()
-            self.log.info(
-                &quot;Connection established to LDAP {uri}&quot;, uri=repr(self.uri)
-            )
-            if self.credentials.get(&quot;dn&quot;, &quot;&quot;):
-                try:
-                    self.log.info(
-                        &quot;Binding to LDAP {dn}&quot;,
-                        dn=repr(self.credentials.get(&quot;dn&quot;))
-                    )
-                    self.ldap.simple_bind_s(
-                        self.credentials.get(&quot;dn&quot;),
-                        self.credentials.get(&quot;password&quot;),
-                    )
-                    self.log.info(
-                        &quot;Successfully authenticated with LDAP as {dn}&quot;,
-                        dn=repr(self.credentials.get(&quot;dn&quot;))
-                    )
-                except ldap.INVALID_CREDENTIALS:
-                    self.log.error(
-                        &quot;Can't bind to LDAP {uri}: check credentials&quot;,
-                        uri=self.uri
-                    )
-                    raise DirectoryConfigurationError()
-
-        return self.ldap
-
-
-    def createLDAPConnection(self):
-        &quot;&quot;&quot;
-        Create and configure LDAP connection
-        &quot;&quot;&quot;
-        cxn = ldap.initialize(self.uri)
-
-        if self.tlsCACertFile:
-            cxn.set_option(ldap.OPT_X_TLS_CACERTFILE, self.tlsCACertFile)
-        if self.tlsCACertDir:
-            cxn.set_option(ldap.OPT_X_TLS_CACERTDIR, self.tlsCACertDir)
-
-        if self.tlsRequireCert == &quot;never&quot;:
-            cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_NEVER)
-        elif self.tlsRequireCert == &quot;allow&quot;:
-            cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_ALLOW)
-        elif self.tlsRequireCert == &quot;try&quot;:
-            cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_TRY)
-        elif self.tlsRequireCert == &quot;demand&quot;:
-            cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
-        elif self.tlsRequireCert == &quot;hard&quot;:
-            cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_HARD)
-
-        if self.tls:
-            cxn.start_tls_s()
-
-        return cxn
-
-
-    def authenticate(self, dn, password):
-        &quot;&quot;&quot;
-        Perform simple bind auth, raising ldap.INVALID_CREDENTIALS if
-        bad password
-        &quot;&quot;&quot;
-        TRIES = 3
-
-        for _ignore_i in xrange(TRIES):
-            self.log.debug(&quot;Authenticating {dn}&quot;, dn=dn)
-
-            if self.authLDAP is None:
-                self.log.debug(&quot;Creating authentication connection to LDAP&quot;)
-                self.authLDAP = self.createLDAPConnection()
-
-            try:
-                startTime = time.time()
-                self.authLDAP.simple_bind_s(dn, password)
-                # Getting here means success, so break the retry loop
-                break
-
-            except ldap.INAPPROPRIATE_AUTH:
-                # Seen when using an empty password, treat as invalid creds
-                raise ldap.INVALID_CREDENTIALS()
-
-            except ldap.NO_SUCH_OBJECT:
-                self.log.error(
-                    &quot;LDAP Authentication error for {dn}: NO_SUCH_OBJECT&quot;,
-                    dn=dn
-                )
-                # fall through to try again; could be transient
-
-            except ldap.INVALID_CREDENTIALS:
-                raise
-
-            except ldap.SERVER_DOWN:
-                self.log.error(&quot;Lost connection to LDAP server.&quot;)
-                self.authLDAP = None
-                # Fall through and retry if TRIES has been reached
-
-            except Exception, e:
-                self.log.error(
-                    &quot;LDAP authentication failed with {e}.&quot;, e=e
-                )
-                raise
-
-            finally:
-                totalTime = time.time() - startTime
-                if totalTime &gt; self.warningThresholdSeconds:
-                    self.log.error(
-                        &quot;LDAP auth exceeded threshold: {time:.2f} seconds for &quot;
-                        &quot;{dn}&quot;, time=totalTime, dn=dn
-                    )
-
-        else:
-            self.log.error(
-                &quot;Giving up on LDAP authentication after {count:d} tries.  &quot;
-                &quot;Responding with 503.&quot;, count=TRIES
-            )
-            raise HTTPError(StatusResponse(
-                responsecode.SERVICE_UNAVAILABLE, &quot;LDAP server unavailable&quot;
-            ))
-
-        self.log.debug(&quot;Authentication succeeded for {dn}&quot;, dn=dn)
-
-
-    def timedSearch(
-        self, base, scope, filterstr=&quot;(objectClass=*)&quot;, attrlist=None,
-        timeoutSeconds=-1, resultLimit=0
-    ):
-        &quot;&quot;&quot;
-        Execute an LDAP query, retrying up to 3 times in case the LDAP server
-        has gone down and we need to reconnect. If it takes longer than the
-        configured threshold, emit a log error.
-        The number of records requested is controlled by resultLimit (0=no
-        limit).
-        If timeoutSeconds is not -1, the query will abort after the specified
-        number of seconds and the results retrieved so far are returned.
-        &quot;&quot;&quot;
-        TRIES = 3
-
-        for i in xrange(TRIES):
-            try:
-                s = ldap.async.List(self.getLDAPConnection())
-                s.startSearch(
-                    base, scope, filterstr, attrList=attrlist,
-                    timeout=timeoutSeconds, sizelimit=resultLimit
-                )
-                startTime = time.time()
-                s.processResults()
-            except ldap.NO_SUCH_OBJECT:
-                return []
-            except ldap.FILTER_ERROR, e:
-                self.log.error(
-                    &quot;LDAP filter error: {e} {filter}&quot;, e=e, filter=filterstr
-                )
-                return []
-            except ldap.SIZELIMIT_EXCEEDED, e:
-                self.log.debug(
-                    &quot;LDAP result limit exceeded: {limit:d}&quot;, limit=resultLimit
-                )
-            except ldap.TIMELIMIT_EXCEEDED, e:
-                self.log.warn(
-                    &quot;LDAP timeout exceeded: {t:d} seconds&quot;, t=timeoutSeconds
-                )
-            except ldap.SERVER_DOWN:
-                self.ldap = None
-                self.log.error(
-                    &quot;LDAP server unavailable (tried {count:d} times)&quot;,
-                    count=(i + 1)
-                )
-                continue
-
-            # change format, ignoring resultsType
-            result = [
-                resultItem for _ignore_resultType, resultItem in s.allResults
-            ]
-
-            totalTime = time.time() - startTime
-            if totalTime &gt; self.warningThresholdSeconds:
-                if filterstr and len(filterstr) &gt; 100:
-                    filterstr = &quot;%s...&quot; % (filterstr[:100],)
-                self.log.error(
-                    &quot;LDAP query exceeded threshold: {time:.2f} seconds for &quot;
-                    &quot;{base} {filter} {attrs} (#results={count:d})&quot;,
-                    time=totalTime, base=base, filter=filterstr,
-                    attrs=attrlist, count=len(result),
-                )
-            return result
-
-        raise HTTPError(StatusResponse(
-            responsecode.SERVICE_UNAVAILABLE, &quot;LDAP server unavailable&quot;
-        ))
-
-
-    def isAllowedByRestrictToGroup(self, dn, attrs):
-        &quot;&quot;&quot;
-        Check to see if the principal with the given DN and LDAP attributes is
-        a member of the restrictToGroup.
-
-        @param dn: an LDAP dn
-        @type dn: C{str}
-        @param attrs: LDAP attributes
-        @type attrs: C{dict}
-        @return: True if principal is in the group (or restrictEnabledRecords if turned off).
-        @rtype: C{boolean}
-        &quot;&quot;&quot;
-        if not self.restrictEnabledRecords:
-            return True
-        if self.groupSchema[&quot;memberIdAttr&quot;]:
-            value = self._getUniqueLdapAttribute(
-                attrs, self.groupSchema[&quot;memberIdAttr&quot;]
-            )
-        else:  # No memberIdAttr implies DN
-            value = dn
-        return value in self.restrictedPrincipals
-
-
-    @property
-    def restrictedPrincipals(self):
-        &quot;&quot;&quot;
-        Look up (and cache) the set of guids that are members of the
-        restrictToGroup.  If restrictToGroup is not set, return None to
-        indicate there are no group restrictions.
-        &quot;&quot;&quot;
-        if self.restrictEnabledRecords:
-
-            if time.time() - self.restrictedTimestamp &gt; self.cacheTimeout:
-                # fault in the members of group of name self.restrictToGroup
-                recordType = self.recordType_groups
-                base = self.typeDNs[recordType]
-                # TODO: This shouldn't be hardcoded to cn
-                filterstr = &quot;(cn=%s)&quot; % (self.restrictToGroup,)
-                self.log.debug(
-                    &quot;Retrieving ldap record with base {base} and filter &quot;
-                    &quot;{filter}.&quot;,
-                    base=ldap.dn.dn2str(base), filter=filterstr
-                )
-                result = self.timedSearch(
-                    ldap.dn.dn2str(base),
-                    ldap.SCOPE_SUBTREE,
-                    filterstr=filterstr,
-                    attrlist=self.attrlist
-                )
-
-                members = []
-                nestedGroups = []
-
-                if len(result) == 1:
-                    dn, attrs = result[0]
-                    dn = normalizeDNstr(dn)
-                    if self.groupSchema[&quot;membersAttr&quot;]:
-                        members = self._getMultipleLdapAttributes(
-                            attrs,
-                            self.groupSchema[&quot;membersAttr&quot;]
-                        )
-                        if not self.groupSchema[&quot;memberIdAttr&quot;]:  # DNs
-                            members = [normalizeDNstr(m) for m in members]
-                        members = set(members)
-
-                    if self.groupSchema[&quot;nestedGroupsAttr&quot;]:
-                        nestedGroups = self._getMultipleLdapAttributes(
-                            attrs,
-                            self.groupSchema[&quot;nestedGroupsAttr&quot;]
-                        )
-                        if not self.groupSchema[&quot;memberIdAttr&quot;]:  # DNs
-                            nestedGroups = [
-                                normalizeDNstr(g) for g in nestedGroups
-                            ]
-                        nestedGroups = set(nestedGroups)
-                    else:
-                        # Since all members are lumped into the same attribute,
-                        # treat them all as nestedGroups instead
-                        nestedGroups = members
-                        members = set()
-
-                self._cachedRestrictedPrincipals = set(
-                    self._expandGroupMembership(members, nestedGroups)
-                )
-                self.log.info(
-                    &quot;Got {count} restricted group members&quot;,
-                    count=len(self._cachedRestrictedPrincipals)
-                )
-                self.restrictedTimestamp = time.time()
-            return self._cachedRestrictedPrincipals
-        else:
-            # No restrictions
-            return None
-
-
-    def _expandGroupMembership(self, members, nestedGroups, processedItems=None):
-        &quot;&quot;&quot;
-        A generator which recursively yields principals which are included within nestedGroups
-
-        @param members:  If the LDAP service is configured to use different attributes to
-            indicate member users and member nested groups, members will include the non-groups.
-            Otherwise, members will be empty and only nestedGroups will be used.
-        @type members: C{set}
-        @param nestedGroups:  If the LDAP service is configured to use different attributes to
-            indicate member users and member nested groups, nestedGroups will include only
-            the groups; otherwise nestedGroups will include all members
-        @type members: C{set}
-        @param processedItems: The set of members that have already been looked up in LDAP
-            so the code doesn't have to look up the same member twice or get stuck in a
-            membership loop.
-        @type processedItems: C{set}
-        @return: All members of the group, the values will correspond to memberIdAttr
-            if memberIdAttr is set in the group schema, or DNs otherwise.
-        @rtype: generator of C{str}
-        &quot;&quot;&quot;
-
-        if processedItems is None:
-            processedItems = set()
-
-        if isinstance(members, str):
-            members = [members]
-
-        if isinstance(nestedGroups, str):
-            nestedGroups = [nestedGroups]
-
-        for member in members:
-            if member not in processedItems:
-                processedItems.add(member)
-                yield member
-
-        for group in nestedGroups:
-            if group in processedItems:
-                continue
-
-            recordType = self.recordType_groups
-            base = self.typeDNs[recordType]
-            if self.groupSchema[&quot;memberIdAttr&quot;]:
-                scope = ldap.SCOPE_SUBTREE
-                base = self.typeDNs[recordType]
-                filterstr = &quot;(%s=%s)&quot; % (self.groupSchema[&quot;memberIdAttr&quot;], group)
-            else:  # Use DN
-                scope = ldap.SCOPE_BASE
-                base = ldap.dn.str2dn(group)
-                filterstr = &quot;(objectClass=*)&quot;
-
-            self.log.debug(
-                &quot;Retrieving ldap record with base {base} and filter {filter}.&quot;,
-                base=ldap.dn.dn2str(base), filter=filterstr
-            )
-            result = self.timedSearch(ldap.dn.dn2str(base),
-                                      scope,
-                                      filterstr=filterstr,
-                                      attrlist=self.attrlist)
-
-            if len(result) == 0:
-                continue
-
-            subMembers = set()
-            subNestedGroups = set()
-            if len(result) == 1:
-                dn, attrs = result[0]
-                dn = normalizeDNstr(dn)
-                if self.groupSchema[&quot;membersAttr&quot;]:
-                    subMembers = self._getMultipleLdapAttributes(
-                        attrs,
-                        self.groupSchema[&quot;membersAttr&quot;]
-                    )
-                    if not self.groupSchema[&quot;memberIdAttr&quot;]:  # these are DNs
-                        subMembers = [normalizeDNstr(m) for m in subMembers]
-                    subMembers = set(subMembers)
-
-                if self.groupSchema[&quot;nestedGroupsAttr&quot;]:
-                    subNestedGroups = self._getMultipleLdapAttributes(
-                        attrs,
-                        self.groupSchema[&quot;nestedGroupsAttr&quot;]
-                    )
-                    if not self.groupSchema[&quot;memberIdAttr&quot;]:  # these are DNs
-                        subNestedGroups = [normalizeDNstr(g) for g in subNestedGroups]
-                    subNestedGroups = set(subNestedGroups)
-
-            processedItems.add(group)
-            yield group
-
-            for item in self._expandGroupMembership(subMembers,
-                                                    subNestedGroups,
-                                                    processedItems):
-                yield item
-
-
-    def _getUniqueLdapAttribute(self, attrs, *keys):
-        &quot;&quot;&quot;
-        Get the first value for one or several attributes
-        Useful when attributes have aliases (e.g. sn vs. surname)
-        &quot;&quot;&quot;
-        for key in keys:
-            values = attrs.get(key)
-            if values is not None:
-                return values[0]
-        return None
-
-
-    def _getMultipleLdapAttributes(self, attrs, *keys):
-        &quot;&quot;&quot;
-        Get all values for one or several attributes
-        &quot;&quot;&quot;
-        results = []
-        for key in keys:
-            if key:
-                values = attrs.get(key)
-                if values is not None:
-                    results += values
-        return results
-
-
-    def _ldapResultToRecord(self, dn, attrs, recordType):
-        &quot;&quot;&quot;
-        Convert the attrs returned by a LDAP search into a LdapDirectoryRecord
-        object.
-
-        If guidAttr was specified in the config but is missing from attrs,
-        raises MissingGuidException
-        &quot;&quot;&quot;
-
-        guid = None
-        authIDs = set()
-        fullName = None
-        firstName = &quot;&quot;
-        lastName = &quot;&quot;
-        emailAddresses = set()
-        enabledForCalendaring = None
-        enabledForAddressBooks = None
-        uid = None
-        enabledForLogin = True
-        extras = {}
-
-        shortNames = tuple(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;recordName&quot;]))
-        if not shortNames:
-            raise MissingRecordNameException()
-
-        # First check for and add guid
-        guidAttr = self.rdnSchema[&quot;guidAttr&quot;]
-        if guidAttr:
-            guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-            if not guid:
-                self.log.debug(
-                    &quot;LDAP data for {shortNames} is missing guid attribute &quot;
-                    &quot;{attr}&quot;,
-                    shortNames=shortNames, attr=guidAttr
-                )
-                raise MissingGuidException()
-            guid = normalizeUUID(guid)
-
-        # Find or build email
-        # (The emailAddresses mapping is a list of ldap fields)
-        emailAddressesMappedTo = self.rdnSchema[recordType][&quot;mapping&quot;].get(&quot;emailAddresses&quot;, &quot;&quot;)
-        # Supporting either string or list for emailAddresses:
-        if isinstance(emailAddressesMappedTo, str):
-            emailAddresses = set(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType][&quot;mapping&quot;].get(&quot;emailAddresses&quot;, &quot;&quot;)))
-        else:
-            emailAddresses = set(self._getMultipleLdapAttributes(attrs, *self.rdnSchema[recordType][&quot;mapping&quot;][&quot;emailAddresses&quot;]))
-        emailSuffix = self.rdnSchema[recordType].get(&quot;emailSuffix&quot;, None)
-
-        if len(emailAddresses) == 0 and emailSuffix:
-            emailPrefix = self._getUniqueLdapAttribute(
-                attrs,
-                self.rdnSchema[recordType].get(&quot;attr&quot;, &quot;cn&quot;)
-            )
-            emailAddresses.add(emailPrefix + emailSuffix)
-
-        proxyGUIDs = ()
-        readOnlyProxyGUIDs = ()
-        autoSchedule = False
-        autoAcceptGroup = &quot;&quot;
-        memberGUIDs = []
-
-        # LDAP attribute -&gt; principal matchings
-        if recordType == self.recordType_users:
-            fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;fullName&quot;])
-            firstName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;firstName&quot;])
-            lastName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;lastName&quot;])
-            enabledForCalendaring = True
-            enabledForAddressBooks = True
-
-        elif recordType == self.recordType_groups:
-            fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;fullName&quot;])
-            enabledForCalendaring = False
-            enabledForAddressBooks = False
-            enabledForLogin = False
-
-            if self.groupSchema[&quot;membersAttr&quot;]:
-                members = self._getMultipleLdapAttributes(attrs, self.groupSchema[&quot;membersAttr&quot;])
-                memberGUIDs.extend(members)
-            if self.groupSchema[&quot;nestedGroupsAttr&quot;]:
-                members = self._getMultipleLdapAttributes(attrs, self.groupSchema[&quot;nestedGroupsAttr&quot;])
-                memberGUIDs.extend(members)
-
-            # Normalize members if they're in DN form
-            if not self.groupSchema[&quot;memberIdAttr&quot;]:  # empty = dn
-                guids = list(memberGUIDs)
-                memberGUIDs = []
-                for dnStr in guids:
-                    try:
-                        dnStr = normalizeDNstr(dnStr)
-                        memberGUIDs.append(dnStr)
-                    except Exception, e:
-                        # LDAP returned an illegal DN value, log and ignore it
-                        self.log.warn(&quot;Bad LDAP DN: {dn!r}&quot;, dn=dnStr)
-
-        elif recordType in (self.recordType_resources,
-                            self.recordType_locations):
-            fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType][&quot;mapping&quot;][&quot;fullName&quot;])
-            enabledForCalendaring = True
-            enabledForAddressBooks = False
-            enabledForLogin = False
-            if self.resourceSchema[&quot;resourceInfoAttr&quot;]:
-                resourceInfo = self._getUniqueLdapAttribute(
-                    attrs,
-                    self.resourceSchema[&quot;resourceInfoAttr&quot;]
-                )
-                if resourceInfo:
-                    try:
-                        (
-                            autoSchedule,
-                            proxy,
-                            readOnlyProxy,
-                            autoAcceptGroup
-                        ) = self.parseResourceInfo(
-                            resourceInfo,
-                            guid,
-                            recordType,
-                            shortNames[0]
-                        )
-                        if proxy:
-                            proxyGUIDs = (proxy,)
-                        if readOnlyProxy:
-                            readOnlyProxyGUIDs = (readOnlyProxy,)
-                    except ValueError, e:
-                        self.log.error(
-                            &quot;Unable to parse resource info: {e}&quot;, e=e
-                        )
-            else:  # the individual resource attributes might be specified
-                if self.resourceSchema[&quot;autoScheduleAttr&quot;]:
-                    autoScheduleValue = self._getUniqueLdapAttribute(
-                        attrs,
-                        self.resourceSchema[&quot;autoScheduleAttr&quot;]
-                    )
-                    autoSchedule = (
-                        autoScheduleValue == self.resourceSchema[&quot;autoScheduleEnabledValue&quot;]
-                    )
-                if self.resourceSchema[&quot;proxyAttr&quot;]:
-                    proxyGUIDs = set(
-                        self._getMultipleLdapAttributes(
-                            attrs,
-                            self.resourceSchema[&quot;proxyAttr&quot;]
-                        )
-                    )
-                if self.resourceSchema[&quot;readOnlyProxyAttr&quot;]:
-                    readOnlyProxyGUIDs = set(
-                        self._getMultipleLdapAttributes(
-                            attrs,
-                            self.resourceSchema[&quot;readOnlyProxyAttr&quot;]
-                        )
-                    )
-                if self.resourceSchema[&quot;autoAcceptGroupAttr&quot;]:
-                    autoAcceptGroup = self._getUniqueLdapAttribute(
-                        attrs,
-                        self.resourceSchema[&quot;autoAcceptGroupAttr&quot;]
-                    )
-
-            if recordType == self.recordType_locations:
-                if self.rdnSchema[recordType].get(&quot;associatedAddressAttr&quot;, &quot;&quot;):
-                    associatedAddress = self._getUniqueLdapAttribute(
-                        attrs,
-                        self.rdnSchema[recordType][&quot;associatedAddressAttr&quot;]
-                    )
-                    if associatedAddress:
-                        extras[&quot;associatedAddress&quot;] = associatedAddress
-
-        elif recordType == self.recordType_addresses:
-            if self.rdnSchema[recordType].get(&quot;geoAttr&quot;, &quot;&quot;):
-                geo = self._getUniqueLdapAttribute(
-                    attrs,
-                    self.rdnSchema[recordType][&quot;geoAttr&quot;]
-                )
-                if geo:
-                    extras[&quot;geo&quot;] = geo
-            if self.rdnSchema[recordType].get(&quot;streetAddressAttr&quot;, &quot;&quot;):
-                street = self._getUniqueLdapAttribute(
-                    attrs,
-                    self.rdnSchema[recordType][&quot;streetAddressAttr&quot;]
-                )
-                if street:
-                    extras[&quot;streetAddress&quot;] = street
-
-        serverID = None
-        if self.poddingSchema[&quot;serverIdAttr&quot;]:
-            serverID = self._getUniqueLdapAttribute(
-                attrs,
-                self.poddingSchema[&quot;serverIdAttr&quot;]
-            )
-
-        record = LdapDirectoryRecord(
-            service=self,
-            recordType=recordType,
-            guid=guid,
-            shortNames=shortNames,
-            authIDs=authIDs,
-            fullName=fullName,
-            firstName=firstName,
-            lastName=lastName,
-            emailAddresses=emailAddresses,
-            uid=uid,
-            dn=dn,
-            memberGUIDs=memberGUIDs,
-            extProxies=proxyGUIDs,
-            extReadOnlyProxies=readOnlyProxyGUIDs,
-            attrs=attrs,
-            **extras
-        )
-
-        if self.augmentService is not None:
-            # Look up augment information
-            # TODO: this needs to be deferred but for now we hard code
-            # the deferred result because we know it is completing
-            # immediately.
-            d = self.augmentService.getAugmentRecord(record.guid, recordType)
-            d.addCallback(lambda x: record.addAugmentInformation(x))
-
-        else:
-            # Generate augment record based on information retrieved from LDAP
-            augmentRecord = AugmentRecord(
-                guid,
-                enabled=True,
-                serverID=serverID,
-                enabledForCalendaring=enabledForCalendaring,
-                autoSchedule=autoSchedule,
-                autoAcceptGroup=autoAcceptGroup,
-                enabledForAddressBooks=enabledForAddressBooks,  # TODO: add to LDAP?
-                enabledForLogin=enabledForLogin,
-            )
-            record.addAugmentInformation(augmentRecord)
-
-        # Override with LDAP login control if attribute specified
-        if recordType == self.recordType_users:
-            loginEnabledAttr = self.rdnSchema[recordType][&quot;loginEnabledAttr&quot;]
-            if loginEnabledAttr:
-                loginEnabledValue = self.rdnSchema[recordType][&quot;loginEnabledValue&quot;]
-                record.enabledForLogin = self._getUniqueLdapAttribute(
-                    attrs, loginEnabledAttr
-                ) == loginEnabledValue
-
-        # Override with LDAP calendar-enabled control if attribute specified
-        calendarEnabledAttr = self.rdnSchema[recordType].get(&quot;calendarEnabledAttr&quot;, &quot;&quot;)
-        if calendarEnabledAttr:
-            calendarEnabledValue = self.rdnSchema[recordType][&quot;calendarEnabledValue&quot;]
-            record.enabledForCalendaring = self._getUniqueLdapAttribute(
-                attrs,
-                calendarEnabledAttr
-            ) == calendarEnabledValue
-
-        return record
-
-
-    def queryDirectory(
-        self, recordTypes, indexType, indexKey, queryMethod=None
-    ):
-        &quot;&quot;&quot;
-        Queries the LDAP directory for the record which has an attribute value
-        matching the indexType and indexKey parameters.
-
-        recordTypes is a list of record types to limit the search to.
-        indexType specifies one of the CachingDirectoryService constants
-            identifying which attribute to search on.
-        indexKey is the value to search for.
-
-        Nothing is returned -- the resulting record (if any) is placed in
-        the cache.
-        &quot;&quot;&quot;
-
-        if queryMethod is None:
-            queryMethod = self.timedSearch
-
-        self.log.debug(
-            &quot;LDAP query for types {types}, indexType {indexType} and &quot;
-            &quot;indexKey {indexKey}&quot;,
-            types=recordTypes, indexType=indexType, indexKey=indexKey
-        )
-
-        guidAttr = self.rdnSchema[&quot;guidAttr&quot;]
-        for recordType in recordTypes:
-            # Build base for this record Type
-            base = self.typeDNs[recordType]
-
-            # Build filter
-            filterstr = &quot;(!(objectClass=organizationalUnit))&quot;
-            typeFilter = self.rdnSchema[recordType].get(&quot;filter&quot;, &quot;&quot;)
-            if typeFilter:
-                filterstr = &quot;(&amp;%s%s)&quot; % (filterstr, typeFilter)
-
-            if indexType == self.INDEX_TYPE_GUID:
-                # Query on guid only works if guid attribute has been defined.
-                # Support for query on guid even if is auto-generated should
-                # be added.
-                if not guidAttr:
-                    return
-                filterstr = &quot;(&amp;%s(%s=%s))&quot; % (filterstr, guidAttr, indexKey)
-
-            elif indexType == self.INDEX_TYPE_SHORTNAME:
-                filterstr = &quot;(&amp;%s(%s=%s))&quot; % (
-                    filterstr,
-                    self.rdnSchema[recordType][&quot;mapping&quot;][&quot;recordName&quot;],
-                    ldapEsc(indexKey)
-                )
-
-            elif indexType == self.INDEX_TYPE_CUA:
-                # indexKey is of the form &quot;mailto:test@example.net&quot;
-                email = indexKey[7:]  # strip &quot;mailto:&quot;
-                emailSuffix = self.rdnSchema[recordType].get(
-                    &quot;emailSuffix&quot;, None
-                )
-                if (
-                    emailSuffix is not None and
-                    email.partition(&quot;@&quot;)[2] == emailSuffix
-                ):
-                    filterstr = &quot;(&amp;%s(|(&amp;(!(mail=*))(%s=%s))(mail=%s)))&quot; % (
-                        filterstr,
-                        self.rdnSchema[recordType].get(&quot;attr&quot;, &quot;cn&quot;),
-                        email.partition(&quot;@&quot;)[0],
-                        ldapEsc(email)
-                    )
-                else:
-                    # emailAddresses can map to multiple LDAP fields
-                    ldapFields = self.rdnSchema[recordType][&quot;mapping&quot;].get(
-                        &quot;emailAddresses&quot;, &quot;&quot;
-                    )
-                    if isinstance(ldapFields, str):
-                        if ldapFields:
-                            subfilter = (
-                                &quot;(%s=%s)&quot; % (ldapFields, ldapEsc(email))
-                            )
-                        else:
-                            # No LDAP attribute assigned for emailAddresses
-                            continue
-
-                    else:
-                        subfilter = []
-                        for ldapField in ldapFields:
-                            if ldapField:
-                                subfilter.append(
-                                    &quot;(%s=%s)&quot; % (ldapField, ldapEsc(email))
-                                )
-                        if not subfilter:
-                            # No LDAP attribute assigned for emailAddresses
-                            continue
-
-                        subfilter = &quot;(|%s)&quot; % (&quot;&quot;.join(subfilter))
-                    filterstr = &quot;(&amp;%s%s)&quot; % (filterstr, subfilter)
-
-            elif indexType == self.INDEX_TYPE_AUTHID:
-                return
-
-            # Query the LDAP server
-            self.log.debug(
-                &quot;Retrieving ldap record with base %s and filter %s.&quot;,
-                base=ldap.dn.dn2str(base), filter=filterstr,
-            )
-            result = queryMethod(
-                ldap.dn.dn2str(base),
-                ldap.SCOPE_SUBTREE,
-                filterstr=filterstr,
-                attrlist=self.attrlist,
-            )
-
-            if result:
-                dn, attrs = result.pop()
-                dn = normalizeDNstr(dn)
-
-                unrestricted = self.isAllowedByRestrictToGroup(dn, attrs)
-
-                try:
-                    record = self._ldapResultToRecord(dn, attrs, recordType)
-                    self.log.debug(&quot;Got LDAP record {rec}&quot;, rec=record)
-
-                    if not unrestricted:
-                        self.log.debug(
-                            &quot;{dn} is not enabled because it's not a member of &quot;
-                            &quot;group {group!r}&quot;,
-                            dn=dn, group=self.restrictToGroup
-                        )
-                        record.enabledForCalendaring = False
-                        record.enabledForAddressBooks = False
-
-                    record.applySACLs()
-
-                    self.recordCacheForType(recordType).addRecord(
-                        record, indexType, indexKey
-                    )
-
-                    # We got a match, so don't bother checking other types
-                    break
-
-                except MissingRecordNameException:
-                    self.log.warn(
-                        &quot;Ignoring record missing record name &quot;
-                        &quot;attribute: recordType {recordType}, indexType &quot;
-                        &quot;{indexType} and indexKey {indexKey}&quot;,
-                        recordTypes=recordTypes, indexType=indexType,
-                        indexKey=indexKey,
-                    )
-
-                except MissingGuidException:
-                    self.log.warn(
-                        &quot;Ignoring record missing guid attribute: &quot;
-                        &quot;recordType {recordType}, indexType {indexType} and &quot;
-                        &quot;indexKey {indexKey}&quot;,
-                        recordTypes=recordTypes, indexType=indexType,
-                        indexKey=indexKey
-                    )
-
-
-    def recordsMatchingTokens(self, tokens, context=None, limitResults=50, timeoutSeconds=10):
-        &quot;&quot;&quot;
-        # TODO: hook up limitResults to the client limit in the query
-
-        @param tokens: The tokens to search on
-        @type tokens: C{list} of C{str} (utf-8 bytes)
-        @param context: An indication of what the end user is searching
-            for; &quot;attendee&quot;, &quot;location&quot;, or None
-        @type context: C{str}
-        @return: a deferred sequence of L{IDirectoryRecord}s which
-            match the given tokens and optional context.
-
-        Each token is searched for within each record's full name and
-        email address; if each token is found within a record that
-        record is returned in the results.
-
-        If context is None, all record types are considered.  If
-        context is &quot;location&quot;, only locations are considered.  If
-        context is &quot;attendee&quot;, only users, groups, and resources
-        are considered.
-        &quot;&quot;&quot;
-        self.log.debug(
-            &quot;Peforming calendar user search for {tokens} ({context})&quot;,
-            tokens=tokens, context=context
-        )
-        startTime = time.time()
-        records = []
-        recordTypes = self.recordTypesForSearchContext(context)
-        recordTypes = [r for r in recordTypes if r in self.recordTypes()]
-
-        typeCounts = {}
-        for recordType in recordTypes:
-            if limitResults == 0:
-                self.log.debug(&quot;LDAP search aggregate limit reached&quot;)
-                break
-            typeCounts[recordType] = 0
-            base = self.typeDNs[recordType]
-            scope = ldap.SCOPE_SUBTREE
-            extraFilter = self.rdnSchema[recordType].get(&quot;filter&quot;, &quot;&quot;)
-            filterstr = buildFilterFromTokens(
-                recordType,
-                self.rdnSchema[recordType][&quot;mapping&quot;],
-                tokens,
-                extra=extraFilter
-            )
-
-            if filterstr is not None:
-                # Query the LDAP server
-                self.log.debug(
-                    &quot;LDAP search {base} {filter} (limit={limit:d})&quot;,
-                    base=ldap.dn.dn2str(base), filter=filterstr,
-                    limit=limitResults,
-                )
-                results = self.timedSearch(
-                    ldap.dn.dn2str(base),
-                    scope,
-                    filterstr=filterstr,
-                    attrlist=self.attrlist,
-                    timeoutSeconds=timeoutSeconds,
-                    resultLimit=limitResults
-                )
-                numMissingGuids = 0
-                numMissingRecordNames = 0
-                numNotEnabled = 0
-                for dn, attrs in results:
-                    dn = normalizeDNstr(dn)
-                    # Skip if group restriction is in place and guid is not
-                    # a member
-                    if (
-                            recordType != self.recordType_groups and
-                            not self.isAllowedByRestrictToGroup(dn, attrs)
-                    ):
-                        continue
-
-                    try:
-                        record = self._ldapResultToRecord(dn, attrs, recordType)
-
-                        # For non-group records, if not enabled for calendaring do
-                        # not include in principal property search results
-                        if (recordType != self.recordType_groups):
-                            if not record.enabledForCalendaring:
-                                numNotEnabled += 1
-                                continue
-
-                        records.append(record)
-                        typeCounts[recordType] += 1
-                        limitResults -= 1
-
-                    except MissingGuidException:
-                        numMissingGuids += 1
-
-                    except MissingRecordNameException:
-                        numMissingRecordNames += 1
-
-                self.log.debug(
-                    &quot;LDAP search returned {resultCount:d} results, &quot;
-                    &quot;{typeCount:d} usable&quot;,
-                    resultCount=len(results), typeCount=typeCounts[recordType]
-                )
-
-        typeCountsStr = &quot;, &quot;.join(
-            [&quot;%s:%d&quot; % (rt, ct) for (rt, ct) in typeCounts.iteritems()]
-        )
-        totalTime = time.time() - startTime
-        self.log.info(
-            &quot;Calendar user search for {tokens} matched {recordCount:d} &quot;
-            &quot;records ({typeCount}) in {time!.2f} seconds&quot;,
-            tokens=tokens, recordCount=len(records),
-            typeCount=typeCountsStr, time=totalTime,
-        )
-        return succeed(records)
-
-
-    @inlineCallbacks
-    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None):
-        &quot;&quot;&quot;
-        Carries out the work of a principal-property-search against LDAP
-        Returns a deferred list of directory records.
-        &quot;&quot;&quot;
-        records = []
-
-        self.log.debug(
-            &quot;Performing principal property search for {fields}&quot;, fields=fields
-        )
-
-        if recordType is None:
-            # Make a copy since we're modifying it
-            recordTypes = list(self.recordTypes())
-
-            # principal-property-search syntax doesn't provide a way to ask
-            # for 3 of the 4 types (either all types or a single type).  This
-            # is wasteful in the case of iCal looking for event attendees
-            # since it always ignores the locations.  This config flag lets
-            # you skip querying for locations in this case:
-            if not self.queryLocationsImplicitly:
-                if self.recordType_locations in recordTypes:
-                    recordTypes.remove(self.recordType_locations)
-        else:
-            recordTypes = [recordType]
-
-        guidAttr = self.rdnSchema[&quot;guidAttr&quot;]
-        for recordType in recordTypes:
-
-            base = self.typeDNs[recordType]
-
-            if fields[0][0] == &quot;dn&quot;:
-                # DN's are not an attribute that can be searched on by filter
-                scope = ldap.SCOPE_BASE
-                filterstr = &quot;(objectClass=*)&quot;
-                base = ldap.dn.str2dn(fields[0][1])
-
-            else:
-                scope = ldap.SCOPE_SUBTREE
-                filterstr = buildFilter(
-                    recordType,
-                    self.rdnSchema[recordType][&quot;mapping&quot;],
-                    fields,
-                    operand=operand,
-                    optimizeMultiName=self.optimizeMultiName
-                )
-
-            if filterstr is not None:
-                # Query the LDAP server
-                self.log.debug(
-                    &quot;LDAP search {base} {scope} {filter}&quot;,
-                    base=ldap.dn.dn2str(base), scope=scope, filter=filterstr
-                )
-                results = (yield deferToThread(
-                    self.timedSearch,
-                    ldap.dn.dn2str(base),
-                    scope,
-                    filterstr=filterstr,
-                    attrlist=self.attrlist,
-                    timeoutSeconds=self.requestTimeoutSeconds,
-                    resultLimit=self.requestResultsLimit)
-                )
-                self.log.debug(
-                    &quot;LDAP search returned {count} results&quot;, count=len(results)
-                )
-                numMissingGuids = 0
-                numMissingRecordNames = 0
-                for dn, attrs in results:
-                    dn = normalizeDNstr(dn)
-                    # Skip if group restriction is in place and guid is not
-                    # a member
-                    if (
-                        recordType != self.recordType_groups and
-                        not self.isAllowedByRestrictToGroup(dn, attrs)
-                    ):
-                        continue
-
-                    try:
-                        record = self._ldapResultToRecord(dn, attrs, recordType)
-
-                        # For non-group records, if not enabled for calendaring do
-                        # not include in principal property search results
-                        if (recordType != self.recordType_groups):
-                            if not record.enabledForCalendaring:
-                                continue
-
-                        records.append(record)
-
-                    except MissingGuidException:
-                        numMissingGuids += 1
-
-                    except MissingRecordNameException:
-                        numMissingRecordNames += 1
-
-                if numMissingGuids:
-                    self.log.warn(
-                        &quot;{count:d} {type} records are missing {attr}&quot;,
-                        count=numMissingGuids, type=recordType, attr=guidAttr
-                    )
-
-                if numMissingRecordNames:
-                    self.log.warn(
-                        &quot;{count:d} {type} records are missing record name&quot;,
-                        count=numMissingRecordNames, type=recordType,
-                    )
-
-        self.log.debug(
-            &quot;Principal property search matched {count} records&quot;,
-            count=len(records)
-        )
-        returnValue(records)
-
-
-    @inlineCallbacks
-    def getGroups(self, guids):
-        &quot;&quot;&quot;
-        Returns a set of group records for the list of guids passed in.  For
-        any group that also contains subgroups, those subgroups' records are
-        also returned, and so on.
-        &quot;&quot;&quot;
-
-        recordsByAlias = {}
-
-        groupsDN = self.typeDNs[self.recordType_groups]
-        memberIdAttr = self.groupSchema[&quot;memberIdAttr&quot;]
-
-        # First time through the loop we search using the attribute
-        # corresponding to guid, since that is what the proxydb uses.
-        # Subsequent iterations fault in groups via the attribute
-        # used to identify members.
-        attributeToSearch = &quot;guid&quot;
-        valuesToFetch = guids
-
-        while valuesToFetch:
-            results = []
-
-            if attributeToSearch == &quot;dn&quot;:
-                # Since DN can't be searched on in a filter we have to call
-                # recordsMatchingFields for *each* DN.
-                for value in valuesToFetch:
-                    fields = [[&quot;dn&quot;, value, False, &quot;equals&quot;]]
-                    result = (
-                        yield self.recordsMatchingFields(
-                            fields,
-                            recordType=self.recordType_groups
-                        )
-                    )
-                    results.extend(result)
-            else:
-                for batch in splitIntoBatches(valuesToFetch, self.batchSize):
-                    fields = []
-                    for value in batch:
-                        fields.append([attributeToSearch, value, False, &quot;equals&quot;])
-                    result = (
-                        yield self.recordsMatchingFields(
-                            fields,
-                            recordType=self.recordType_groups
-                        )
-                    )
-                    results.extend(result)
-
-            # Reset values for next iteration
-            valuesToFetch = set()
-
-            for record in results:
-                alias = record.cachedGroupsAlias()
-                if alias not in recordsByAlias:
-                    recordsByAlias[alias] = record
-
-                # record.memberGUIDs() contains the members of this group,
-                # but it might not be in guid form; it will be data from
-                # self.groupSchema[&quot;memberIdAttr&quot;]
-                for memberAlias in record.memberGUIDs():
-                    if not memberIdAttr:
-                        # Members are identified by dn so we can take a short
-                        # cut:  we know we only need to examine groups, and
-                        # those will be children of the groups DN
-                        if not dnContainedIn(ldap.dn.str2dn(memberAlias),
-                                             groupsDN):
-                            continue
-                    if memberAlias not in recordsByAlias:
-                        valuesToFetch.add(memberAlias)
-
-            # Switch to the LDAP attribute used for identifying members
-            # for subsequent iterations.  If memberIdAttr is not specified
-            # in the config, we'll search using dn.
-            attributeToSearch = &quot;memberIdAttr&quot; if memberIdAttr else &quot;dn&quot;
-
-        returnValue(recordsByAlias.values())
-
-
-    def recordTypeForDN(self, dnStr):
-        &quot;&quot;&quot;
-        Examine a DN to determine which recordType it belongs to
-        @param dn: DN to compare
-        @type dn: string
-        @return: recordType string, or None if no match
-        &quot;&quot;&quot;
-        dn = ldap.dn.str2dn(dnStr.lower())
-        for recordType in self.recordTypes():
-            base = self.typeDNs[recordType]  # already lowercase
-            if dnContainedIn(dn, base):
-                return recordType
-        return None
-
-
-
-def dnContainedIn(child, parent):
-    &quot;&quot;&quot;
-    Return True if child dn is contained within parent dn, otherwise False.
-    &quot;&quot;&quot;
-    return child[-len(parent):] == parent
-
-
-
-def normalizeDNstr(dnStr):
-    &quot;&quot;&quot;
-    Convert to lowercase and remove extra whitespace
-    @param dnStr: dn
-    @type dnStr: C{str}
-    @return: normalized dn C{str}
-    &quot;&quot;&quot;
-    return ' '.join(ldap.dn.dn2str(ldap.dn.str2dn(dnStr.lower())).split())
-
-
-
-def _convertValue(value, matchType):
-    if matchType == &quot;starts-with&quot;:
-        value = &quot;%s*&quot; % (ldapEsc(value),)
-    elif matchType == &quot;contains&quot;:
-        value = &quot;*%s*&quot; % (ldapEsc(value),)
-    # otherwise it's an exact match
-    else:
-        value = ldapEsc(value)
-    return value
-
-
-
-def buildFilter(recordType, mapping, fields, operand=&quot;or&quot;, optimizeMultiName=False):
-    &quot;&quot;&quot;
-    Create an LDAP filter string from a list of tuples representing directory
-    attributes to search
-
-    mapping is a dict mapping internal directory attribute names to ldap names.
-    fields is a list of tuples...
-        (directory field name, value to search, caseless (ignored), matchType)
-    ...where matchType is one of &quot;starts-with&quot;, &quot;contains&quot;, &quot;exact&quot;
-    &quot;&quot;&quot;
-
-    converted = []
-    combined = {}
-    for field, value, caseless, matchType in fields:
-        ldapField = mapping.get(field, None)
-        if ldapField:
-            combined.setdefault(field, []).append((value, caseless, matchType))
-            value = _convertValue(value, matchType)
-            if isinstance(ldapField, str):
-                converted.append(&quot;(%s=%s)&quot; % (ldapField, value))
-            else:
-                subConverted = []
-                for lf in ldapField:
-                    subConverted.append(&quot;(%s=%s)&quot; % (lf, value))
-                converted.append(&quot;(|%s)&quot; % &quot;&quot;.join(subConverted))
-
-    if len(converted) == 0:
-        return None
-
-    if optimizeMultiName and recordType in (&quot;users&quot;, &quot;groups&quot;):
-        for field in [key for key in combined.keys() if key != &quot;guid&quot;]:
-            if len(combined.get(field, [])) &gt; 1:
-                # Client is searching on more than one name -- interpret this as the user
-                # explicitly looking up a user by name (ignoring other record types), and
-                # try the various firstName/lastName permutations:
-                if recordType == &quot;users&quot;:
-                    converted = []
-                    for firstName, _ignore_firstCaseless, firstMatchType in combined[&quot;firstName&quot;]:
-                        for lastName, _ignore_lastCaseless, lastMatchType in combined[&quot;lastName&quot;]:
-                            if firstName != lastName:
-                                firstValue = _convertValue(firstName, firstMatchType)
-                                lastValue = _convertValue(lastName, lastMatchType)
-                                converted.append(
-                                    &quot;(&amp;(%s=%s)(%s=%s))&quot; %
-                                    (mapping[&quot;firstName&quot;], firstValue,
-                                     mapping[&quot;lastName&quot;], lastValue)
-                                )
-                else:
-                    return None
-
-    if len(converted) == 1:
-        filterstr = converted[0]
-    else:
-        operand = (&quot;|&quot; if operand == &quot;or&quot; else &quot;&amp;&quot;)
-        filterstr = &quot;(%s%s)&quot; % (operand, &quot;&quot;.join(converted))
-
-    if filterstr:
-        # To reduce the amount of records returned, filter out the ones
-        # that don't have (possibly) required attribute values (record
-        # name, guid)
-        additional = []
-        for key in (&quot;recordName&quot;, &quot;guid&quot;):
-            if key in mapping:
-                additional.append(&quot;(%s=*)&quot; % (mapping.get(key),))
-        if additional:
-            filterstr = &quot;(&amp;%s%s)&quot; % (&quot;&quot;.join(additional), filterstr)
-
-    return filterstr
-
-
-
-def buildFilterFromTokens(recordType, mapping, tokens, extra=None):
-    &quot;&quot;&quot;
-    Create an LDAP filter string from a list of query tokens.  Each token is
-    searched for in each LDAP attribute corresponding to &quot;fullName&quot; and
-    &quot;emailAddresses&quot; (could be multiple LDAP fields for either).
-
-    @param recordType: The recordType to use to customize the filter
-    @param mapping: A dict mapping internal directory attribute names to ldap names.
-    @type mapping: C{dict}
-    @param tokens: The list of tokens to search for
-    @type tokens: C{list}
-    @param extra: Extra filter to &quot;and&quot; into the final filter
-    @type extra: C{str} or None
-    @return: An LDAP filterstr
-    @rtype: C{str}
-    &quot;&quot;&quot;
-
-    filterStr = None
-
-    # Eliminate any substring duplicates
-    tokenSet = set()
-    for token in tokens:
-        collision = False
-        for existing in tokenSet:
-            if token in existing:
-                collision = True
-                break
-            elif existing in token:
-                tokenSet.remove(existing)
-                break
-        if not collision:
-            tokenSet.add(token)
-
-    tokens = [ldapEsc(t) for t in tokenSet]
-    if len(tokens) == 0:
-        return None
-    tokens.sort()
-
-    attributes = [
-        (&quot;fullName&quot;, &quot;(%s=*%s*)&quot;),
-        (&quot;emailAddresses&quot;, &quot;(%s=%s*)&quot;),
-    ]
-
-    ldapFields = []
-    for attribute, template in attributes:
-        ldapField = mapping.get(attribute, None)
-        if ldapField:
-            if isinstance(ldapField, str):
-                ldapFields.append((ldapField, template))
-            else:
-                for lf in ldapField:
-                    ldapFields.append((lf, template))
-
-    if len(ldapFields) == 0:
-        return None
-
-    tokenFragments = []
-    if extra:
-        tokenFragments.append(extra)
-
-    for token in tokens:
-        fragments = []
-        for ldapField, template in ldapFields:
-            fragments.append(template % (ldapField, token))
-        if len(fragments) == 1:
-            tokenFragment = fragments[0]
-        else:
-            tokenFragment = &quot;(|%s)&quot; % (&quot;&quot;.join(fragments),)
-        tokenFragments.append(tokenFragment)
-
-    if len(tokenFragments) == 1:
-        filterStr = tokenFragments[0]
-    else:
-        filterStr = &quot;(&amp;%s)&quot; % (&quot;&quot;.join(tokenFragments),)
-
-    return filterStr
-
-
-
-class LdapDirectoryRecord(CachingDirectoryRecord):
-    &quot;&quot;&quot;
-    LDAP implementation of L{IDirectoryRecord}.
-    &quot;&quot;&quot;
-    def __init__(
-        self, service, recordType,
-        guid, shortNames, authIDs, fullName,
-        firstName, lastName, emailAddresses,
-        uid, dn, memberGUIDs, extProxies, extReadOnlyProxies,
-        attrs, **kwargs
-    ):
-        super(LdapDirectoryRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            guid=guid,
-            shortNames=shortNames,
-            authIDs=authIDs,
-            fullName=fullName,
-            firstName=firstName,
-            lastName=lastName,
-            emailAddresses=emailAddresses,
-            extProxies=extProxies,
-            extReadOnlyProxies=extReadOnlyProxies,
-            uid=uid,
-            **kwargs
-        )
-
-        # Save attributes of dn and attrs in case you might need them later
-        self.dn = dn
-        self.attrs = attrs
-
-        # Store copy of member guids
-        self._memberGUIDs = memberGUIDs
-
-        # Identifier of this record as a group member
-        memberIdAttr = self.service.groupSchema[&quot;memberIdAttr&quot;]
-        if memberIdAttr:
-            self._memberId = self.service._getUniqueLdapAttribute(
-                attrs,
-                memberIdAttr
-            )
-        else:
-            self._memberId = normalizeDNstr(self.dn)
-
-
-    def members(self):
-        &quot;&quot;&quot; Return the records representing members of this group &quot;&quot;&quot;
-
-        try:
-            return self._members_storage
-        except AttributeError:
-            self._members_storage = self._members()
-            return self._members_storage
-
-
-    def _members(self):
-        &quot;&quot;&quot; Fault in records for the members of this group &quot;&quot;&quot;
-
-        memberIdAttr = self.service.groupSchema[&quot;memberIdAttr&quot;]
-        results = []
-
-        for memberId in self._memberGUIDs:
-
-            if memberIdAttr:
-
-                base = self.service.base
-                filterstr = &quot;(%s=%s)&quot; % (memberIdAttr, ldapEsc(memberId))
-                self.log.debug(
-                    &quot;Retrieving subtree of {base} with filter {filter}&quot;,
-                    base=ldap.dn.dn2str(base), filter=filterstr,
-                    system=&quot;LdapDirectoryService&quot;
-                )
-                result = self.service.timedSearch(
-                    ldap.dn.dn2str(base),
-                    ldap.SCOPE_SUBTREE,
-                    filterstr=filterstr,
-                    attrlist=self.service.attrlist
-                )
-
-            else:  # using DN
-
-                self.log.debug(
-                    &quot;Retrieving {id}.&quot;,
-                    id=memberId, system=&quot;LdapDirectoryService&quot;
-                )
-                result = self.service.timedSearch(
-                    memberId,
-                    ldap.SCOPE_BASE, attrlist=self.service.attrlist
-                )
-
-            if result:
-
-                dn, attrs = result.pop()
-                dn = normalizeDNstr(dn)
-                self.log.debug(&quot;Retrieved: {dn} {attrs}&quot;, dn=dn, attrs=attrs)
-                recordType = self.service.recordTypeForDN(dn)
-                if recordType is None:
-                    self.log.error(
-                        &quot;Unable to map {dn} to a record type&quot;, dn=dn
-                    )
-                    continue
-
-                shortName = self.service._getUniqueLdapAttribute(
-                    attrs,
-                    self.service.rdnSchema[recordType][&quot;mapping&quot;][&quot;recordName&quot;]
-                )
-
-                if shortName:
-                    record = self.service.recordWithShortName(
-                        recordType,
-                        shortName
-                    )
-                    if record:
-                        results.append(record)
-
-        return results
-
-
-    def groups(self):
-        &quot;&quot;&quot; Return the records representing groups this record is a member of &quot;&quot;&quot;
-        try:
-            return self._groups_storage
-        except AttributeError:
-            self._groups_storage = self._groups()
-            return self._groups_storage
-
-
-    def _groups(self):
-        &quot;&quot;&quot; Fault in the groups of which this record is a member &quot;&quot;&quot;
-
-        recordType = self.service.recordType_groups
-        base = self.service.typeDNs[recordType]
-
-        membersAttrs = []
-        if self.service.groupSchema[&quot;membersAttr&quot;]:
-            membersAttrs.append(self.service.groupSchema[&quot;membersAttr&quot;])
-        if self.service.groupSchema[&quot;nestedGroupsAttr&quot;]:
-            membersAttrs.append(self.service.groupSchema[&quot;nestedGroupsAttr&quot;])
-
-        if len(membersAttrs) == 1:
-            filterstr = &quot;(%s=%s)&quot; % (membersAttrs[0], self._memberId)
-        else:
-            filterstr = &quot;(|%s)&quot; % (
-                &quot;&quot;.join(
-                    [&quot;(%s=%s)&quot; % (a, self._memberId) for a in membersAttrs]
-                ),
-            )
-        self.log.debug(&quot;Finding groups containing {id}&quot;, id=self._memberId)
-        groups = []
-
-        try:
-            results = self.service.timedSearch(
-                ldap.dn.dn2str(base),
-                ldap.SCOPE_SUBTREE,
-                filterstr=filterstr,
-                attrlist=self.service.attrlist
-            )
-
-            for dn, attrs in results:
-                dn = normalizeDNstr(dn)
-                shortName = self.service._getUniqueLdapAttribute(attrs, &quot;cn&quot;)
-                self.log.debug(
-                    &quot;{id} is a member of {shortName}&quot;,
-                    id=self._memberId, shortName=shortName
-                )
-                record = self.service.recordWithShortName(recordType, shortName)
-                if record is not None:
-                    groups.append(record)
-        except ldap.PROTOCOL_ERROR, e:
-            self.log.warn(&quot;{e}&quot;, e=e)
-
-        return groups
-
-
-    def cachedGroupsAlias(self):
-        &quot;&quot;&quot;
-        See directory.py for full description
-
-        LDAP group members can be referred to by attributes other than guid.  _memberId
-        will be set to the appropriate value to look up group-membership with.
-        &quot;&quot;&quot;
-        return self._memberId
-
-
-    def memberGUIDs(self):
-        return set(self._memberGUIDs)
-
-
-    def verifyCredentials(self, credentials):
-        &quot;&quot;&quot; Supports PAM or simple LDAP bind for username+password &quot;&quot;&quot;
-
-        if isinstance(credentials, UsernamePassword):
-
-            # TODO: investigate:
-            # Check that the username supplied matches one of the shortNames
-            # (The DCS might already enforce this constraint, not sure)
-            if credentials.username not in self.shortNames:
-                return False
-
-            # Check cached password
-            try:
-                if credentials.password == self.password:
-                    return True
-            except AttributeError:
-                pass
-
-            if self.service.authMethod.upper() == &quot;PAM&quot;:
-                # Authenticate against PAM (UNTESTED)
-
-                if not pamAvailable:
-                    self.log.error(&quot;PAM module is not installed&quot;)
-                    raise DirectoryConfigurationError()
-
-                def pam_conv(auth, query_list, userData):
-                    return [(credentials.password, 0)]
-
-                auth = PAM.pam()
-                auth.start(&quot;caldav&quot;)
-                auth.set_item(PAM.PAM_USER, credentials.username)
-                auth.set_item(PAM.PAM_CONV, pam_conv)
-                try:
-                    auth.authenticate()
-                except PAM.error:
-                    return False
-                else:
-                    # Cache the password to avoid further LDAP queries
-                    self.password = credentials.password
-                    return True
-
-            elif self.service.authMethod.upper() == &quot;LDAP&quot;:
-
-                # Authenticate against LDAP
-                try:
-                    self.service.authenticate(self.dn, credentials.password)
-                    # Cache the password to avoid further LDAP queries
-                    self.password = credentials.password
-                    return True
-
-                except ldap.INVALID_CREDENTIALS:
-                    self.log.info(
-                        &quot;Invalid credentials for {dn}&quot;,
-                        dn=repr(self.dn), system=&quot;LdapDirectoryService&quot;
-                    )
-                    return False
-
-            else:
-                self.log.error(
-                    &quot;Unknown Authentication Method {method!r}&quot;,
-                    method=self.service.authMethod.upper()
-                )
-                raise DirectoryConfigurationError()
-
-        return super(LdapDirectoryRecord, self).verifyCredentials(credentials)
-
-
-
-class MissingRecordNameException(Exception):
-    &quot;&quot;&quot; Raised when LDAP record is missing recordName &quot;&quot;&quot;
-    pass
-
-
-
-class MissingGuidException(Exception):
-    &quot;&quot;&quot; Raised when LDAP record is missing guidAttr and it's required &quot;&quot;&quot;
-    pass
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryprincipalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/principal.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/principal.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/principal.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -28,49 +28,47 @@
</span><span class="cx">     &quot;DirectoryCalendarPrincipalResource&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-import uuid
</del><span class="cx"> from urllib import unquote
</span><span class="cx"> from urlparse import urlparse
</span><ins>+import uuid
</ins><span class="cx"> 
</span><ins>+from twext.python.log import Logger
</ins><span class="cx"> from twisted.cred.credentials import UsernamePassword
</span><del>-from twisted.python.failure import Failure
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.defer import succeed
</span><del>-from twisted.web.template import XMLFile, Element, renderer, tags
-from twistedcaldav.directory.util import NotFoundResource
-
-from txweb2.auth.digest import DigestedCredentials
-from txweb2 import responsecode
-from txweb2.http import HTTPError
-from txdav.xml import element as davxml
-from txweb2.dav.util import joinURL
-from txweb2.dav.noneprops import NonePropertyStore
-
-from twext.python.log import Logger
-
-
-try:
-    from twistedcaldav.authkerb import NegotiateCredentials
-    NegotiateCredentials # sigh, pyflakes
-except ImportError:
-    NegotiateCredentials = None
</del><span class="cx"> from twisted.python.modules import getModule
</span><del>-
</del><ins>+from twisted.web.template import XMLFile, Element, renderer
</ins><span class="cx"> from twistedcaldav import caldavxml, customxml
</span><span class="cx"> from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><span class="cx"> from twistedcaldav.directory.augment import allowedAutoScheduleModes
</span><span class="cx"> from twistedcaldav.directory.common import uidsResourceName
</span><del>-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.idirectory import IDirectoryService
</del><ins>+from twistedcaldav.directory.util import NotFoundResource
+from twistedcaldav.directory.util import (
+    formatLink, formatLinks, formatPrincipals, formatList
+)
</ins><span class="cx"> from twistedcaldav.directory.wiki import getWikiACL
</span><ins>+from twistedcaldav.extensions import (
+    ReadOnlyResourceMixIn, DAVPrincipalResource, DAVResourceWithChildrenMixin
+)
</ins><span class="cx"> from twistedcaldav.extensions import DirectoryElement
</span><del>-from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource, \
-    DAVResourceWithChildrenMixin
</del><span class="cx"> from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
</span><span class="cx"> from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
</span><ins>+from txdav.who.directory import CalendarDirectoryRecordMixin
+from txdav.xml import element as davxml
+from txweb2 import responsecode
+from txweb2.auth.digest import DigestedCredentials
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.util import joinURL
+from txweb2.http import HTTPError
</ins><span class="cx"> 
</span><ins>+try:
+    from twistedcaldav.authkerb import NegotiateCredentials
+    NegotiateCredentials  # sigh, pyflakes
+except ImportError:
+    NegotiateCredentials = None
+
</ins><span class="cx"> thisModule = getModule(__name__)
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -109,7 +107,7 @@
</span><span class="cx"> def cuTypeConverter(cuType):
</span><span class="cx">     &quot;&quot;&quot; Converts calendar user types to OD type names &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    return &quot;recordType&quot;, DirectoryRecord.fromCUType(cuType)
</del><ins>+    return &quot;recordType&quot;, CalendarDirectoryRecordMixin.fromCUType(cuType)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -127,7 +125,7 @@
</span><span class="cx">     elif cua.startswith(&quot;/&quot;) or cua.startswith(&quot;http&quot;):
</span><span class="cx">         ignored, collection, id = cua.rsplit(&quot;/&quot;, 2)
</span><span class="cx">         if collection == &quot;__uids__&quot;:
</span><del>-            return &quot;guid&quot;, id
</del><ins>+            return &quot;uid&quot;, id
</ins><span class="cx">         else:
</span><span class="cx">             return &quot;recordName&quot;, id
</span><span class="cx"> 
</span><span class="lines">@@ -223,7 +221,7 @@
</span><span class="cx">     _cs_ns = &quot;http://calendarserver.org/ns/&quot;
</span><span class="cx">     _fieldMap = {
</span><span class="cx">         (&quot;DAV:&quot; , &quot;displayname&quot;) :
</span><del>-            (&quot;fullName&quot;, None, &quot;Display Name&quot;, davxml.DisplayName),
</del><ins>+            (&quot;fullNames&quot;, None, &quot;Display Name&quot;, davxml.DisplayName),
</ins><span class="cx">         (&quot;urn:ietf:params:xml:ns:caldav&quot; , &quot;calendar-user-type&quot;) :
</span><span class="cx">             (&quot;&quot;, cuTypeConverter, &quot;Calendar User Type&quot;,
</span><span class="cx">             caldavxml.CalendarUserType),
</span><span class="lines">@@ -287,10 +285,16 @@
</span><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        # MOVE2WHO - hack: appending &quot;s&quot; -- need mapping
-        for name, recordType in [(r.name + &quot;s&quot;, r) for r in self.directory.recordTypes()]:
-            self.putChild(name, DirectoryPrincipalTypeProvisioningResource(self,
-                name, recordType))
</del><ins>+        for name, recordType in [
+            (self.directory.recordTypeToOldName(r), r)
+            for r in self.directory.recordTypes()
+        ]:
+            self.putChild(
+                name,
+                DirectoryPrincipalTypeProvisioningResource(
+                    self, name, recordType
+                )
+            )
</ins><span class="cx"> 
</span><span class="cx">         self.putChild(uidsResourceName, DirectoryPrincipalUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="lines">@@ -869,8 +873,12 @@
</span><span class="cx">             #         returnValue(None)
</span><span class="cx"> 
</span><span class="cx">             if name == &quot;email-address-set&quot;:
</span><ins>+                try:
+                    emails = self.record.emailAddresses
+                except AttributeError:
+                    emails = []
</ins><span class="cx">                 returnValue(customxml.EmailAddressSet(
</span><del>-                    *[customxml.EmailAddressProperty(addr) for addr in sorted(self.record.emailAddresses)]
</del><ins>+                    *[customxml.EmailAddressProperty(addr) for addr in sorted(emails)]
</ins><span class="cx">                 ))
</span><span class="cx"> 
</span><span class="cx">         result = (yield super(DirectoryPrincipalResource, self).readProperty(property, request))
</span><span class="lines">@@ -1463,71 +1471,3 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def formatPrincipals(principals):
-    &quot;&quot;&quot;
-    Format a list of principals into some twisted.web.template DOM objects.
-    &quot;&quot;&quot;
-    def recordKey(principal):
-        try:
-            record = principal.record
-        except AttributeError:
-            try:
-                record = principal.parent.record
-            except:
-                return None
-        return (record.recordType, record.shortNames[0])
-
-
-    def describe(principal):
-        if hasattr(principal, &quot;record&quot;):
-            return &quot; - %s&quot; % (principal.record.displayName,)
-        else:
-            return &quot;&quot;
-
-    return formatList(
-        tags.a(href=principal.principalURL())(
-            str(principal), describe(principal)
-        )
-        for principal in sorted(principals, key=recordKey)
-    )
-
-
-
-def formatList(iterable):
-    &quot;&quot;&quot;
-    Format a list of stuff as an interable.
-    &quot;&quot;&quot;
-    thereAreAny = False
-    try:
-        item = None
-        for item in iterable:
-            thereAreAny = True
-            yield &quot; -&gt; &quot;
-            if item is None:
-                yield &quot;None&quot;
-            else:
-                yield item
-            yield &quot;\n&quot;
-    except Exception, e:
-        log.error(&quot;Exception while rendering: %s&quot; % (e,))
-        Failure().printTraceback()
-        yield &quot;  ** %s **: %s\n&quot; % (e.__class__.__name__, e)
-    if not thereAreAny:
-        yield &quot; '()\n&quot;
-
-
-
-def formatLink(url):
-    &quot;&quot;&quot;
-    Convert a URL string into some twisted.web.template DOM objects for
-    rendering as a link to itself.
-    &quot;&quot;&quot;
-    return tags.a(href=url)(url)
-
-
-
-def formatLinks(urls):
-    &quot;&quot;&quot;
-    Format a list of URL strings as a list of twisted.web.template DOM links.
-    &quot;&quot;&quot;
-    return formatList(formatLink(link) for link in urls)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytestaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/accounts.xml (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/accounts.xml        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/accounts.xml        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -135,8 +135,42 @@
</span><span class="cx">     &lt;short-name&gt;delegategroup&lt;/short-name&gt;
</span><span class="cx">     &lt;uid&gt;00599DAF-3E75-42DD-9DB7-52617E79943F&lt;/uid&gt;
</span><span class="cx">     &lt;full-name&gt;Delegate Group&lt;/full-name&gt;
</span><del>-      &lt;member-uid&gt;delegateviagroup&lt;/member-uid&gt;
</del><ins>+    &lt;member-uid&gt;delegateviagroup&lt;/member-uid&gt;
</ins><span class="cx">   &lt;/record&gt;
</span><ins>+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user01&lt;/short-name&gt;
+    &lt;uid&gt;user01&lt;/uid&gt;
+    &lt;password&gt;user01&lt;/password&gt;
+    &lt;full-name&gt;User 01&lt;/full-name&gt;
+    &lt;email&gt;user01@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user02&lt;/short-name&gt;
+    &lt;uid&gt;user02&lt;/uid&gt;
+    &lt;password&gt;user02&lt;/password&gt;
+    &lt;full-name&gt;User 02&lt;/full-name&gt;
+    &lt;email&gt;user02@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user03&lt;/short-name&gt;
+    &lt;uid&gt;user03&lt;/uid&gt;
+    &lt;password&gt;user03&lt;/password&gt;
+    &lt;full-name&gt;User 03&lt;/full-name&gt;
+    &lt;email&gt;user03@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user04&lt;/short-name&gt;
+    &lt;uid&gt;user04&lt;/uid&gt;
+    &lt;password&gt;user04&lt;/password&gt;
+    &lt;full-name&gt;User 04&lt;/full-name&gt;
+    &lt;email&gt;user04@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;!-- Repeat is not (yet?) supported in twext.who.xml
</ins><span class="cx">   &lt;user repeat=&quot;100&quot;&gt;
</span><span class="cx">     &lt;short-name&gt;user%02d&lt;/short-name&gt;
</span><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="lines">@@ -146,6 +180,8 @@
</span><span class="cx">     &lt;last-name&gt;~9 User %02d&lt;/last-name&gt;
</span><span class="cx">     &lt;email&gt;~10@example.com&lt;/email&gt;
</span><span class="cx">   &lt;/user&gt;
</span><ins>+  --&gt;
+
</ins><span class="cx">   &lt;record type=&quot;group&quot;&gt;
</span><span class="cx">     &lt;short-name&gt;managers&lt;/short-name&gt;
</span><span class="cx">     &lt;uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/uid&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_aggregatepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_aggregate.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_aggregate.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_aggregate.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,87 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory import augment
-
-xml_prefix = &quot;xml:&quot;
-
-testServices = (
-    (xml_prefix   , twistedcaldav.directory.test.test_xmlfile.XMLFile),
-)
-
-class AggregatedDirectories (twistedcaldav.directory.test.util.DirectoryTestCase):
-    def _recordTypes(self):
-        recordTypes = set()
-        for prefix, testClass in testServices:
-            for recordType in testClass.recordTypes:
-                recordTypes.add(prefix + recordType)
-        return recordTypes
-
-
-    def _records(key): #@NoSelf
-        def get(self):
-            records = {}
-            for prefix, testClass in testServices:
-                for record, info in getattr(testClass, key).iteritems():
-                    info = dict(info)
-                    info[&quot;prefix&quot;] = prefix
-                    info[&quot;members&quot;] = tuple(
-                        (t, prefix + s) for t, s in info.get(&quot;members&quot;, {})
-                    )
-                    records[prefix + record] = info
-            return records
-        return get
-
-    recordTypes = property(_recordTypes)
-    users = property(_records(&quot;users&quot;))
-    groups = property(_records(&quot;groups&quot;))
-    locations = property(_records(&quot;locations&quot;))
-    resources = property(_records(&quot;resources&quot;))
-    addresses = property(_records(&quot;addresses&quot;))
-
-    recordTypePrefixes = tuple(s[0] for s in testServices)
-
-
-    def service(self):
-        &quot;&quot;&quot;
-        Returns an IDirectoryService.
-        &quot;&quot;&quot;
-        xmlService = XMLDirectoryService(
-            {
-                'xmlFile' : xmlFile,
-                'augmentService' :
-                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-            }
-        )
-        xmlService.recordTypePrefix = xml_prefix
-
-        return AggregateDirectoryService((xmlService,), None)
-
-
-    def test_setRealm(self):
-        &quot;&quot;&quot;
-        setRealm gets propagated to nested services
-        &quot;&quot;&quot;
-        aggregatedService = self.service()
-        aggregatedService.setRealm(&quot;foo.example.com&quot;)
-        for service in aggregatedService._recordTypes.values():
-            self.assertEquals(&quot;foo.example.com&quot;, service.realmName)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_augmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_augment.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_augment.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_augment.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -17,7 +17,6 @@
</span><span class="cx"> from twistedcaldav.test.util import TestCase
</span><span class="cx"> from twistedcaldav.directory.augment import AugmentXMLDB, AugmentSqliteDB, \
</span><span class="cx">     AugmentPostgreSQLDB, AugmentRecord
</span><del>-from twistedcaldav.directory.directory import DirectoryService
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> from twistedcaldav.directory.xmlaugmentsparser import XMLAugmentsParser
</span><span class="cx"> import cStringIO
</span><span class="lines">@@ -78,7 +77,7 @@
</span><span class="cx"> class AugmentTests(TestCase):
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _checkRecord(self, db, items, recordType=DirectoryService.recordType_users):
</del><ins>+    def _checkRecord(self, db, items, recordType=&quot;users&quot;):
</ins><span class="cx"> 
</span><span class="cx">         record = (yield db.getAugmentRecord(items[&quot;uid&quot;], recordType))
</span><span class="cx">         self.assertTrue(record is not None, &quot;Failed record uid: %s&quot; % (items[&quot;uid&quot;],))
</span><span class="lines">@@ -88,7 +87,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _checkRecordExists(self, db, uid, recordType=DirectoryService.recordType_users):
</del><ins>+    def _checkRecordExists(self, db, uid, recordType=&quot;users&quot;):
</ins><span class="cx"> 
</span><span class="cx">         record = (yield db.getAugmentRecord(uid, recordType))
</span><span class="cx">         self.assertTrue(record is not None, &quot;Failed record uid: %s&quot; % (uid,))
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_buildquerypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_buildquery.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_buildquery.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_buildquery.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,160 +0,0 @@
</span><del>-##
-# Copyright (c) 2009-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-try:
-    from calendarserver.platform.darwin.od import dsattributes
-except ImportError:
-    pass
-else:
-    from twistedcaldav.test.util import TestCase
-    from twistedcaldav.directory.appleopendirectory import (buildQueries,
-        buildLocalQueriesFromTokens, OpenDirectoryService, buildNestedQueryFromTokens)
-
-    class BuildQueryTests(TestCase):
-
-        def test_buildQuery(self):
-            self.assertEquals(
-                buildQueries(
-                    [dsattributes.kDSStdRecordTypeUsers],
-                    (
-                        (&quot;firstName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;sagen&quot;, True, &quot;starts-with&quot;),
-                    ),
-                    OpenDirectoryService._ODFields
-                ),
-                {
-                    ('dsAttrTypeStandard:FirstName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
-                    ('dsAttrTypeStandard:LastName', 'sagen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
-                }
-            )
-            self.assertEquals(
-                buildQueries(
-                    [
-                        dsattributes.kDSStdRecordTypeUsers,
-                    ],
-                    (
-                        (&quot;firstName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;morgen&quot;, True, &quot;contains&quot;),
-                    ),
-                    OpenDirectoryService._ODFields
-                ),
-                {
-                    ('dsAttrTypeStandard:FirstName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
-                    ('dsAttrTypeStandard:EMailAddress', 'morgen', True, 'contains') : [dsattributes.kDSStdRecordTypeUsers],
-                }
-            )
-            self.assertEquals(
-                buildQueries(
-                    [
-                        dsattributes.kDSStdRecordTypeGroups,
-                    ],
-                    (
-                        (&quot;firstName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;fullName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;morgen&quot;, True, &quot;contains&quot;),
-                    ),
-                    OpenDirectoryService._ODFields
-                ),
-                {
-                    ('dsAttrTypeStandard:RealName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeGroups],
-                    ('dsAttrTypeStandard:EMailAddress', 'morgen', True, 'contains') : [dsattributes.kDSStdRecordTypeGroups],
-                }
-            )
-            self.assertEquals(
-                buildQueries(
-                    [
-                        dsattributes.kDSStdRecordTypeUsers,
-                        dsattributes.kDSStdRecordTypeGroups,
-                    ],
-                    (
-                        (&quot;firstName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;fullName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;morgen&quot;, True, &quot;contains&quot;),
-                    ),
-                    OpenDirectoryService._ODFields
-                ),
-                {
-                    ('dsAttrTypeStandard:RealName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers, dsattributes.kDSStdRecordTypeGroups],
-                    ('dsAttrTypeStandard:EMailAddress', 'morgen', True, 'contains') : [dsattributes.kDSStdRecordTypeUsers, dsattributes.kDSStdRecordTypeGroups],
-                    ('dsAttrTypeStandard:FirstName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
-                    ('dsAttrTypeStandard:LastName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
-                }
-            )
-            self.assertEquals(
-                buildQueries(
-                    [
-                        dsattributes.kDSStdRecordTypeGroups,
-                    ],
-                    (
-                        (&quot;firstName&quot;, &quot;morgen&quot;, True, &quot;starts-with&quot;),
-                    ),
-                    OpenDirectoryService._ODFields
-                ),
-                {
-                }
-            )
-
-
-        def test_buildLocalQueryFromTokens(self):
-            &quot;&quot;&quot;
-            Verify the generating of the simpler queries passed to /Local/Default
-            &quot;&quot;&quot;
-            results = buildLocalQueriesFromTokens([], OpenDirectoryService._ODFields)
-            self.assertEquals(results, None)
-
-            results = buildLocalQueriesFromTokens([&quot;foo&quot;], OpenDirectoryService._ODFields)
-            self.assertEquals(
-                results[0].generate(),
-                &quot;(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*))&quot;
-            )
-
-            results = buildLocalQueriesFromTokens([&quot;foo&quot;, &quot;bar&quot;], OpenDirectoryService._ODFields)
-            self.assertEquals(
-                results[0].generate(),
-                &quot;(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*))&quot;
-            )
-            self.assertEquals(
-                results[1].generate(),
-                &quot;(|(dsAttrTypeStandard:RealName=*bar*)(dsAttrTypeStandard:EMailAddress=bar*))&quot;
-            )
-
-
-        def test_buildNestedQueryFromTokens(self):
-            &quot;&quot;&quot;
-            Verify the generating of the complex nested queries
-            &quot;&quot;&quot;
-            query = buildNestedQueryFromTokens([], OpenDirectoryService._ODFields)
-            self.assertEquals(query, None)
-
-            query = buildNestedQueryFromTokens([&quot;foo&quot;], OpenDirectoryService._ODFields)
-            self.assertEquals(
-                query.generate(),
-                &quot;(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*)(dsAttrTypeStandard:RecordName=foo*))&quot;
-            )
-
-            query = buildNestedQueryFromTokens([&quot;foo&quot;, &quot;bar&quot;], OpenDirectoryService._ODFields)
-            self.assertEquals(
-                query.generate(),
-                &quot;(&amp;(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*)(dsAttrTypeStandard:RecordName=foo*))(|(dsAttrTypeStandard:RealName=*bar*)(dsAttrTypeStandard:EMailAddress=bar*)(dsAttrTypeStandard:RecordName=bar*)))&quot;
-            )
-
-            query = buildNestedQueryFromTokens([&quot;foo&quot;, &quot;bar&quot;, &quot;baz&quot;], OpenDirectoryService._ODFields)
-            self.assertEquals(
-                query.generate(),
-                &quot;(&amp;(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*)(dsAttrTypeStandard:RecordName=foo*))(|(dsAttrTypeStandard:RealName=*bar*)(dsAttrTypeStandard:EMailAddress=bar*)(dsAttrTypeStandard:RecordName=bar*))(|(dsAttrTypeStandard:RealName=*baz*)(dsAttrTypeStandard:EMailAddress=baz*)(dsAttrTypeStandard:RecordName=baz*)))&quot;
-            )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_cachedirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_cachedirectory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_cachedirectory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_cachedirectory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,405 +0,0 @@
</span><del>-#
-# Copyright (c) 2009-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from uuid import uuid4
-
-from twistedcaldav.directory.cachingdirectory import CachingDirectoryService
-from twistedcaldav.directory.cachingdirectory import CachingDirectoryRecord
-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.util import uuidFromName
-from twistedcaldav.directory.augment import AugmentRecord
-from twistedcaldav.test.util import TestCase
-from twistedcaldav.config import config
-
-
-class TestDirectoryService (CachingDirectoryService):
-
-    realmName = &quot;Dummy Realm&quot;
-    baseGUID = &quot;20CB1593-DE3F-4422-A7D7-BA9C2099B317&quot;
-
-    def recordTypes(self):
-        return (
-            DirectoryService.recordType_users,
-            DirectoryService.recordType_groups,
-            DirectoryService.recordType_locations,
-            DirectoryService.recordType_resources,
-        )
-
-
-    def queryDirectory(self, recordTypes, indexType, indexKey):
-
-        self.queried = True
-
-        for recordType in recordTypes:
-            for record in self.fakerecords[recordType]:
-                cacheIt = False
-                if indexType in (
-                    CachingDirectoryService.INDEX_TYPE_SHORTNAME,
-                    CachingDirectoryService.INDEX_TYPE_CUA,
-                    CachingDirectoryService.INDEX_TYPE_AUTHID,
-                ):
-                    if indexKey in record[indexType]:
-                        cacheIt = True
-                else:
-                    if indexKey == record[indexType]:
-                        cacheIt = True
-
-                if cacheIt:
-                    cacheRecord = CachingDirectoryRecord(
-                        service=self,
-                        recordType=recordType,
-                        guid=record.get(&quot;guid&quot;),
-                        shortNames=record.get(&quot;shortname&quot;),
-                        authIDs=record.get(&quot;authid&quot;),
-                        fullName=record.get(&quot;fullName&quot;),
-                        firstName=&quot;&quot;,
-                        lastName=&quot;&quot;,
-                        emailAddresses=record.get(&quot;email&quot;),
-                    )
-
-                    augmentRecord = AugmentRecord(
-                        uid=cacheRecord.guid,
-                        enabled=True,
-                        enabledForCalendaring=True,
-                    )
-
-                    cacheRecord.addAugmentInformation(augmentRecord)
-
-                    self.recordCacheForType(recordType).addRecord(cacheRecord,
-                        indexType, indexKey)
-
-
-
-class CachingDirectoryTest(TestCase):
-
-    baseGUID = str(uuid4())
-
-
-    def setUp(self):
-        super(CachingDirectoryTest, self).setUp()
-        self.service = TestDirectoryService()
-        self.service.queried = False
-
-
-    def loadRecords(self, records):
-        self.service._initCaches()
-        self.service.fakerecords = records
-        self.service.queried = False
-
-
-    def fakeRecord(
-        self,
-        fullName,
-        recordType,
-        shortNames=None,
-        guid=None,
-        emails=None,
-        members=None,
-        resourceInfo=None,
-        multinames=False
-    ):
-        if shortNames is None:
-            shortNames = (self.shortNameForFullName(fullName),)
-            if multinames:
-                shortNames += (fullName,)
-
-        if guid is None:
-            guid = self.guidForShortName(shortNames[0], recordType=recordType)
-        else:
-            guid = guid.lower()
-
-        if emails is None:
-            emails = (&quot;%s@example.com&quot; % (shortNames[0],),)
-
-        attrs = {
-            &quot;fullName&quot;: fullName,
-            &quot;guid&quot;: guid,
-            &quot;shortname&quot;: shortNames,
-            &quot;email&quot;: emails,
-            &quot;cua&quot;: tuple([&quot;mailto:%s&quot; % email for email in emails]),
-            &quot;authid&quot;: tuple([&quot;Kerberos:%s&quot; % email for email in emails])
-        }
-
-        if members:
-            attrs[&quot;members&quot;] = members
-
-        if resourceInfo:
-            attrs[&quot;resourceInfo&quot;] = resourceInfo
-
-        return attrs
-
-
-    def shortNameForFullName(self, fullName):
-        return fullName.lower().replace(&quot; &quot;, &quot;&quot;)
-
-
-    def guidForShortName(self, shortName, recordType=&quot;&quot;):
-        return uuidFromName(self.baseGUID, &quot;%s%s&quot; % (recordType, shortName))
-
-
-    def dummyRecords(self):
-        SIZE = 10
-        records = {
-            DirectoryService.recordType_users: [
-                self.fakeRecord(&quot;User %02d&quot; % x, DirectoryService.recordType_users, multinames=(x &gt; 5)) for x in range(1, SIZE + 1)
-            ],
-            DirectoryService.recordType_groups: [
-                self.fakeRecord(&quot;Group %02d&quot; % x, DirectoryService.recordType_groups) for x in range(1, SIZE + 1)
-            ],
-            DirectoryService.recordType_resources: [
-                self.fakeRecord(&quot;Resource %02d&quot; % x, DirectoryService.recordType_resources) for x in range(1, SIZE + 1)
-            ],
-            DirectoryService.recordType_locations: [
-                self.fakeRecord(&quot;Location %02d&quot; % x, DirectoryService.recordType_locations) for x in range(1, SIZE + 1)
-            ],
-        }
-        # Add duplicate shortnames
-        records[DirectoryService.recordType_users].append(self.fakeRecord(&quot;Duplicate&quot;, DirectoryService.recordType_users, multinames=True))
-        records[DirectoryService.recordType_groups].append(self.fakeRecord(&quot;Duplicate&quot;, DirectoryService.recordType_groups, multinames=True))
-        records[DirectoryService.recordType_resources].append(self.fakeRecord(&quot;Duplicate&quot;, DirectoryService.recordType_resources, multinames=True))
-        records[DirectoryService.recordType_locations].append(self.fakeRecord(&quot;Duplicate&quot;, DirectoryService.recordType_locations, multinames=True))
-
-        self.loadRecords(records)
-
-
-    def verifyRecords(self, recordType, expectedGUIDs):
-
-        records = self.service.listRecords(recordType)
-        recordGUIDs = set([record.guid for record in records])
-        self.assertEqual(recordGUIDs, expectedGUIDs)
-
-
-
-class GUIDLookups(CachingDirectoryTest):
-
-    def test_emptylist(self):
-        self.dummyRecords()
-
-        self.verifyRecords(DirectoryService.recordType_users, set())
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-
-    def test_cacheoneguid(self):
-        self.dummyRecords()
-
-        self.assertTrue(self.service.recordWithGUID(self.guidForShortName(&quot;user01&quot;, recordType=DirectoryService.recordType_users)) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user01&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithGUID(self.guidForShortName(&quot;user01&quot;, recordType=DirectoryService.recordType_users)) is not None)
-        self.assertFalse(self.service.queried)
-
-        # Make sure guid is case-insensitive
-        self.assertTrue(self.service.recordWithGUID(self.guidForShortName(&quot;user01&quot;, recordType=DirectoryService.recordType_users).lower()) is not None)
-
-
-    def test_cacheoneshortname(self):
-        self.dummyRecords()
-
-        self.assertTrue(self.service.recordWithShortName(
-            DirectoryService.recordType_users,
-            &quot;user02&quot;
-        ) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user02&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithShortName(
-            DirectoryService.recordType_users,
-            &quot;user02&quot;
-        ) is not None)
-        self.assertFalse(self.service.queried)
-
-
-    def test_cacheoneemail(self):
-        self.dummyRecords()
-
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;mailto:user03@example.com&quot;
-        ) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user03&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;mailto:user03@example.com&quot;
-        ) is not None)
-        self.assertFalse(self.service.queried)
-
-
-    def test_cacheonePrincipalsURLWithUIDS(self):
-        self.dummyRecords()
-
-        guid = self.guidForShortName(&quot;user03&quot;, &quot;users&quot;)
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;/principals/__uids__/%s&quot; % (guid,)
-        ) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user03&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;/principals/__uids__/%s&quot; % (guid,)
-        ) is not None)
-        self.assertFalse(self.service.queried)
-
-
-    def test_cacheonePrincipalsURLWithUsers(self):
-        self.dummyRecords()
-
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;/principals/users/user03&quot;
-        ) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user03&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithCalendarUserAddress(
-            &quot;/principals/users/user03&quot;
-        ) is not None)
-        self.assertFalse(self.service.queried)
-
-
-    def test_cacheoneauthid(self):
-        self.dummyRecords()
-
-        self.assertTrue(self.service.recordWithAuthID(
-            &quot;Kerberos:user03@example.com&quot;
-        ) is not None)
-        self.assertTrue(self.service.queried)
-        self.verifyRecords(DirectoryService.recordType_users, set((
-            self.guidForShortName(&quot;user03&quot;, recordType=DirectoryService.recordType_users),
-        )))
-        self.verifyRecords(DirectoryService.recordType_groups, set())
-        self.verifyRecords(DirectoryService.recordType_resources, set())
-        self.verifyRecords(DirectoryService.recordType_locations, set())
-
-        # Make sure it really is cached and won't cause another query
-        self.service.queried = False
-        self.assertTrue(self.service.recordWithAuthID(
-            &quot;Kerberos:user03@example.com&quot;
-        ) is not None)
-        self.assertFalse(self.service.queried)
-
-
-    def test_negativeCaching(self):
-        self.dummyRecords()
-
-        # If negativeCaching is off, each miss will result in a call to
-        # queryDirectory( )
-        self.service.negativeCaching = False
-
-        self.service.queried = False
-        self.assertEquals(self.service.recordWithGUID(self.guidForShortName(&quot;missing&quot;)), None)
-        self.assertTrue(self.service.queried)
-
-        self.service.queried = False
-        self.assertEquals(self.service.recordWithGUID(self.guidForShortName(&quot;missing&quot;)), None)
-        self.assertTrue(self.service.queried)
-
-        # However, if negativeCaching is on, a miss is recorded as such,
-        # preventing a similar queryDirectory( ) until cacheTimeout passes
-        self.service.negativeCaching = True
-
-        self.service.queried = False
-        self.assertEquals(self.service.recordWithGUID(self.guidForShortName(&quot;missing&quot;)), None)
-        self.assertTrue(self.service.queried)
-
-        self.service.queried = False
-        self.assertEquals(self.service.recordWithGUID(self.guidForShortName(&quot;missing&quot;)), None)
-        self.assertFalse(self.service.queried)
-
-        # Simulate time passing by clearing the negative timestamp for this
-        # entry, then try again, this time queryDirectory( ) is called
-        self.service._disabledKeys[self.service.INDEX_TYPE_GUID][self.guidForShortName(&quot;missing&quot;)] = 0
-
-        self.service.queried = False
-        self.assertEquals(self.service.recordWithGUID(self.guidForShortName(&quot;missing&quot;)), None)
-        self.assertTrue(self.service.queried)
-
-
-    def test_duplicateShortNames(self):
-        &quot;&quot;&quot;
-        Verify that when looking up records having duplicate short-names, the record of the
-        proper type is returned
-        &quot;&quot;&quot;
-
-        self.patch(config.Memcached.Pools.Default, &quot;ClientEnabled&quot;, True)
-        self.dummyRecords()
-
-        record = self.service.recordWithShortName(DirectoryService.recordType_users,
-            &quot;Duplicate&quot;)
-        self.assertEquals(record.recordType, DirectoryService.recordType_users)
-
-        record = self.service.recordWithShortName(DirectoryService.recordType_groups,
-            &quot;Duplicate&quot;)
-        self.assertEquals(record.recordType, DirectoryService.recordType_groups)
-
-        record = self.service.recordWithShortName(DirectoryService.recordType_resources,
-            &quot;Duplicate&quot;)
-        self.assertEquals(record.recordType, DirectoryService.recordType_resources)
-
-        record = self.service.recordWithShortName(DirectoryService.recordType_locations,
-            &quot;Duplicate&quot;)
-        self.assertEquals(record.recordType, DirectoryService.recordType_locations)
-
-
-    def test_generateMemcacheKey(self):
-        &quot;&quot;&quot;
-        Verify keys are correctly generated based on the index type -- if index type is
-        short-name, then the recordtype is encoded into the key.
-        &quot;&quot;&quot;
-        self.assertEquals(
-            self.service.generateMemcacheKey(self.service.INDEX_TYPE_GUID, &quot;foo&quot;, &quot;users&quot;),
-            &quot;dir|v2|20CB1593-DE3F-4422-A7D7-BA9C2099B317|guid|foo&quot;,
-        )
-        self.assertEquals(
-            self.service.generateMemcacheKey(self.service.INDEX_TYPE_SHORTNAME, &quot;foo&quot;, &quot;users&quot;),
-            &quot;dir|v2|20CB1593-DE3F-4422-A7D7-BA9C2099B317|users|shortname|foo&quot;,
-        )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_directorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_directory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_directory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_directory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,1201 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks
-from twisted.python.filepath import FilePath
-
-from twistedcaldav.test.util import TestCase
-from twistedcaldav.test.util import xmlFile, augmentsFile, proxiesFile, dirTest
-from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, GroupMembershipCache, GroupMembershipCacheUpdater, diffAssignments
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
-from twistedcaldav.directory import augment, calendaruserproxy
-from twistedcaldav.directory.util import normalizeUUID
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-
-import cPickle as pickle
-import uuid
-
-def StubCheckSACL(cls, username, service):
-    services = {
-        &quot;calendar&quot; : [&quot;amanda&quot;, &quot;betty&quot;],
-        &quot;addressbook&quot; : [&quot;amanda&quot;, &quot;carlene&quot;],
-    }
-    if username in services[service]:
-        return 0
-    return 1
-
-
-
-class SACLTests(TestCase):
-
-    def setUp(self):
-        self.patch(DirectoryRecord, &quot;CheckSACL&quot;, StubCheckSACL)
-        self.patch(config, &quot;EnableSACLs&quot;, True)
-        self.service = DirectoryService()
-        self.service.setRealm(&quot;test&quot;)
-        self.service.baseGUID = &quot;0E8E6EC2-8E52-4FF3-8F62-6F398B08A498&quot;
-
-
-    def test_applySACLs(self):
-        &quot;&quot;&quot;
-        Users not in calendar SACL will have enabledForCalendaring set to
-        False.
-        Users not in addressbook SACL will have enabledForAddressBooks set to
-        False.
-        &quot;&quot;&quot;
-
-        data = [
-            (&quot;amanda&quot;, True, True,),
-            (&quot;betty&quot;, True, False,),
-            (&quot;carlene&quot;, False, True,),
-            (&quot;daniel&quot;, False, False,),
-        ]
-        for username, cal, ab in data:
-            record = DirectoryRecord(self.service, &quot;users&quot;, None, (username,),
-                enabledForCalendaring=True, enabledForAddressBooks=True)
-            record.applySACLs()
-            self.assertEquals(record.enabledForCalendaring, cal)
-            self.assertEquals(record.enabledForAddressBooks, ab)
-
-
-
-class GroupMembershipTests (TestCase):
-
-    @inlineCallbacks
-    def setUp(self):
-        super(GroupMembershipTests, self).setUp()
-
-        self.directoryFixture.addDirectoryService(XMLDirectoryService(
-            {
-                'xmlFile' : xmlFile,
-                'augmentService' :
-                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-            }
-        ))
-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(&quot;proxies.sqlite&quot;)
-
-        # Set up a principals hierarchy for each service we're testing with
-        self.principalRootResources = {}
-        name = self.directoryService.__class__.__name__
-        url = &quot;/&quot; + name + &quot;/&quot;
-
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
-
-        self.site.resource.putChild(name, provisioningResource)
-
-        self.principalRootResources[self.directoryService.__class__.__name__] = provisioningResource
-
-        yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
-
-
-    def tearDown(self):
-        &quot;&quot;&quot; Empty the proxy db between tests &quot;&quot;&quot;
-        return calendaruserproxy.ProxyDBService.clean() #@UndefinedVariable
-
-
-    def _getPrincipalByShortName(self, type, name):
-        provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
-        return provisioningResource.principalForShortName(type, name)
-
-
-    def _updateMethod(self):
-        &quot;&quot;&quot;
-        Update a counter in the following test
-        &quot;&quot;&quot;
-        self.count += 1
-
-
-    def test_expandedMembers(self):
-        &quot;&quot;&quot;
-        Make sure expandedMembers( ) returns a complete, flattened set of
-        members of a group, including all sub-groups.
-        &quot;&quot;&quot;
-        bothCoasts = self.directoryService.recordWithShortName(
-            DirectoryService.recordType_groups, &quot;both_coasts&quot;)
-        self.assertEquals(
-            set([r.guid for r in bothCoasts.expandedMembers()]),
-            set(['8B4288F6-CC82-491D-8EF9-642EF4F3E7D0',
-                 '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                 '5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                 '5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
-                 'left_coast',
-                 'right_coast'])
-        )
-
-
-    @inlineCallbacks
-    def test_groupMembershipCache(self):
-        &quot;&quot;&quot;
-        Ensure we get back what we put in
-        &quot;&quot;&quot;
-        cache = GroupMembershipCache(&quot;ProxyDB&quot;, expireSeconds=10)
-
-        yield cache.setGroupsFor(&quot;a&quot;, set([&quot;b&quot;, &quot;c&quot;, &quot;d&quot;])) # a is in b, c, d
-        members = (yield cache.getGroupsFor(&quot;a&quot;))
-        self.assertEquals(members, set([&quot;b&quot;, &quot;c&quot;, &quot;d&quot;]))
-
-        yield cache.setGroupsFor(&quot;b&quot;, set()) # b not in any groups
-        members = (yield cache.getGroupsFor(&quot;b&quot;))
-        self.assertEquals(members, set())
-
-        cache._memcacheProtocol.advanceClock(10)
-
-        members = (yield cache.getGroupsFor(&quot;a&quot;)) # has expired
-        self.assertEquals(members, set())
-
-
-    @inlineCallbacks
-    def test_groupMembershipCacheUpdater(self):
-        &quot;&quot;&quot;
-        Let the GroupMembershipCacheUpdater populate the cache, then make
-        sure proxyFor( ) and groupMemberships( ) work from the cache
-        &quot;&quot;&quot;
-        cache = GroupMembershipCache(&quot;ProxyDB&quot;, expireSeconds=60)
-        # Having a groupMembershipCache assigned to the directory service is the
-        # trigger to use such a cache:
-        self.directoryService.groupMembershipCache = cache
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache, useExternalProxies=False)
-
-        # Exercise getGroups()
-        groups, aliases = (yield updater.getGroups())
-        self.assertEquals(
-            groups,
-            {
-                '00599DAF-3E75-42DD-9DB7-52617E79943F':
-                    set(['46D9D716-CBEE-490F-907A-66FA6C3767FF']),
-                '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1':
-                    set(['8B4288F6-CC82-491D-8EF9-642EF4F3E7D0']),
-                'admin':
-                    set(['9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1']),
-                'both_coasts':
-                    set(['left_coast', 'right_coast']),
-                'grunts':
-                    set(['5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                         '5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
-                         '6423F94A-6B76-4A3A-815B-D52CFD77935D']),
-                'left_coast':
-                    set(['5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
-                         '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                         '8B4288F6-CC82-491D-8EF9-642EF4F3E7D0']),
-                'non_calendar_group':
-                    set(['5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                         '8B4288F6-CC82-491D-8EF9-642EF4F3E7D0']),
-                'recursive1_coasts':
-                    set(['6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                         'recursive2_coasts']),
-                'recursive2_coasts':
-                    set(['5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                         'recursive1_coasts']),
-                'right_coast':
-                    set(['5A985493-EE2C-4665-94CF-4DFEA3A89500'])
-            }
-        )
-        self.assertEquals(
-            aliases,
-            {
-                '00599DAF-3E75-42DD-9DB7-52617E79943F':
-                    '00599DAF-3E75-42DD-9DB7-52617E79943F',
-                '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1':
-                    '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
-                 'admin': 'admin',
-                 'both_coasts': 'both_coasts',
-                 'grunts': 'grunts',
-                 'left_coast': 'left_coast',
-                 'non_calendar_group': 'non_calendar_group',
-                 'recursive1_coasts': 'recursive1_coasts',
-                 'recursive2_coasts': 'recursive2_coasts',
-                 'right_coast': 'right_coast'
-            }
-        )
-
-        # Exercise expandedMembers()
-        self.assertEquals(
-            updater.expandedMembers(groups, &quot;both_coasts&quot;),
-            set(['5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                 '5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
-                 '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                 '8B4288F6-CC82-491D-8EF9-642EF4F3E7D0',
-                 'left_coast',
-                 'right_coast']
-            )
-        )
-
-        # Prevent an update by locking the cache
-        acquiredLock = (yield cache.acquireLock())
-        self.assertTrue(acquiredLock)
-        self.assertEquals((False, 0, 0), (yield updater.updateCache()))
-
-        # You can't lock when already locked:
-        acquiredLockAgain = (yield cache.acquireLock())
-        self.assertFalse(acquiredLockAgain)
-
-        # Allow an update by unlocking the cache
-        yield cache.releaseLock()
-
-        self.assertEquals((False, 9, 9), (yield updater.updateCache()))
-
-        # Verify cache is populated:
-        self.assertTrue((yield cache.isPopulated()))
-
-        delegates = (
-
-            # record name
-            # read-write delegators
-            # read-only delegators
-            # groups delegate is in (restricted to only those groups
-            #   participating in delegation)
-
-            (&quot;wsanchez&quot;,
-             set([&quot;mercury&quot;, &quot;apollo&quot;, &quot;orion&quot;, &quot;gemini&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['left_coast',
-                  'both_coasts',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'gemini#calendar-proxy-write',
-                ]),
-            ),
-            (&quot;cdaboo&quot;,
-             set([&quot;apollo&quot;, &quot;orion&quot;, &quot;non_calendar_proxy&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['both_coasts',
-                  'non_calendar_group',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                ]),
-            ),
-            (&quot;lecroy&quot;,
-             set([&quot;apollo&quot;, &quot;mercury&quot;, &quot;non_calendar_proxy&quot;]),
-             set(),
-             set(['both_coasts',
-                  'left_coast',
-                  'non_calendar_group',
-                ]),
-            ),
-            (&quot;usera&quot;,
-             set(),
-             set(),
-             set(),
-            ),
-            (&quot;userb&quot;,
-             set(['7423F94A-6B76-4A3A-815B-D52CFD77935D']),
-             set(),
-             set(['7423F94A-6B76-4A3A-815B-D52CFD77935D#calendar-proxy-write']),
-            ),
-            (&quot;userc&quot;,
-             set(['7423F94A-6B76-4A3A-815B-D52CFD77935D']),
-             set(),
-             set(['7423F94A-6B76-4A3A-815B-D52CFD77935D#calendar-proxy-write']),
-            ),
-        )
-
-        for name, write, read, groups in delegates:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-
-            proxyFor = (yield delegate.proxyFor(True))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                write,
-            )
-            proxyFor = (yield delegate.proxyFor(False))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                read,
-            )
-            groupsIn = (yield delegate.groupMemberships())
-            uids = set()
-            for group in groupsIn:
-                try:
-                    uid = group.uid # a sub-principal
-                except AttributeError:
-                    uid = group.record.guid # a regular group
-                uids.add(uid)
-            self.assertEquals(
-                set(uids),
-                groups,
-            )
-
-        # Verify CalendarUserProxyPrincipalResource.containsPrincipal( ) works
-        delegator = self._getPrincipalByShortName(DirectoryService.recordType_locations, &quot;mercury&quot;)
-        proxyPrincipal = delegator.getChild(&quot;calendar-proxy-write&quot;)
-        for expected, name in [(True, &quot;wsanchez&quot;), (False, &quot;cdaboo&quot;)]:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-            self.assertEquals(expected, (yield proxyPrincipal.containsPrincipal(delegate)))
-
-        # Verify that principals who were previously members of delegated-to groups but
-        # are no longer members have their proxyFor info cleaned out of the cache:
-        # Remove wsanchez from all groups in the directory, run the updater, then check
-        # that wsanchez is only a proxy for gemini (since that assignment does not involve groups)
-        self.directoryService.xmlFile = dirTest.child(&quot;accounts-modified.xml&quot;)
-        self.directoryService._alwaysStat = True
-        self.assertEquals((False, 8, 1), (yield updater.updateCache()))
-        delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, &quot;wsanchez&quot;)
-        proxyFor = (yield delegate.proxyFor(True))
-        self.assertEquals(
-          set([p.record.guid for p in proxyFor]),
-          set(['gemini'])
-        )
-
-
-    @inlineCallbacks
-    def test_groupMembershipCacheUpdaterExternalProxies(self):
-        &quot;&quot;&quot;
-        Exercise external proxy assignment support (assignments come from the
-        directory service itself)
-        &quot;&quot;&quot;
-        cache = GroupMembershipCache(&quot;ProxyDB&quot;, expireSeconds=60)
-        # Having a groupMembershipCache assigned to the directory service is the
-        # trigger to use such a cache:
-        self.directoryService.groupMembershipCache = cache
-
-        # This time, we're setting some external proxy assignments for the
-        # &quot;transporter&quot; resource...
-        def fakeExternalProxies():
-            return [
-                (
-                    &quot;transporter#calendar-proxy-write&quot;,
-                    set([&quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;,
-                         &quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;])
-                ),
-                (
-                    &quot;transporter#calendar-proxy-read&quot;,
-                    set([&quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;])
-                ),
-            ]
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache, useExternalProxies=True,
-            externalProxiesSource=fakeExternalProxies)
-
-        yield updater.updateCache()
-
-        delegates = (
-
-            # record name
-            # read-write delegators
-            # read-only delegators
-            # groups delegate is in (restricted to only those groups
-            #   participating in delegation)
-
-            (&quot;wsanchez&quot;,
-             set([&quot;mercury&quot;, &quot;apollo&quot;, &quot;orion&quot;, &quot;gemini&quot;, &quot;transporter&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['left_coast',
-                  'both_coasts',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'gemini#calendar-proxy-write',
-                  'transporter#calendar-proxy-write',
-                ]),
-            ),
-            (&quot;cdaboo&quot;,
-             set([&quot;apollo&quot;, &quot;orion&quot;, &quot;non_calendar_proxy&quot;]),
-             set([&quot;non_calendar_proxy&quot;, &quot;transporter&quot;]),
-             set(['both_coasts',
-                  'non_calendar_group',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'transporter#calendar-proxy-read',
-                ]),
-            ),
-            (&quot;lecroy&quot;,
-             set([&quot;apollo&quot;, &quot;mercury&quot;, &quot;non_calendar_proxy&quot;, &quot;transporter&quot;]),
-             set(),
-             set(['both_coasts',
-                  'left_coast',
-                  'non_calendar_group',
-                  'transporter#calendar-proxy-write',
-                ]),
-            ),
-        )
-
-        for name, write, read, groups in delegates:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-
-            proxyFor = (yield delegate.proxyFor(True))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                write,
-            )
-            proxyFor = (yield delegate.proxyFor(False))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                read,
-            )
-            groupsIn = (yield delegate.groupMemberships())
-            uids = set()
-            for group in groupsIn:
-                try:
-                    uid = group.uid # a sub-principal
-                except AttributeError:
-                    uid = group.record.guid # a regular group
-                uids.add(uid)
-            self.assertEquals(
-                set(uids),
-                groups,
-            )
-
-        #
-        # Now remove two external assignments, and those should take effect.
-        #
-        def fakeExternalProxiesRemoved():
-            return [
-                (
-                    &quot;transporter#calendar-proxy-write&quot;,
-                    set([&quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;])
-                ),
-            ]
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache, useExternalProxies=True,
-            externalProxiesSource=fakeExternalProxiesRemoved)
-
-        yield updater.updateCache()
-
-        delegates = (
-
-            # record name
-            # read-write delegators
-            # read-only delegators
-            # groups delegate is in (restricted to only those groups
-            #   participating in delegation)
-
-            # Note: &quot;transporter&quot; is now gone for wsanchez and cdaboo
-
-            (&quot;wsanchez&quot;,
-             set([&quot;mercury&quot;, &quot;apollo&quot;, &quot;orion&quot;, &quot;gemini&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['left_coast',
-                  'both_coasts',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'gemini#calendar-proxy-write',
-                ]),
-            ),
-            (&quot;cdaboo&quot;,
-             set([&quot;apollo&quot;, &quot;orion&quot;, &quot;non_calendar_proxy&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['both_coasts',
-                  'non_calendar_group',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                ]),
-            ),
-            (&quot;lecroy&quot;,
-             set([&quot;apollo&quot;, &quot;mercury&quot;, &quot;non_calendar_proxy&quot;, &quot;transporter&quot;]),
-             set(),
-             set(['both_coasts',
-                  'left_coast',
-                  'non_calendar_group',
-                  'transporter#calendar-proxy-write',
-                ]),
-            ),
-        )
-
-        for name, write, read, groups in delegates:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-
-            proxyFor = (yield delegate.proxyFor(True))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                write,
-            )
-            proxyFor = (yield delegate.proxyFor(False))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                read,
-            )
-            groupsIn = (yield delegate.groupMemberships())
-            uids = set()
-            for group in groupsIn:
-                try:
-                    uid = group.uid # a sub-principal
-                except AttributeError:
-                    uid = group.record.guid # a regular group
-                uids.add(uid)
-            self.assertEquals(
-                set(uids),
-                groups,
-            )
-
-        #
-        # Now remove all external assignments, and those should take effect.
-        #
-        def fakeExternalProxiesEmpty():
-            return []
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache, useExternalProxies=True,
-            externalProxiesSource=fakeExternalProxiesEmpty)
-
-        yield updater.updateCache()
-
-        delegates = (
-
-            # record name
-            # read-write delegators
-            # read-only delegators
-            # groups delegate is in (restricted to only those groups
-            #   participating in delegation)
-
-            # Note: &quot;transporter&quot; is now gone for everyone
-
-            (&quot;wsanchez&quot;,
-             set([&quot;mercury&quot;, &quot;apollo&quot;, &quot;orion&quot;, &quot;gemini&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['left_coast',
-                  'both_coasts',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'gemini#calendar-proxy-write',
-                ]),
-            ),
-            (&quot;cdaboo&quot;,
-             set([&quot;apollo&quot;, &quot;orion&quot;, &quot;non_calendar_proxy&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['both_coasts',
-                  'non_calendar_group',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                ]),
-            ),
-            (&quot;lecroy&quot;,
-             set([&quot;apollo&quot;, &quot;mercury&quot;, &quot;non_calendar_proxy&quot;]),
-             set(),
-             set(['both_coasts',
-                  'left_coast',
-                      'non_calendar_group',
-                ]),
-            ),
-        )
-
-        for name, write, read, groups in delegates:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-
-            proxyFor = (yield delegate.proxyFor(True))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                write,
-            )
-            proxyFor = (yield delegate.proxyFor(False))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                read,
-            )
-            groupsIn = (yield delegate.groupMemberships())
-            uids = set()
-            for group in groupsIn:
-                try:
-                    uid = group.uid # a sub-principal
-                except AttributeError:
-                    uid = group.record.guid # a regular group
-                uids.add(uid)
-            self.assertEquals(
-                set(uids),
-                groups,
-            )
-
-        #
-        # Now add back an external assignments, and those should take effect.
-        #
-        def fakeExternalProxiesAdded():
-            return [
-                (
-                    &quot;transporter#calendar-proxy-write&quot;,
-                    set([&quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;])
-                ),
-            ]
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache, useExternalProxies=True,
-            externalProxiesSource=fakeExternalProxiesAdded)
-
-        yield updater.updateCache()
-
-        delegates = (
-
-            # record name
-            # read-write delegators
-            # read-only delegators
-            # groups delegate is in (restricted to only those groups
-            #   participating in delegation)
-
-            (&quot;wsanchez&quot;,
-             set([&quot;mercury&quot;, &quot;apollo&quot;, &quot;orion&quot;, &quot;gemini&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['left_coast',
-                  'both_coasts',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                  'gemini#calendar-proxy-write',
-                ]),
-            ),
-            (&quot;cdaboo&quot;,
-             set([&quot;apollo&quot;, &quot;orion&quot;, &quot;non_calendar_proxy&quot;]),
-             set([&quot;non_calendar_proxy&quot;]),
-             set(['both_coasts',
-                  'non_calendar_group',
-                  'recursive1_coasts',
-                  'recursive2_coasts',
-                ]),
-            ),
-            (&quot;lecroy&quot;,
-             set([&quot;apollo&quot;, &quot;mercury&quot;, &quot;non_calendar_proxy&quot;, &quot;transporter&quot;]),
-             set(),
-             set(['both_coasts',
-                  'left_coast',
-                  'non_calendar_group',
-                  'transporter#calendar-proxy-write',
-                ]),
-            ),
-        )
-
-        for name, write, read, groups in delegates:
-            delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, name)
-
-            proxyFor = (yield delegate.proxyFor(True))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                write,
-            )
-            proxyFor = (yield delegate.proxyFor(False))
-            self.assertEquals(
-                set([p.record.guid for p in proxyFor]),
-                read,
-            )
-            groupsIn = (yield delegate.groupMemberships())
-            uids = set()
-            for group in groupsIn:
-                try:
-                    uid = group.uid # a sub-principal
-                except AttributeError:
-                    uid = group.record.guid # a regular group
-                uids.add(uid)
-            self.assertEquals(
-                set(uids),
-                groups,
-            )
-
-
-    def test_diffAssignments(self):
-        &quot;&quot;&quot;
-        Ensure external proxy assignment diffing works
-        &quot;&quot;&quot;
-
-        self.assertEquals(
-            (
-                # changed
-                [],
-                # removed
-                [],
-            ),
-            diffAssignments(
-                # old
-                [],
-                # new
-                [],
-            )
-        )
-
-        self.assertEquals(
-            (
-                # changed
-                [],
-                # removed
-                [],
-            ),
-            diffAssignments(
-                # old
-                [(&quot;B&quot;, set([&quot;3&quot;])), (&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), ],
-                # new
-                [(&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), (&quot;B&quot;, set([&quot;3&quot;])), ],
-            )
-        )
-
-        self.assertEquals(
-            (
-                # changed
-                [(&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), (&quot;B&quot;, set([&quot;3&quot;])), ],
-                # removed
-                [],
-            ),
-            diffAssignments(
-                # old
-                [],
-                # new
-                [(&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), (&quot;B&quot;, set([&quot;3&quot;])), ],
-            )
-        )
-
-        self.assertEquals(
-            (
-                # changed
-                [],
-                # removed
-                [&quot;A&quot;, &quot;B&quot;],
-            ),
-            diffAssignments(
-                # old
-                [(&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), (&quot;B&quot;, set([&quot;3&quot;])), ],
-                # new
-                [],
-            )
-        )
-
-        self.assertEquals(
-            (
-                # changed
-                [(&quot;A&quot;, set([&quot;2&quot;])), (&quot;C&quot;, set([&quot;4&quot;, &quot;5&quot;])), (&quot;D&quot;, set([&quot;6&quot;])), ],
-                # removed
-                [&quot;B&quot;],
-            ),
-            diffAssignments(
-                # old
-                [(&quot;A&quot;, set([&quot;1&quot;, &quot;2&quot;])), (&quot;B&quot;, set([&quot;3&quot;])), (&quot;C&quot;, set([&quot;4&quot;])), ],
-                # new
-                [(&quot;D&quot;, set([&quot;6&quot;])), (&quot;C&quot;, set([&quot;4&quot;, &quot;5&quot;])), (&quot;A&quot;, set([&quot;2&quot;])), ],
-            )
-        )
-
-
-    @inlineCallbacks
-    def test_groupMembershipCacheSnapshot(self):
-        &quot;&quot;&quot;
-        The group membership cache creates a snapshot (a pickle file) of
-        the member -&gt; groups dictionary, and can quickly refresh memcached
-        from that snapshot when restarting the server.
-        &quot;&quot;&quot;
-        cache = GroupMembershipCache(&quot;ProxyDB&quot;, expireSeconds=60)
-        # Having a groupMembershipCache assigned to the directory service is the
-        # trigger to use such a cache:
-        self.directoryService.groupMembershipCache = cache
-
-        updater = GroupMembershipCacheUpdater(
-            calendaruserproxy.ProxyDBService, self.directoryService, 30, 30, 30,
-            cache=cache)
-
-        dataRoot = FilePath(config.DataRoot)
-        snapshotFile = dataRoot.child(&quot;memberships_cache&quot;)
-
-        # Snapshot doesn't exist initially
-        self.assertFalse(snapshotFile.exists())
-
-        # Try a fast update (as when the server starts up for the very first
-        # time), but since the snapshot doesn't exist we fault in from the
-        # directory (fast now is False), and snapshot will get created
-
-        # Note that because fast=True and isPopulated() is False, locking is
-        # ignored:
-        yield cache.acquireLock()
-
-        self.assertFalse((yield cache.isPopulated()))
-        fast, numMembers, numChanged = (yield updater.updateCache(fast=True))
-        self.assertEquals(fast, False)
-        self.assertEquals(numMembers, 9)
-        self.assertEquals(numChanged, 9)
-        self.assertTrue(snapshotFile.exists())
-        self.assertTrue((yield cache.isPopulated()))
-
-        yield cache.releaseLock()
-
-        # Try another fast update where the snapshot already exists (as in a
-        # server-restart scenario), which will only read from the snapshot
-        # as indicated by the return value for &quot;fast&quot;.  Note that the cache
-        # is already populated so updateCache( ) in fast mode will not do
-        # anything, and numMembers will be 0.
-        fast, numMembers, numChanged = (yield updater.updateCache(fast=True))
-        self.assertEquals(fast, True)
-        self.assertEquals(numMembers, 0)
-
-        # Try an update which faults in from the directory (fast=False)
-        fast, numMembers, numChanged = (yield updater.updateCache(fast=False))
-        self.assertEquals(fast, False)
-        self.assertEquals(numMembers, 9)
-        self.assertEquals(numChanged, 0)
-
-        # Verify the snapshot contains the pickled dictionary we expect
-        expected = {
-            &quot;46D9D716-CBEE-490F-907A-66FA6C3767FF&quot;:
-                set([
-                    u&quot;00599DAF-3E75-42DD-9DB7-52617E79943F&quot;,
-                ]),
-            &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;:
-                set([
-                    u&quot;non_calendar_group&quot;,
-                    u&quot;recursive1_coasts&quot;,
-                    u&quot;recursive2_coasts&quot;,
-                    u&quot;both_coasts&quot;
-                ]),
-            &quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;:
-                set([
-                    u&quot;left_coast&quot;,
-                    u&quot;recursive1_coasts&quot;,
-                    u&quot;recursive2_coasts&quot;,
-                    u&quot;both_coasts&quot;
-                ]),
-            &quot;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&quot;:
-                set([
-                    u&quot;left_coast&quot;,
-                    u&quot;both_coasts&quot;
-                ]),
-            &quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;:
-                set([
-                    u&quot;non_calendar_group&quot;,
-                    u&quot;left_coast&quot;,
-                    u&quot;both_coasts&quot;
-                ]),
-            &quot;left_coast&quot;:
-                 set([
-                     u&quot;both_coasts&quot;
-                 ]),
-            &quot;recursive1_coasts&quot;:
-                 set([
-                     u&quot;recursive1_coasts&quot;,
-                     u&quot;recursive2_coasts&quot;
-                 ]),
-            &quot;recursive2_coasts&quot;:
-                set([
-                    u&quot;recursive1_coasts&quot;,
-                    u&quot;recursive2_coasts&quot;
-                ]),
-            &quot;right_coast&quot;:
-                set([
-                    u&quot;both_coasts&quot;
-                ])
-        }
-        members = pickle.loads(snapshotFile.getContent())
-        self.assertEquals(members, expected)
-
-        # &quot;Corrupt&quot; the snapshot and verify it is regenerated properly
-        snapshotFile.setContent(&quot;xyzzy&quot;)
-        cache.delete(&quot;group-cacher-populated&quot;)
-        fast, numMembers, numChanged = (yield updater.updateCache(fast=True))
-        self.assertEquals(fast, False)
-        self.assertEquals(numMembers, 9)
-        self.assertEquals(numChanged, 9)
-        self.assertTrue(snapshotFile.exists())
-        members = pickle.loads(snapshotFile.getContent())
-        self.assertEquals(members, expected)
-
-
-    def test_autoAcceptMembers(self):
-        &quot;&quot;&quot;
-        autoAcceptMembers( ) returns an empty list if no autoAcceptGroup is
-        assigned, or the expanded membership if assigned.
-        &quot;&quot;&quot;
-
-        # No auto-accept-group for &quot;orion&quot; in augments.xml
-        orion = self.directoryService.recordWithGUID(&quot;orion&quot;)
-        self.assertEquals(orion.autoAcceptMembers(), [])
-
-        # &quot;both_coasts&quot; group assigned to &quot;apollo&quot; in augments.xml
-        apollo = self.directoryService.recordWithGUID(&quot;apollo&quot;)
-        self.assertEquals(
-            set(apollo.autoAcceptMembers()),
-            set([
-                &quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;,
-                 &quot;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&quot;,
-                 &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;,
-                 &quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;,
-                 &quot;right_coast&quot;,
-                 &quot;left_coast&quot;,
-            ])
-        )
-
-
-    # @inlineCallbacks
-    # def testScheduling(self):
-    #     &quot;&quot;&quot;
-    #     Exercise schedulePolledGroupCachingUpdate
-    #     &quot;&quot;&quot;
-
-    #     groupCacher = StubGroupCacher()
-
-
-    #     def decorateTransaction(txn):
-    #         txn._groupCacher = groupCacher
-
-    #     store = yield buildStore(self, None)
-    #     store.callWithNewTransactions(decorateTransaction)
-    #     wp = (yield schedulePolledGroupCachingUpdate(store))
-    #     yield wp.whenExecuted()
-    #     self.assertTrue(groupCacher.called)
-
-    # testScheduling.skip = &quot;Fix WorkProposal to track delayed calls and cancel them&quot;
-
-
-
-class StubGroupCacher(object):
-    def __init__(self):
-        self.called = False
-        self.updateSeconds = 99
-
-
-    def updateCache(self):
-        self.called = True
-
-
-
-class RecordsMatchingTokensTests(TestCase):
-
-    @inlineCallbacks
-    def setUp(self):
-        super(RecordsMatchingTokensTests, self).setUp()
-
-        self.directoryFixture.addDirectoryService(XMLDirectoryService(
-            {
-                'xmlFile' : xmlFile,
-                'augmentService' :
-                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-            }
-        ))
-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(&quot;proxies.sqlite&quot;)
-
-        # Set up a principals hierarchy for each service we're testing with
-        self.principalRootResources = {}
-        name = self.directoryService.__class__.__name__
-        url = &quot;/&quot; + name + &quot;/&quot;
-
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
-
-        self.site.resource.putChild(name, provisioningResource)
-
-        self.principalRootResources[self.directoryService.__class__.__name__] = provisioningResource
-
-        yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
-
-
-    def tearDown(self):
-        &quot;&quot;&quot; Empty the proxy db between tests &quot;&quot;&quot;
-        return calendaruserproxy.ProxyDBService.clean() #@UndefinedVariable
-
-
-    @inlineCallbacks
-    def test_recordsMatchingTokens(self):
-        &quot;&quot;&quot;
-        Exercise the default recordsMatchingTokens implementation
-        &quot;&quot;&quot;
-        records = list((yield self.directoryService.recordsMatchingTokens([&quot;Use&quot;, &quot;01&quot;])))
-        self.assertNotEquals(len(records), 0)
-        shorts = [record.shortNames[0] for record in records]
-        self.assertTrue(&quot;user01&quot; in shorts)
-
-        records = list((yield self.directoryService.recordsMatchingTokens(['&quot;quotey&quot;'],
-            context=self.directoryService.searchContext_attendee)))
-        self.assertEquals(len(records), 1)
-        self.assertEquals(records[0].shortNames[0], &quot;doublequotes&quot;)
-
-        records = list((yield self.directoryService.recordsMatchingTokens([&quot;coast&quot;])))
-        self.assertEquals(len(records), 5)
-
-        records = list((yield self.directoryService.recordsMatchingTokens([&quot;poll&quot;],
-            context=self.directoryService.searchContext_location)))
-        self.assertEquals(len(records), 1)
-        self.assertEquals(records[0].shortNames[0], &quot;apollo&quot;)
-
-
-    def test_recordTypesForSearchContext(self):
-        self.assertEquals(
-            [self.directoryService.recordType_locations],
-            self.directoryService.recordTypesForSearchContext(&quot;location&quot;)
-        )
-        self.assertEquals(
-            [self.directoryService.recordType_resources],
-            self.directoryService.recordTypesForSearchContext(&quot;resource&quot;)
-        )
-        self.assertEquals(
-            [self.directoryService.recordType_users],
-            self.directoryService.recordTypesForSearchContext(&quot;user&quot;)
-        )
-        self.assertEquals(
-            [self.directoryService.recordType_groups],
-            self.directoryService.recordTypesForSearchContext(&quot;group&quot;)
-        )
-        self.assertEquals(
-            set([
-                self.directoryService.recordType_resources,
-                self.directoryService.recordType_users,
-                self.directoryService.recordType_groups
-            ]),
-            set(self.directoryService.recordTypesForSearchContext(&quot;attendee&quot;))
-        )
-
-
-
-class GUIDTests(TestCase):
-
-    def setUp(self):
-        self.service = DirectoryService()
-        self.service.setRealm(&quot;test&quot;)
-        self.service.baseGUID = &quot;0E8E6EC2-8E52-4FF3-8F62-6F398B08A498&quot;
-
-
-    def test_normalizeUUID(self):
-
-        # Ensure that record.guid automatically gets normalized to
-        # uppercase+hyphenated form if the value is one that uuid.UUID( )
-        # recognizes.
-
-        data = (
-            (
-                &quot;0543A85A-D446-4CF6-80AE-6579FA60957F&quot;,
-                &quot;0543A85A-D446-4CF6-80AE-6579FA60957F&quot;
-            ),
-            (
-                &quot;0543a85a-d446-4cf6-80ae-6579fa60957f&quot;,
-                &quot;0543A85A-D446-4CF6-80AE-6579FA60957F&quot;
-            ),
-            (
-                &quot;0543A85AD4464CF680AE-6579FA60957F&quot;,
-                &quot;0543A85A-D446-4CF6-80AE-6579FA60957F&quot;
-            ),
-            (
-                &quot;0543a85ad4464cf680ae6579fa60957f&quot;,
-                &quot;0543A85A-D446-4CF6-80AE-6579FA60957F&quot;
-            ),
-            (
-                &quot;foo&quot;,
-                &quot;foo&quot;
-            ),
-            (
-                None,
-                None
-            ),
-        )
-        for original, expected in data:
-            self.assertEquals(expected, normalizeUUID(original))
-            record = DirectoryRecord(self.service, &quot;users&quot;, original,
-                shortNames=(&quot;testing&quot;,))
-            self.assertEquals(expected, record.guid)
-
-
-
-class DirectoryServiceTests(TestCase):
-    &quot;&quot;&quot;
-    Test L{DirectoryService} apis.
-    &quot;&quot;&quot;
-
-    class StubDirectoryService(DirectoryService):
-
-        def __init__(self):
-            self._records = {}
-
-
-        def createRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
-            fullName=None, firstName=None, lastName=None, emailAddresses=set(),
-            uid=None, password=None, **kwargs):
-            &quot;&quot;&quot;
-            Create/persist a directory record based on the given values
-            &quot;&quot;&quot;
-
-            record = DirectoryRecord(
-                self,
-                recordType,
-                guid=guid,
-                shortNames=shortNames,
-                authIDs=authIDs,
-                fullName=fullName,
-                firstName=firstName,
-                lastName=lastName,
-                emailAddresses=emailAddresses,
-                uid=uid,
-                password=password,
-                **kwargs
-            )
-            self._records.setdefault(recordType, []).append(record)
-
-
-        def recordTypes(self):
-            return self._records.keys()
-
-
-        def listRecords(self, recordType):
-            return self._records[recordType]
-
-
-    def setUp(self):
-        self.service = self.StubDirectoryService()
-        self.service.setRealm(&quot;test&quot;)
-        self.service.baseGUID = &quot;0E8E6EC2-8E52-4FF3-8F62-6F398B08A498&quot;
-
-
-    def test_recordWithCalendarUserAddress_principal_uris(self):
-        &quot;&quot;&quot;
-        Make sure that recordWithCalendarUserAddress handles percent-encoded
-        principal URIs.
-        &quot;&quot;&quot;
-
-        self.service.createRecord(
-            DirectoryService.recordType_users,
-            guid=&quot;user01&quot;,
-            shortNames=(&quot;user 01&quot;, &quot;User 01&quot;),
-            fullName=&quot;User 01&quot;,
-            enabledForCalendaring=True,
-        )
-        self.service.createRecord(
-            DirectoryService.recordType_users,
-            guid=&quot;user02&quot;,
-            shortNames=(&quot;user02&quot;, &quot;User 02&quot;),
-            fullName=&quot;User 02&quot;,
-            enabledForCalendaring=True,
-        )
-
-        record = self.service.recordWithCalendarUserAddress(&quot;/principals/users/user%2001&quot;)
-        self.assertTrue(record is not None)
-        record = self.service.recordWithCalendarUserAddress(&quot;/principals/users/user02&quot;)
-        self.assertTrue(record is not None)
-        record = self.service.recordWithCalendarUserAddress(&quot;/principals/users/user%0202&quot;)
-        self.assertTrue(record is None)
-
-
-
-class DirectoryRecordTests(TestCase):
-    &quot;&quot;&quot;
-    Test L{DirectoryRecord} apis.
-    &quot;&quot;&quot;
-
-    def setUp(self):
-        self.service = DirectoryService()
-        self.service.setRealm(&quot;test&quot;)
-        self.service.baseGUID = &quot;0E8E6EC2-8E52-4FF3-8F62-6F398B08A498&quot;
-
-
-    def test_cacheToken(self):
-        &quot;&quot;&quot;
-        Test that DirectoryRecord.cacheToken is different for different records, and its value changes
-        as attributes on the record change.
-        &quot;&quot;&quot;
-
-        record1 = DirectoryRecord(self.service, &quot;users&quot;, str(uuid.uuid4()), shortNames=(&quot;testing1&quot;,))
-        record2 = DirectoryRecord(self.service, &quot;users&quot;, str(uuid.uuid4()), shortNames=(&quot;testing2&quot;,))
-        self.assertNotEquals(record1.cacheToken(), record2.cacheToken())
-
-        cache1 = record1.cacheToken()
-        record1.enabled = True
-        self.assertNotEquals(cache1, record1.cacheToken())
-
-        cache1 = record1.cacheToken()
-        record1.enabledForCalendaring = True
-        self.assertNotEquals(cache1, record1.cacheToken())
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_modifypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_modify.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_modify.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_modify.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,159 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-import os
-from twistedcaldav.config import config
-from twistedcaldav.test.util import TestCase
-from calendarserver.tools.util import getDirectory
-from twext.python.filepath import CachingFilePath as FilePath
-from twistedcaldav.directory.directory import DirectoryError
-
-
-class ModificationTestCase(TestCase):
-
-    def setUp(self):
-        super(ModificationTestCase, self).setUp()
-
-        testRoot = os.path.join(os.path.dirname(__file__), &quot;modify&quot;)
-        #configFileName = os.path.join(testRoot, &quot;caldavd.plist&quot;)
-        #config.load(configFileName)
-
-        usersFile = os.path.join(testRoot, &quot;users-groups.xml&quot;)
-        config.DirectoryService.params.xmlFile = usersFile
-
-        # Copy xml file containing locations/resources to a temp file because
-        # we're going to be modifying it during testing
-
-        origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;modify&quot;, &quot;resources-locations.xml&quot;))
-        copyResourcesFile = FilePath(self.mktemp())
-        origResourcesFile.copyTo(copyResourcesFile)
-        config.ResourceService.params.xmlFile = copyResourcesFile
-        config.ResourceService.Enabled = True
-
-        augmentsFile = os.path.join(testRoot, &quot;augments.xml&quot;)
-        config.AugmentService.params.xmlFiles = (augmentsFile,)
-
-
-    def test_createRecord(self):
-        directory = getDirectory()
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record, None)
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource01&quot;,
-            shortNames=(&quot;resource01&quot;,), uid=&quot;resource01&quot;,
-            emailAddresses=(&quot;res1@example.com&quot;, &quot;res2@example.com&quot;),
-            comment=&quot;Test Comment&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertNotEquals(record, None)
-
-        self.assertEquals(len(record.emailAddresses), 2)
-        self.assertEquals(record.extras['comment'], &quot;Test Comment&quot;)
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource02&quot;, shortNames=(&quot;resource02&quot;,), uid=&quot;resource02&quot;)
-
-        record = directory.recordWithUID(&quot;resource02&quot;)
-        self.assertNotEquals(record, None)
-
-        # Make sure old records are still there:
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertNotEquals(record, None)
-        record = directory.recordWithUID(&quot;location01&quot;)
-        self.assertNotEquals(record, None)
-
-
-    def test_destroyRecord(self):
-        directory = getDirectory()
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record, None)
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource01&quot;, shortNames=(&quot;resource01&quot;,), uid=&quot;resource01&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertNotEquals(record, None)
-
-        directory.destroyRecord(&quot;resources&quot;, guid=&quot;resource01&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record, None)
-
-        # Make sure old records are still there:
-        record = directory.recordWithUID(&quot;location01&quot;)
-        self.assertNotEquals(record, None)
-
-
-    def test_updateRecord(self):
-        directory = getDirectory()
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource01&quot;,
-            shortNames=(&quot;resource01&quot;,), uid=&quot;resource01&quot;,
-            fullName=&quot;Resource number 1&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record.fullName, &quot;Resource number 1&quot;)
-
-        directory.updateRecord(&quot;resources&quot;, guid=&quot;resource01&quot;,
-            shortNames=(&quot;resource01&quot;, &quot;r01&quot;), uid=&quot;resource01&quot;,
-            fullName=&quot;Resource #1&quot;, firstName=&quot;First&quot;, lastName=&quot;Last&quot;,
-            emailAddresses=(&quot;resource01@example.com&quot;, &quot;r01@example.com&quot;),
-            comment=&quot;Test Comment&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record.fullName, &quot;Resource #1&quot;)
-        self.assertEquals(record.firstName, &quot;First&quot;)
-        self.assertEquals(record.lastName, &quot;Last&quot;)
-        self.assertEquals(set(record.shortNames), set([&quot;resource01&quot;, &quot;r01&quot;]))
-        self.assertEquals(record.emailAddresses,
-            set([&quot;resource01@example.com&quot;, &quot;r01@example.com&quot;]))
-        self.assertEquals(record.extras['comment'], &quot;Test Comment&quot;)
-
-        # Make sure old records are still there:
-        record = directory.recordWithUID(&quot;location01&quot;)
-        self.assertNotEquals(record, None)
-
-
-    def test_createDuplicateRecord(self):
-        directory = getDirectory()
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource01&quot;, shortNames=(&quot;resource01&quot;,), uid=&quot;resource01&quot;)
-        self.assertRaises(DirectoryError, directory.createRecord, &quot;resources&quot;, guid=&quot;resource01&quot;, shortNames=(&quot;resource01&quot;,), uid=&quot;resource01&quot;)
-
-
-    def test_missingShortNames(self):
-        directory = getDirectory()
-
-        directory.createRecord(&quot;resources&quot;, guid=&quot;resource01&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record.shortNames[0], &quot;resource01&quot;)
-
-        directory.updateRecord(&quot;resources&quot;, guid=&quot;resource01&quot;,
-            fullName=&quot;Resource #1&quot;)
-
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record.shortNames[0], &quot;resource01&quot;)
-        self.assertEquals(record.fullName, &quot;Resource #1&quot;)
-
-
-    def test_missingGUID(self):
-        directory = getDirectory()
-
-        record = directory.createRecord(&quot;resources&quot;)
-
-        self.assertEquals(record.shortNames[0], record.guid)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_principalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_principal.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_principal.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_principal.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -16,68 +16,63 @@
</span><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><span class="cx"> import os
</span><ins>+from urllib import quote
</ins><span class="cx"> 
</span><span class="cx"> from twisted.cred.credentials import UsernamePassword
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><del>-from txdav.xml import element as davxml
-from txweb2.dav.fileop import rmdir
-from txweb2.dav.resource import AccessDeniedError
-from txweb2.http import HTTPError
-from txweb2.test.test_server import SimpleRequest
-
</del><ins>+from twistedcaldav import carddavxml
</ins><span class="cx"> from twistedcaldav.cache import DisabledCacheNotifier
</span><span class="cx"> from twistedcaldav.caldavxml import caldav_namespace
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><del>-from twistedcaldav.directory import augment, calendaruserproxy
</del><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
</span><del>-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
</del><ins>+from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
</ins><span class="cx"> from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
</span><del>-from twistedcaldav.directory.principal import DirectoryPrincipalTypeProvisioningResource
</del><span class="cx"> from twistedcaldav.directory.principal import DirectoryPrincipalResource
</span><del>-from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
-from twistedcaldav import carddavxml
-import twistedcaldav.test.util
-
</del><ins>+from twistedcaldav.directory.principal import DirectoryPrincipalTypeProvisioningResource
+from twistedcaldav.test.util import StoreTestCase
</ins><span class="cx"> from txdav.common.datastore.file import CommonDataStore
</span><del>-from urllib import quote
</del><ins>+from txdav.xml import element as davxml
+from txweb2.dav.fileop import rmdir
+from txweb2.dav.resource import AccessDeniedError
+from txweb2.http import HTTPError
+from txweb2.test.test_server import SimpleRequest
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
</del><ins>+
+class ProvisionedPrincipals(StoreTestCase):  # twistedcaldav.test.util.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory service provisioned principals.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    def setUp(self):
-        super(ProvisionedPrincipals, self).setUp()
</del><ins>+    # def setUp(self):
+    #     super(ProvisionedPrincipals, self).setUp()
</ins><span class="cx"> 
</span><del>-        self.directoryServices = (
-            XMLDirectoryService(
-                {
-                    'xmlFile' : xmlFile,
-                    'augmentService' :
-                        augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-                }
-            ),
-        )
</del><ins>+    #     self.directoryServices = (
+    #         XMLDirectoryService(
+    #             {
+    #                 'xmlFile' : xmlFile,
+    #                 'augmentService' :
+    #                     augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
+    #             }
+    #         ),
+    #     )
</ins><span class="cx"> 
</span><del>-        # Set up a principals hierarchy for each service we're testing with
-        self.principalRootResources = {}
-        for directory in self.directoryServices:
-            name = directory.__class__.__name__
-            url = &quot;/&quot; + name + &quot;/&quot;
</del><ins>+    #     # Set up a principals hierarchy for each service we're testing with
+    #     self.principalRootResources = {}
+    #     for directory in self.directoryServices:
+    #         name = directory.__class__.__name__
+    #         url = &quot;/&quot; + name + &quot;/&quot;
</ins><span class="cx"> 
</span><del>-            provisioningResource = DirectoryPrincipalProvisioningResource(url, directory)
-            directory.setPrincipalCollection(provisioningResource)
</del><ins>+    #         provisioningResource = DirectoryPrincipalProvisioningResource(url, directory)
+    #         directory.setPrincipalCollection(provisioningResource)
</ins><span class="cx"> 
</span><del>-            self.site.resource.putChild(name, provisioningResource)
</del><ins>+    #         self.site.resource.putChild(name, provisioningResource)
</ins><span class="cx"> 
</span><del>-            self.principalRootResources[directory.__class__.__name__] = provisioningResource
</del><ins>+    #         self.principalRootResources[directory.__class__.__name__] = provisioningResource
</ins><span class="cx"> 
</span><del>-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
</del><ins>+    #     calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -95,7 +90,8 @@
</span><span class="cx"> 
</span><span class="cx">         DirectoryPrincipalResource.principalURL(),
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for directory in self.directoryServices:
</del><ins>+        directory = self.directory
+        if True:
</ins><span class="cx">             #print(&quot;\n -&gt; %s&quot; % (directory.__class__.__name__,))
</span><span class="cx">             provisioningResource = self.principalRootResources[directory.__class__.__name__]
</span><span class="cx"> 
</span><span class="lines">@@ -170,33 +166,33 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalProvisioningResource.principalForUser()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for directory in self.directoryServices:
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
</del><ins>+        directory = self.directory
+        provisioningResource = self.principalRootResources[directory.__class__.__name__]
</ins><span class="cx"> 
</span><del>-            for user in directory.listRecords(DirectoryService.recordType_users):
-                userResource = provisioningResource.principalForUser(user.shortNames[0])
-                if user.enabled:
-                    self.failIf(userResource is None)
-                    self.assertEquals(user, userResource.record)
-                else:
-                    self.failIf(userResource is not None)
</del><ins>+        for user in directory.listRecords(DirectoryService.recordType_users):
+            userResource = provisioningResource.principalForUser(user.shortNames[0])
+            if user.enabled:
+                self.failIf(userResource is None)
+                self.assertEquals(user, userResource.record)
+            else:
+                self.failIf(userResource is not None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_principalForAuthID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalProvisioningResource.principalForAuthID()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for directory in self.directoryServices:
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
</del><ins>+        directory = self.directory
+        provisioningResource = self.principalRootResources[directory.__class__.__name__]
</ins><span class="cx"> 
</span><del>-            for user in directory.listRecords(DirectoryService.recordType_users):
-                creds = UsernamePassword(user.shortNames[0], &quot;bogus&quot;)
-                userResource = provisioningResource.principalForAuthID(creds)
-                if user.enabled:
-                    self.failIf(userResource is None)
-                    self.assertEquals(user, userResource.record)
-                else:
-                    self.failIf(userResource is not None)
</del><ins>+        for user in directory.listRecords(DirectoryService.recordType_users):
+            creds = UsernamePassword(user.shortNames[0], &quot;bogus&quot;)
+            userResource = provisioningResource.principalForAuthID(creds)
+            if user.enabled:
+                self.failIf(userResource is None)
+                self.assertEquals(user, userResource.record)
+            else:
+                self.failIf(userResource is not None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_principalForUID(self):
</span><span class="lines">@@ -465,23 +461,23 @@
</span><span class="cx">         # Need to create a addressbook home provisioner for each service.
</span><span class="cx">         addressBookRootResources = {}
</span><span class="cx"> 
</span><del>-        for directory in self.directoryServices:
-            path = os.path.join(self.docroot, directory.__class__.__name__)
</del><ins>+        directory = self.directory
+        path = os.path.join(self.docroot, directory.__class__.__name__)
</ins><span class="cx"> 
</span><del>-            if os.path.exists(path):
-                rmdir(path)
-            os.mkdir(path)
</del><ins>+        if os.path.exists(path):
+            rmdir(path)
+        os.mkdir(path)
</ins><span class="cx"> 
</span><del>-            # Need a data store
-            _newStore = CommonDataStore(path, None, None, True, False)
</del><ins>+        # need a data store
+        _newstore = commondatastore(path, none, none, true, false)
</ins><span class="cx"> 
</span><del>-            provisioningResource = DirectoryAddressBookHomeProvisioningResource(
-                directory,
-                &quot;/addressbooks/&quot;,
-                _newStore
-            )
</del><ins>+        provisioningresource = directoryaddressbookhomeprovisioningresource(
+            directory,
+            &quot;/addressbooks/&quot;,
+            _newstore
+        )
</ins><span class="cx"> 
</span><del>-            addressBookRootResources[directory.__class__.__name__] = provisioningResource
</del><ins>+        addressbookrootresources[directory.__class__.__name__] = provisioningResource
</ins><span class="cx"> 
</span><span class="cx">         # AddressBook home provisioners should result in addressBook homes.
</span><span class="cx">         for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
</span><span class="lines">@@ -517,23 +513,23 @@
</span><span class="cx">         # Need to create a calendar home provisioner for each service.
</span><span class="cx">         calendarRootResources = {}
</span><span class="cx"> 
</span><del>-        for directory in self.directoryServices:
-            path = os.path.join(self.docroot, directory.__class__.__name__)
</del><ins>+        directory = self.directory
+        path = os.path.join(self.docroot, directory.__class__.__name__)
</ins><span class="cx"> 
</span><del>-            if os.path.exists(path):
-                rmdir(path)
-            os.mkdir(path)
</del><ins>+        if os.path.exists(path):
+            rmdir(path)
+        os.mkdir(path)
</ins><span class="cx"> 
</span><del>-            # Need a data store
-            _newStore = CommonDataStore(path, None, None, True, False)
</del><ins>+        # Need a data store
+        _newStore = CommonDataStore(path, None, None, True, False)
</ins><span class="cx"> 
</span><del>-            provisioningResource = DirectoryCalendarHomeProvisioningResource(
-                directory,
-                &quot;/calendars/&quot;,
-                _newStore
-            )
</del><ins>+        provisioningResource = DirectoryCalendarHomeProvisioningResource(
+            directory,
+            &quot;/calendars/&quot;,
+            _newStore
+        )
</ins><span class="cx"> 
</span><del>-            calendarRootResources[directory.__class__.__name__] = provisioningResource
</del><ins>+        calendarRootResources[directory.__class__.__name__] = provisioningResource
</ins><span class="cx"> 
</span><span class="cx">         # Calendar home provisioners should result in calendar homes.
</span><span class="cx">         for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
</span><span class="lines">@@ -643,19 +639,19 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Default access controls for principal provisioning resources.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for directory in self.directoryServices:
-            #print(&quot;\n -&gt; %s&quot; % (directory.__class__.__name__,))
-            provisioningResource = self.principalRootResources[directory.__class__.__name__]
</del><ins>+        directory = self.directory
+        #print(&quot;\n -&gt; %s&quot; % (directory.__class__.__name__,))
+        provisioningResource = self.principalRootResources[directory.__class__.__name__]
</ins><span class="cx"> 
</span><del>-            for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
-                yield self._checkPrivileges(*args)
</del><ins>+        for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
+            yield self._checkPrivileges(*args)
</ins><span class="cx"> 
</span><del>-            for recordType in (yield provisioningResource.listChildren()):
-                #print(&quot;   -&gt; %s&quot; % (recordType,))
-                typeResource = provisioningResource.getChild(recordType)
</del><ins>+        for recordType in (yield provisioningResource.listChildren()):
+            #print(&quot;   -&gt; %s&quot; % (recordType,))
+            typeResource = provisioningResource.getChild(recordType)
</ins><span class="cx"> 
</span><del>-                for args in _authReadOnlyPrivileges(self, typeResource, typeResource.principalCollectionURL()):
-                    yield self._checkPrivileges(*args)
</del><ins>+            for args in _authReadOnlyPrivileges(self, typeResource, typeResource.principalCollectionURL()):
+                yield self._checkPrivileges(*args)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_propertyToField(self):
</span><span class="lines">@@ -705,14 +701,14 @@
</span><span class="cx">             C{record} is the directory service record
</span><span class="cx">             for each record in each directory in C{directoryServices}.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for directory in self.directoryServices:
-            provisioningResource = self.principalRootResources[
-                directory.__class__.__name__
-            ]
-            for recordType in directory.recordTypes():
-                for record in directory.listRecords(recordType):
-                    recordResource = provisioningResource.principalForRecord(record)
-                    yield provisioningResource, recordType, recordResource, record
</del><ins>+        directory = self.directory
+        provisioningResource = self.principalRootResources[
+            directory.__class__.__name__
+        ]
+        for recordType in directory.recordTypes():
+            for record in directory.listRecords(recordType):
+                recordResource = provisioningResource.principalForRecord(record)
+                yield provisioningResource, recordType, recordResource, record
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _checkPrivileges(self, resource, url, principal, privilege, allowed):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_proxyprincipalmemberspy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_proxyprincipalmembers.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_proxyprincipalmembers.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_proxyprincipalmembers.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,506 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import DeferredList, inlineCallbacks, succeed
-from txdav.xml import element as davxml
-
-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.test.util import xmlFile, augmentsFile, proxiesFile
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource, \
-    DirectoryPrincipalResource
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-
-import twistedcaldav.test.util
-from twistedcaldav.directory import augment, calendaruserproxy
-from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
-
-
-class ProxyPrincipals (twistedcaldav.test.util.TestCase):
-    &quot;&quot;&quot;
-    Directory service provisioned principals.
-    &quot;&quot;&quot;
-
-    @inlineCallbacks
-    def setUp(self):
-        super(ProxyPrincipals, self).setUp()
-
-        self.directoryFixture.addDirectoryService(XMLDirectoryService(
-            {
-                'xmlFile' : xmlFile,
-                'augmentService' :
-                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-            }
-        ))
-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(&quot;proxies.sqlite&quot;)
-
-        # Set up a principals hierarchy for each service we're testing with
-        self.principalRootResources = {}
-        name = self.directoryService.__class__.__name__
-        url = &quot;/&quot; + name + &quot;/&quot;
-
-        provisioningResource = DirectoryPrincipalProvisioningResource(url, self.directoryService)
-
-        self.site.resource.putChild(name, provisioningResource)
-
-        self.principalRootResources[self.directoryService.__class__.__name__] = provisioningResource
-
-        yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
-
-
-    def tearDown(self):
-        &quot;&quot;&quot; Empty the proxy db between tests &quot;&quot;&quot;
-        return calendaruserproxy.ProxyDBService.clean() #@UndefinedVariable
-
-
-    def _getPrincipalByShortName(self, type, name):
-        provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
-        return provisioningResource.principalForShortName(type, name)
-
-
-    def _groupMembersTest(self, recordType, recordName, subPrincipalName, expectedMembers):
-        def gotMembers(members):
-            memberNames = set([p.displayName() for p in members])
-            self.assertEquals(memberNames, set(expectedMembers))
-
-        principal = self._getPrincipalByShortName(recordType, recordName)
-        if subPrincipalName is not None:
-            principal = principal.getChild(subPrincipalName)
-
-        d = principal.expandedGroupMembers()
-        d.addCallback(gotMembers)
-        return d
-
-
-    def _groupMembershipsTest(self, recordType, recordName, subPrincipalName, expectedMemberships):
-        def gotMemberships(memberships):
-            uids = set([p.principalUID() for p in memberships])
-            self.assertEquals(uids, set(expectedMemberships))
-
-        principal = self._getPrincipalByShortName(recordType, recordName)
-        if subPrincipalName is not None:
-            principal = principal.getChild(subPrincipalName)
-
-        d = principal.groupMemberships()
-        d.addCallback(gotMemberships)
-        return d
-
-
-    @inlineCallbacks
-    def _addProxy(self, principal, subPrincipalName, proxyPrincipal):
-
-        if isinstance(principal, tuple):
-            principal = self._getPrincipalByShortName(principal[0], principal[1])
-        principal = principal.getChild(subPrincipalName)
-        members = (yield principal.groupMembers())
-
-        if isinstance(proxyPrincipal, tuple):
-            proxyPrincipal = self._getPrincipalByShortName(proxyPrincipal[0], proxyPrincipal[1])
-        members.add(proxyPrincipal)
-
-        yield principal.setGroupMemberSetPrincipals(members)
-
-
-    @inlineCallbacks
-    def _removeProxy(self, recordType, recordName, subPrincipalName, proxyRecordType, proxyRecordName):
-
-        principal = self._getPrincipalByShortName(recordType, recordName)
-        principal = principal.getChild(subPrincipalName)
-        members = (yield principal.groupMembers())
-
-        proxyPrincipal = self._getPrincipalByShortName(proxyRecordType, proxyRecordName)
-        for p in members:
-            if p.principalUID() == proxyPrincipal.principalUID():
-                members.remove(p)
-                break
-
-        yield principal.setGroupMemberSetPrincipals(members)
-
-
-    @inlineCallbacks
-    def _clearProxy(self, principal, subPrincipalName):
-
-        if isinstance(principal, tuple):
-            principal = self._getPrincipalByShortName(principal[0], principal[1])
-        principal = principal.getChild(subPrincipalName)
-        yield principal.setGroupMemberSetPrincipals(set())
-
-
-    @inlineCallbacks
-    def _proxyForTest(self, recordType, recordName, expectedProxies, read_write):
-        principal = self._getPrincipalByShortName(recordType, recordName)
-        proxies = (yield principal.proxyFor(read_write))
-        proxies = sorted([_principal.displayName() for _principal in proxies])
-        self.assertEquals(proxies, sorted(expectedProxies))
-
-
-    @inlineCallbacks
-    def test_multipleProxyAssignmentsAtOnce(self):
-        yield self._proxyForTest(
-            DirectoryService.recordType_users, &quot;userb&quot;,
-            ('a',),
-            True
-        )
-        yield self._proxyForTest(
-            DirectoryService.recordType_users, &quot;userc&quot;,
-            ('a',),
-            True
-        )
-
-
-    def test_groupMembersRegular(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_groups, &quot;both_coasts&quot;, None,
-            (&quot;Chris Lecroy&quot;, &quot;David Reid&quot;, &quot;Wilfredo Sanchez&quot;, &quot;West Coast&quot;, &quot;East Coast&quot;, &quot;Cyrus Daboo&quot;,),
-        )
-
-
-    def test_groupMembersRecursive(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_groups, &quot;recursive1_coasts&quot;, None,
-            (&quot;Wilfredo Sanchez&quot;, &quot;Recursive2 Coasts&quot;, &quot;Cyrus Daboo&quot;,),
-        )
-
-
-    def test_groupMembersProxySingleUser(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_locations, &quot;gemini&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Wilfredo Sanchez&quot;,),
-        )
-
-
-    def test_groupMembersProxySingleGroup(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_locations, &quot;mercury&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Chris Lecroy&quot;, &quot;David Reid&quot;, &quot;Wilfredo Sanchez&quot;, &quot;West Coast&quot;,),
-        )
-
-
-    def test_groupMembersProxySingleGroupWithNestedGroups(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_locations, &quot;apollo&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Chris Lecroy&quot;, &quot;David Reid&quot;, &quot;Wilfredo Sanchez&quot;, &quot;West Coast&quot;, &quot;East Coast&quot;, &quot;Cyrus Daboo&quot;, &quot;Both Coasts&quot;,),
-        )
-
-
-    def test_groupMembersProxySingleGroupWithNestedRecursiveGroups(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        return self._groupMembersTest(
-            DirectoryService.recordType_locations, &quot;orion&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Wilfredo Sanchez&quot;, &quot;Cyrus Daboo&quot;, &quot;Recursive1 Coasts&quot;, &quot;Recursive2 Coasts&quot;,),
-        )
-
-
-    def test_groupMembersProxySingleGroupWithNonCalendarGroup(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        ds = []
-
-        ds.append(self._groupMembersTest(
-            DirectoryService.recordType_resources, &quot;non_calendar_proxy&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Chris Lecroy&quot;, &quot;Cyrus Daboo&quot;, &quot;Non-calendar group&quot;),
-        ))
-
-        ds.append(self._groupMembershipsTest(
-            DirectoryService.recordType_groups, &quot;non_calendar_group&quot;, None,
-            (&quot;non_calendar_proxy#calendar-proxy-write&quot;,),
-        ))
-
-        return DeferredList(ds)
-
-
-    def test_groupMembersProxyMissingUser(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        proxy = self._getPrincipalByShortName(DirectoryService.recordType_users, &quot;cdaboo&quot;)
-        proxyGroup = proxy.getChild(&quot;calendar-proxy-write&quot;)
-
-        def gotMembers(members):
-            members.add(&quot;12345&quot;)
-            return proxyGroup._index().setGroupMembers(&quot;%s#calendar-proxy-write&quot; % (proxy.principalUID(),), members)
-
-        def check(_):
-            return self._groupMembersTest(
-                DirectoryService.recordType_users, &quot;cdaboo&quot;, &quot;calendar-proxy-write&quot;,
-                (),
-            )
-
-        # Setup the fake entry in the DB
-        d = proxyGroup._index().getMembers(&quot;%s#calendar-proxy-write&quot; % (proxy.principalUID(),))
-        d.addCallback(gotMembers)
-        d.addCallback(check)
-        return d
-
-
-    def test_groupMembershipsMissingUser(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.expandedGroupMembers()
-        &quot;&quot;&quot;
-        # Setup the fake entry in the DB
-        fake_uid = &quot;12345&quot;
-        proxy = self._getPrincipalByShortName(DirectoryService.recordType_users, &quot;cdaboo&quot;)
-        proxyGroup = proxy.getChild(&quot;calendar-proxy-write&quot;)
-
-        def gotMembers(members):
-            members.add(&quot;%s#calendar-proxy-write&quot; % (proxy.principalUID(),))
-            return proxyGroup._index().setGroupMembers(&quot;%s#calendar-proxy-write&quot; % (fake_uid,), members)
-
-        def check(_):
-            return self._groupMembershipsTest(
-                DirectoryService.recordType_users, &quot;cdaboo&quot;, &quot;calendar-proxy-write&quot;,
-                (),
-            )
-
-        d = proxyGroup._index().getMembers(&quot;%s#calendar-proxy-write&quot; % (fake_uid,))
-        d.addCallback(gotMembers)
-        d.addCallback(check)
-        return d
-
-
-    @inlineCallbacks
-    def test_setGroupMemberSet(self):
-        class StubMemberDB(object):
-            def __init__(self):
-                self.members = set()
-
-            def setGroupMembers(self, uid, members):
-                self.members = members
-                return succeed(None)
-
-            def getMembers(self, uid):
-                return succeed(self.members)
-
-        user = self._getPrincipalByShortName(self.directoryService.recordType_users,
-                                           &quot;cdaboo&quot;)
-
-        proxyGroup = user.getChild(&quot;calendar-proxy-write&quot;)
-
-        memberdb = StubMemberDB()
-
-        proxyGroup._index = (lambda: memberdb)
-
-        new_members = davxml.GroupMemberSet(
-            davxml.HRef.fromString(
-                &quot;/XMLDirectoryService/__uids__/8B4288F6-CC82-491D-8EF9-642EF4F3E7D0/&quot;),
-            davxml.HRef.fromString(
-                &quot;/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/&quot;))
-
-        yield proxyGroup.setGroupMemberSet(new_members, None)
-
-        self.assertEquals(
-            set([str(p) for p in memberdb.members]),
-            set([&quot;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&quot;,
-                 &quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;]))
-
-
-    @inlineCallbacks
-    def test_setGroupMemberSetNotifiesPrincipalCaches(self):
-        class StubCacheNotifier(object):
-            changedCount = 0
-            def changed(self):
-                self.changedCount += 1
-                return succeed(None)
-
-        user = self._getPrincipalByShortName(self.directoryService.recordType_users, &quot;cdaboo&quot;)
-
-        proxyGroup = user.getChild(&quot;calendar-proxy-write&quot;)
-
-        notifier = StubCacheNotifier()
-
-        oldCacheNotifier = DirectoryPrincipalResource.cacheNotifierFactory
-
-        try:
-            DirectoryPrincipalResource.cacheNotifierFactory = (lambda _1, _2, **kwargs: notifier)
-
-            self.assertEquals(notifier.changedCount, 0)
-
-            yield proxyGroup.setGroupMemberSet(
-                davxml.GroupMemberSet(
-                    davxml.HRef.fromString(
-                        &quot;/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/&quot;)),
-                None)
-
-            self.assertEquals(notifier.changedCount, 1)
-        finally:
-            DirectoryPrincipalResource.cacheNotifierFactory = oldCacheNotifier
-
-
-    def test_proxyFor(self):
-
-        return self._proxyForTest(
-            DirectoryService.recordType_users, &quot;wsanchez&quot;,
-            (&quot;Mercury Seven&quot;, &quot;Gemini Twelve&quot;, &quot;Apollo Eleven&quot;, &quot;Orion&quot;,),
-            True
-        )
-
-
-    @inlineCallbacks
-    def test_proxyForDuplicates(self):
-
-        yield self._addProxy(
-            (DirectoryService.recordType_locations, &quot;gemini&quot;,),
-            &quot;calendar-proxy-write&quot;,
-            (DirectoryService.recordType_groups, &quot;grunts&quot;,),
-        )
-
-        yield self._proxyForTest(
-            DirectoryService.recordType_users, &quot;wsanchez&quot;,
-            (&quot;Mercury Seven&quot;, &quot;Gemini Twelve&quot;, &quot;Apollo Eleven&quot;, &quot;Orion&quot;,),
-            True
-        )
-
-
-    def test_readOnlyProxyFor(self):
-
-        return self._proxyForTest(
-            DirectoryService.recordType_users, &quot;wsanchez&quot;,
-            (&quot;Non-calendar proxy&quot;,),
-            False
-        )
-
-
-    @inlineCallbacks
-    def test_UserProxy(self):
-
-        for proxyType in (&quot;calendar-proxy-read&quot;, &quot;calendar-proxy-write&quot;):
-
-            yield self._addProxy(
-                (DirectoryService.recordType_users, &quot;wsanchez&quot;,),
-                proxyType,
-                (DirectoryService.recordType_users, &quot;cdaboo&quot;,),
-            )
-
-            yield self._groupMembersTest(
-                DirectoryService.recordType_users, &quot;wsanchez&quot;,
-                proxyType,
-                (&quot;Cyrus Daboo&quot;,),
-            )
-
-            yield self._addProxy(
-                (DirectoryService.recordType_users, &quot;wsanchez&quot;,),
-                proxyType,
-                (DirectoryService.recordType_users, &quot;lecroy&quot;,),
-            )
-
-            yield self._groupMembersTest(
-                DirectoryService.recordType_users, &quot;wsanchez&quot;,
-                proxyType,
-                (&quot;Cyrus Daboo&quot;, &quot;Chris Lecroy&quot;,),
-            )
-
-            yield self._removeProxy(
-                DirectoryService.recordType_users, &quot;wsanchez&quot;,
-                proxyType,
-                DirectoryService.recordType_users, &quot;cdaboo&quot;,
-            )
-
-            yield self._groupMembersTest(
-                DirectoryService.recordType_users, &quot;wsanchez&quot;,
-                proxyType,
-                (&quot;Chris Lecroy&quot;,),
-            )
-
-
-    @inlineCallbacks
-    def test_NonAsciiProxy(self):
-        &quot;&quot;&quot;
-        Ensure that principalURLs with non-ascii don't cause problems
-        within CalendarUserProxyPrincipalResource
-        &quot;&quot;&quot;
-
-        recordType = DirectoryService.recordType_users
-        proxyType = &quot;calendar-proxy-read&quot;
-
-        record = self.directoryService.recordWithGUID(&quot;320B73A1-46E2-4180-9563-782DFDBE1F63&quot;)
-        provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
-        principal = provisioningResource.principalForRecord(record)
-        proxyPrincipal = provisioningResource.principalForShortName(recordType,
-            &quot;wsanchez&quot;)
-
-        yield self._addProxy(principal, proxyType, proxyPrincipal)
-        memberships = yield proxyPrincipal._calendar_user_proxy_index().getMemberships(proxyPrincipal.principalUID())
-        for uid in memberships:
-            provisioningResource.principalForUID(uid)
-
-
-    @inlineCallbacks
-    def test_getAllMembers(self):
-        &quot;&quot;&quot;
-        getAllMembers( ) returns the unique set of guids that have been
-        delegated-to directly
-        &quot;&quot;&quot;
-        self.assertEquals(
-            set((yield calendaruserproxy.ProxyDBService.getAllMembers())), #@UndefinedVariable
-            set([
-                u'00599DAF-3E75-42DD-9DB7-52617E79943F',
-                u'6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                u'8A985493-EE2C-4665-94CF-4DFEA3A89500',
-                u'9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2',
-                u'both_coasts',
-                u'left_coast',
-                u'non_calendar_group',
-                u'recursive1_coasts',
-                u'recursive2_coasts',
-                u'EC465590-E9E9-4746-ACE8-6C756A49FE4D'])
-        )
-
-
-    @inlineCallbacks
-    def test_hideDisabledDelegates(self):
-        &quot;&quot;&quot;
-        Delegates who are not enabledForLogin are &quot;hidden&quot; from the delegate lists
-        (but groups *are* allowed)
-        &quot;&quot;&quot;
-
-        record = self.directoryService.recordWithGUID(&quot;EC465590-E9E9-4746-ACE8-6C756A49FE4D&quot;)
-
-        record.enabledForLogin = True
-        yield self._groupMembersTest(
-            DirectoryService.recordType_users, &quot;delegator&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Occasional Delegate&quot;, &quot;Delegate Via Group&quot;, &quot;Delegate Group&quot;),
-        )
-
-        # Login disabled -- no longer shown as a delegate
-        record.enabledForLogin = False
-        yield self._groupMembersTest(
-            DirectoryService.recordType_users, &quot;delegator&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Delegate Via Group&quot;, &quot;Delegate Group&quot;),
-        )
-
-        # Login re-enabled -- once again a delegate (it wasn't not removed from proxydb)
-        record.enabledForLogin = True
-        yield self._groupMembersTest(
-            DirectoryService.recordType_users, &quot;delegator&quot;, &quot;calendar-proxy-write&quot;,
-            (&quot;Occasional Delegate&quot;, &quot;Delegate Via Group&quot;, &quot;Delegate Group&quot;),
-        )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_resourcespy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_resources.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_resources.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_resources.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,80 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-import os
-from twistedcaldav.config import config
-from twistedcaldav.test.util import TestCase
-from calendarserver.tools.util import getDirectory
-
-class ResourcesTestCase(TestCase):
-
-    def setUp(self):
-        super(ResourcesTestCase, self).setUp()
-
-        testRoot = os.path.join(&quot;.&quot;, os.path.dirname(__file__), &quot;resources&quot;)
-
-        xmlFile = os.path.join(testRoot, &quot;users-groups.xml&quot;)
-        config.DirectoryService.params.xmlFile = xmlFile
-
-        xmlFile = os.path.join(testRoot, &quot;resources-locations.xml&quot;)
-        config.ResourceService.params.xmlFile = xmlFile
-        config.ResourceService.Enabled = True
-
-        xmlFile = os.path.join(testRoot, &quot;augments.xml&quot;)
-        config.AugmentService.type = &quot;twistedcaldav.directory.augment.AugmentXMLDB&quot;
-        config.AugmentService.params.xmlFiles = (xmlFile,)
-
-# Uh, what's this testing?
-#    def test_loadConfig(self):
-#        directory = getDirectory()
-
-
-    def test_recordInPrimaryDirectory(self):
-        directory = getDirectory()
-
-        # Look up a user, which comes out of primary directory service
-        record = directory.recordWithUID(&quot;user01&quot;)
-        self.assertNotEquals(record, None)
-
-
-    def test_recordInSupplementalDirectory(self):
-        directory = getDirectory()
-
-        # Look up a resource, which comes out of locations/resources service
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertNotEquals(record, None)
-
-
-    def test_augments(self):
-        directory = getDirectory()
-
-        # Primary directory
-        record = directory.recordWithUID(&quot;user01&quot;)
-        self.assertEquals(record.enabled, True)
-        self.assertEquals(record.enabledForCalendaring, True)
-        record = directory.recordWithUID(&quot;user02&quot;)
-        self.assertEquals(record.enabled, False)
-        self.assertEquals(record.enabledForCalendaring, False)
-
-        # Supplemental directory
-        record = directory.recordWithUID(&quot;resource01&quot;)
-        self.assertEquals(record.enabled, True)
-        self.assertEquals(record.enabledForCalendaring, True)
-        self.assertEquals(record.autoSchedule, True)
-        record = directory.recordWithUID(&quot;resource02&quot;)
-        self.assertEquals(record.enabled, False)
-        self.assertEquals(record.enabledForCalendaring, False)
-        self.assertEquals(record.autoSchedule, False)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorytesttest_xmlfilepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_xmlfile.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_xmlfile.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/test/test_xmlfile.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1,375 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.python.filepath import CachingFilePath as FilePath
-
-from twistedcaldav.directory import augment
-from twistedcaldav.directory.directory import DirectoryService
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.test.util import TestCase, xmlFile, augmentsFile
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class XMLFileBase(object):
-    &quot;&quot;&quot;
-    L{XMLFileBase} is a base/mix-in object for testing L{XMLDirectoryService}
-    (or things that depend on L{IDirectoryService} and need a simple
-    implementation to use).
-    &quot;&quot;&quot;
-    recordTypes = set((
-        DirectoryService.recordType_users,
-        DirectoryService.recordType_groups,
-        DirectoryService.recordType_locations,
-        DirectoryService.recordType_resources,
-        DirectoryService.recordType_addresses,
-    ))
-
-    users = {
-        &quot;admin&quot;      : {&quot;password&quot;: &quot;nimda&quot;, &quot;guid&quot;: &quot;D11F03A0-97EA-48AF-9A6C-FAC7F3975766&quot;, &quot;addresses&quot;: ()},
-        &quot;wsanchez&quot;   : {&quot;password&quot;: &quot;zehcnasw&quot;, &quot;guid&quot;: &quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;, &quot;addresses&quot;: (&quot;mailto:wsanchez@example.com&quot;,)},
-        &quot;cdaboo&quot;     : {&quot;password&quot;: &quot;oobadc&quot;, &quot;guid&quot;: &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;, &quot;addresses&quot;: (&quot;mailto:cdaboo@example.com&quot;,)  },
-        &quot;lecroy&quot;     : {&quot;password&quot;: &quot;yorcel&quot;, &quot;guid&quot;: &quot;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&quot;, &quot;addresses&quot;: (&quot;mailto:lecroy@example.com&quot;,)  },
-        &quot;dreid&quot;      : {&quot;password&quot;: &quot;dierd&quot;, &quot;guid&quot;: &quot;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&quot;, &quot;addresses&quot;: (&quot;mailto:dreid@example.com&quot;,)   },
-        &quot;nocalendar&quot; : {&quot;password&quot;: &quot;radnelacon&quot;, &quot;guid&quot;: &quot;543D28BA-F74F-4D5F-9243-B3E3A61171E5&quot;, &quot;addresses&quot;: ()},
-        &quot;user01&quot;     : {&quot;password&quot;: &quot;01user&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:c4ca4238a0@example.com&quot;,)},
-        &quot;user02&quot;     : {&quot;password&quot;: &quot;02user&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:c81e728d9d@example.com&quot;,)},
-   }
-
-    groups = {
-        &quot;admin&quot;      : {&quot;password&quot;: &quot;admin&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_groups, &quot;managers&quot;),)},
-        &quot;managers&quot;   : {&quot;password&quot;: &quot;managers&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_users , &quot;lecroy&quot;),)},
-        &quot;grunts&quot;     : {&quot;password&quot;: &quot;grunts&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_users , &quot;wsanchez&quot;),
-                                                                                               (DirectoryService.recordType_users , &quot;cdaboo&quot;),
-                                                                                               (DirectoryService.recordType_users , &quot;dreid&quot;))},
-        &quot;right_coast&quot;: {&quot;password&quot;: &quot;right_coast&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_users , &quot;cdaboo&quot;),)},
-        &quot;left_coast&quot; : {&quot;password&quot;: &quot;left_coast&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_users , &quot;wsanchez&quot;),
-                                                                                               (DirectoryService.recordType_users , &quot;dreid&quot;),
-                                                                                               (DirectoryService.recordType_users , &quot;lecroy&quot;))},
-        &quot;both_coasts&quot;: {&quot;password&quot;: &quot;both_coasts&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_groups, &quot;right_coast&quot;),
-                                                                                               (DirectoryService.recordType_groups, &quot;left_coast&quot;))},
-        &quot;recursive1_coasts&quot;: {&quot;password&quot;: &quot;recursive1_coasts&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_groups, &quot;recursive2_coasts&quot;),
-                                                                                               (DirectoryService.recordType_users, &quot;wsanchez&quot;))},
-        &quot;recursive2_coasts&quot;: {&quot;password&quot;: &quot;recursive2_coasts&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_groups, &quot;recursive1_coasts&quot;),
-                                                                                               (DirectoryService.recordType_users, &quot;cdaboo&quot;))},
-        &quot;non_calendar_group&quot;: {&quot;password&quot;: &quot;non_calendar_group&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (), &quot;members&quot;: ((DirectoryService.recordType_users , &quot;cdaboo&quot;),
-                                                                                               (DirectoryService.recordType_users , &quot;lecroy&quot;))},
-   }
-
-    locations = {
-        &quot;mercury&quot;: {&quot;password&quot;: &quot;mercury&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:mercury@example.com&quot;,)},
-        &quot;gemini&quot; : {&quot;password&quot;: &quot;gemini&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:gemini@example.com&quot;,)},
-        &quot;apollo&quot; : {&quot;password&quot;: &quot;apollo&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:apollo@example.com&quot;,)},
-        &quot;orion&quot;  : {&quot;password&quot;: &quot;orion&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:orion@example.com&quot;,)},
-   }
-
-    resources = {
-        &quot;transporter&quot;        : {&quot;password&quot;: &quot;transporter&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:transporter@example.com&quot;,)       },
-        &quot;ftlcpu&quot;             : {&quot;password&quot;: &quot;ftlcpu&quot;, &quot;guid&quot;: None, &quot;addresses&quot;: (&quot;mailto:ftlcpu@example.com&quot;,)            },
-        &quot;non_calendar_proxy&quot; : {&quot;password&quot;: &quot;non_calendar_proxy&quot;, &quot;guid&quot;: &quot;non_calendar_proxy&quot;, &quot;addresses&quot;: (&quot;mailto:non_calendar_proxy@example.com&quot;,)},
-   }
-
-
-    def xmlFile(self):
-        &quot;&quot;&quot;
-        Create a L{FilePath} that points to a temporary file containing a copy
-        of C{twistedcaldav/directory/test/accounts.xml}.
-
-        @see: L{xmlFile}
-
-        @rtype: L{FilePath}
-        &quot;&quot;&quot;
-        if not hasattr(self, &quot;_xmlFile&quot;):
-            self._xmlFile = FilePath(self.mktemp())
-            xmlFile.copyTo(self._xmlFile)
-        return self._xmlFile
-
-
-    def augmentsFile(self):
-        &quot;&quot;&quot;
-        Create a L{FilePath} that points to a temporary file containing a copy
-        of C{twistedcaldav/directory/test/augments.xml}.
-
-        @see: L{augmentsFile}
-
-        @rtype: L{FilePath}
-        &quot;&quot;&quot;
-        if not hasattr(self, &quot;_augmentsFile&quot;):
-            self._augmentsFile = FilePath(self.mktemp())
-            augmentsFile.copyTo(self._augmentsFile)
-        return self._augmentsFile
-
-
-    def service(self):
-        &quot;&quot;&quot;
-        Create an L{XMLDirectoryService} based on the contents of the paths
-        returned by L{XMLFileBase.augmentsFile} and L{XMLFileBase.xmlFile}.
-
-        @rtype: L{XMLDirectoryService}
-        &quot;&quot;&quot;
-        return XMLDirectoryService(
-            {
-                'xmlFile': self.xmlFile(),
-                'augmentService':
-                    augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)),
-            },
-            alwaysStat=True
-        )
-
-
-
-class XMLFile (
-    XMLFileBase,
-    twistedcaldav.directory.test.util.BasicTestCase,
-    twistedcaldav.directory.test.util.DigestTestCase
-):
-    &quot;&quot;&quot;
-    Test XML file based directory implementation.
-    &quot;&quot;&quot;
-
-    def test_changedXML(self):
-        service = self.service()
-
-        self.xmlFile().open(&quot;w&quot;).write(
-&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;user&gt;
-    &lt;uid&gt;admin&lt;/uid&gt;
-    &lt;guid&gt;admin&lt;/guid&gt;
-    &lt;password&gt;nimda&lt;/password&gt;
-    &lt;name&gt;Super User&lt;/name&gt;
-  &lt;/user&gt;
-&lt;/accounts&gt;
-&quot;&quot;&quot;
-        )
-        for recordType, expectedRecords in (
-            (DirectoryService.recordType_users     , (&quot;admin&quot;,)),
-            (DirectoryService.recordType_groups    , ()),
-            (DirectoryService.recordType_locations , ()),
-            (DirectoryService.recordType_resources , ()),
-        ):
-            # Fault records in
-            for name in expectedRecords:
-                service.recordWithShortName(recordType, name)
-
-            self.assertEquals(
-                set(r.shortNames[0] for r in service.listRecords(recordType)),
-                set(expectedRecords)
-            )
-
-
-    def test_okAutoSchedule(self):
-        service = self.service()
-
-        self.xmlFile().open(&quot;w&quot;).write(
-&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;location&gt;
-    &lt;uid&gt;my office&lt;/uid&gt;
-    &lt;guid&gt;myoffice&lt;/guid&gt;
-    &lt;password&gt;nimda&lt;/password&gt;
-    &lt;name&gt;Super User&lt;/name&gt;
-  &lt;/location&gt;
-&lt;/accounts&gt;
-&quot;&quot;&quot;
-        )
-        self.augmentsFile().open(&quot;w&quot;).write(
-&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-&lt;augments&gt;
-  &lt;record&gt;
-    &lt;uid&gt;myoffice&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-&lt;/augments&gt;
-&quot;&quot;&quot;
-        )
-        service.augmentService.refresh()
-
-        for recordType, expectedRecords in (
-            (DirectoryService.recordType_users     , ()),
-            (DirectoryService.recordType_groups    , ()),
-            (DirectoryService.recordType_locations , (&quot;my office&quot;,)),
-            (DirectoryService.recordType_resources , ()),
-        ):
-            # Fault records in
-            for name in expectedRecords:
-                service.recordWithShortName(recordType, name)
-
-            self.assertEquals(
-                set(r.shortNames[0] for r in service.listRecords(recordType)),
-                set(expectedRecords)
-            )
-        self.assertTrue(service.recordWithShortName(DirectoryService.recordType_locations, &quot;my office&quot;).autoSchedule)
-
-
-    def test_okDisableCalendar(self):
-        service = self.service()
-
-        self.xmlFile().open(&quot;w&quot;).write(
-&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;group&gt;
-    &lt;uid&gt;enabled&lt;/uid&gt;
-    &lt;guid&gt;enabled&lt;/guid&gt;
-    &lt;password&gt;enabled&lt;/password&gt;
-    &lt;name&gt;Enabled&lt;/name&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
-    &lt;uid&gt;disabled&lt;/uid&gt;
-    &lt;guid&gt;disabled&lt;/guid&gt;
-    &lt;password&gt;disabled&lt;/password&gt;
-    &lt;name&gt;Disabled&lt;/name&gt;
-  &lt;/group&gt;
-&lt;/accounts&gt;
-&quot;&quot;&quot;
-        )
-
-        for recordType, expectedRecords in (
-            (DirectoryService.recordType_users     , ()),
-            (DirectoryService.recordType_groups    , (&quot;enabled&quot;, &quot;disabled&quot;)),
-            (DirectoryService.recordType_locations , ()),
-            (DirectoryService.recordType_resources , ()),
-        ):
-            # Fault records in
-            for name in expectedRecords:
-                service.recordWithShortName(recordType, name)
-
-            self.assertEquals(
-                set(r.shortNames[0] for r in service.listRecords(recordType)),
-                set(expectedRecords)
-            )
-
-        # All groups are disabled
-        self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, &quot;enabled&quot;).enabledForCalendaring)
-        self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, &quot;disabled&quot;).enabledForCalendaring)
-
-
-    def test_readExtras(self):
-        service = self.service()
-
-        self.xmlFile().open(&quot;w&quot;).write(
-&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;location&gt;
-    &lt;uid&gt;my office&lt;/uid&gt;
-    &lt;guid&gt;myoffice&lt;/guid&gt;
-    &lt;name&gt;My Office&lt;/name&gt;
-    &lt;extras&gt;
-        &lt;comment&gt;This is the comment&lt;/comment&gt;
-        &lt;capacity&gt;40&lt;/capacity&gt;
-    &lt;/extras&gt;
-  &lt;/location&gt;
-&lt;/accounts&gt;
-&quot;&quot;&quot;
-        )
-
-        record = service.recordWithShortName(
-            DirectoryService.recordType_locations, &quot;my office&quot;)
-        self.assertEquals(record.guid, &quot;myoffice&quot;)
-        self.assertEquals(record.extras[&quot;comment&quot;], &quot;This is the comment&quot;)
-        self.assertEquals(record.extras[&quot;capacity&quot;], &quot;40&quot;)
-
-
-    def test_writeExtras(self):
-        service = self.service()
-
-        service.createRecord(DirectoryService.recordType_locations, &quot;newguid&quot;,
-            shortNames=(&quot;New office&quot;,),
-            fullName=&quot;My New Office&quot;,
-            address=&quot;1 Infinite Loop, Cupertino, CA&quot;,
-            capacity=&quot;10&quot;,
-            comment=&quot;Test comment&quot;,
-        )
-
-        record = service.recordWithShortName(
-            DirectoryService.recordType_locations, &quot;New office&quot;)
-        self.assertEquals(record.extras[&quot;comment&quot;], &quot;Test comment&quot;)
-        self.assertEquals(record.extras[&quot;capacity&quot;], &quot;10&quot;)
-
-        service.updateRecord(DirectoryService.recordType_locations, &quot;newguid&quot;,
-            shortNames=(&quot;New office&quot;,),
-            fullName=&quot;My Newer Office&quot;,
-            address=&quot;2 Infinite Loop, Cupertino, CA&quot;,
-            capacity=&quot;20&quot;,
-            comment=&quot;Test comment updated&quot;,
-        )
-
-        record = service.recordWithShortName(
-            DirectoryService.recordType_locations, &quot;New office&quot;)
-        self.assertEquals(record.fullName, &quot;My Newer Office&quot;)
-        self.assertEquals(record.extras[&quot;address&quot;], &quot;2 Infinite Loop, Cupertino, CA&quot;)
-        self.assertEquals(record.extras[&quot;comment&quot;], &quot;Test comment updated&quot;)
-        self.assertEquals(record.extras[&quot;capacity&quot;], &quot;20&quot;)
-
-        service.destroyRecord(DirectoryService.recordType_locations, &quot;newguid&quot;)
-
-        record = service.recordWithShortName(
-            DirectoryService.recordType_locations, &quot;New office&quot;)
-        self.assertEquals(record, None)
-
-
-    def test_indexing(self):
-        service = self.service()
-        self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_SHORTNAME, &quot;usera&quot;))
-        self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_CUA, &quot;mailto:wsanchez@example.com&quot;))
-        self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_GUID, &quot;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2&quot;))
-        self.assertNotEquals(None, service._lookupInIndex(service.recordType_locations, service.INDEX_TYPE_SHORTNAME, &quot;orion&quot;))
-        self.assertEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_CUA, &quot;mailto:nobody@example.com&quot;))
-
-
-    def test_repeat(self):
-        service = self.service()
-        record = service.recordWithShortName(
-            DirectoryService.recordType_users, &quot;user01&quot;)
-        self.assertEquals(record.fullName, &quot;c4ca4238a0b923820dcc509a6f75849bc4c User 01&quot;)
-        self.assertEquals(record.firstName, &quot;c4ca4&quot;)
-        self.assertEquals(record.lastName, &quot;c4ca4238a User 01&quot;)
-        self.assertEquals(record.emailAddresses, set(['c4ca4238a0@example.com']))
-
-
-
-class XMLFileSubset (XMLFileBase, TestCase):
-    &quot;&quot;&quot;
-    Test the recordTypes subset feature of XMLFile service.
-    &quot;&quot;&quot;
-    recordTypes = set((
-        DirectoryService.recordType_users,
-        DirectoryService.recordType_groups,
-    ))
-
-
-    def test_recordTypesSubset(self):
-        directory = XMLDirectoryService(
-            {
-                'xmlFile' : self.xmlFile(),
-                'augmentService' :
-                    augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)),
-                'recordTypes' :
-                    (
-                        DirectoryService.recordType_users,
-                        DirectoryService.recordType_groups
-                    ),
-            },
-            alwaysStat=True
-        )
-        self.assertEquals(set((&quot;users&quot;, &quot;groups&quot;)), set(directory.recordTypes()))
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectoryutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/util.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/util.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/util.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -34,7 +34,10 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from uuid import UUID, uuid5
</span><ins>+from twisted.python.failure import Failure
+from twisted.web.template import tags
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> def uuidFromName(namespace, name):
</span><span class="lines">@@ -148,3 +151,76 @@
</span><span class="cx">         else:
</span><span class="cx">             response = StatusResponse(responsecode.NOT_FOUND, &quot;Resource not found&quot;)
</span><span class="cx">             returnValue(response)
</span><ins>+
+
+
+
+def formatLink(url):
+    &quot;&quot;&quot;
+    Convert a URL string into some twisted.web.template DOM objects for
+    rendering as a link to itself.
+    &quot;&quot;&quot;
+    return tags.a(href=url)(url)
+
+
+
+def formatLinks(urls):
+    &quot;&quot;&quot;
+    Format a list of URL strings as a list of twisted.web.template DOM links.
+    &quot;&quot;&quot;
+    return formatList(formatLink(link) for link in urls)
+
+
+def formatPrincipals(principals):
+    &quot;&quot;&quot;
+    Format a list of principals into some twisted.web.template DOM objects.
+    &quot;&quot;&quot;
+    def recordKey(principal):
+        try:
+            record = principal.record
+        except AttributeError:
+            try:
+                record = principal.parent.record
+            except:
+                return None
+        return (record.recordType, record.shortNames[0])
+
+
+    def describe(principal):
+        if hasattr(principal, &quot;record&quot;):
+            return &quot; - %s&quot; % (principal.record.displayName,)
+        else:
+            return &quot;&quot;
+
+    return formatList(
+        tags.a(href=principal.principalURL())(
+            str(principal), describe(principal)
+        )
+        for principal in sorted(principals, key=recordKey)
+    )
+
+
+
+def formatList(iterable):
+    &quot;&quot;&quot;
+    Format a list of stuff as an interable.
+    &quot;&quot;&quot;
+    thereAreAny = False
+    try:
+        item = None
+        for item in iterable:
+            thereAreAny = True
+            yield &quot; -&gt; &quot;
+            if item is None:
+                yield &quot;None&quot;
+            else:
+                yield item
+            yield &quot;\n&quot;
+    except Exception, e:
+        log.error(&quot;Exception while rendering: %s&quot; % (e,))
+        Failure().printTraceback()
+        yield &quot;  ** %s **: %s\n&quot; % (e.__class__.__name__, e)
+    if not thereAreAny:
+        yield &quot; '()\n&quot;
+
+
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavdirectorywikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/wiki.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/wiki.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/directory/wiki.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -19,32 +19,32 @@
</span><span class="cx"> as other principals.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-__all__ = [
-    &quot;WikiDirectoryService&quot;,
-]
</del><span class="cx"> 
</span><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav.config import config
+from twisted.web.xmlrpc import Proxy, Fault
</ins><span class="cx"> from calendarserver.platform.darwin.wiki import accessForUserToWiki
</span><ins>+from twext.python.log import Logger
</ins><span class="cx"> 
</span><span class="cx"> from twext.internet.gaiendpoint import MultiFailure
</span><del>-from twext.python.log import Logger
</del><span class="cx"> from txweb2 import responsecode
</span><del>-from txweb2.auth.wrapper import UnauthorizedResponse
-from txweb2.dav.resource import TwistedACLInheritable
</del><ins>+# from txweb2.auth.wrapper import UnauthorizedResponse
+# from txweb2.dav.resource import TwistedACLInheritable
</ins><span class="cx"> from txweb2.http import HTTPError, StatusResponse
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><span class="cx"> from twisted.web.error import Error as WebError
</span><del>-from twisted.web.xmlrpc import Proxy, Fault
</del><span class="cx"> 
</span><del>-from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService, \
-    DirectoryRecord, UnknownRecordTypeError
</del><ins>+# from twistedcaldav.directory.directory import DirectoryService, \
+#     DirectoryRecord, UnknownRecordTypeError
</ins><span class="cx"> 
</span><del>-from txdav.xml import element as davxml
</del><ins>+# from txdav.xml import element as davxml
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><del>-class WikiDirectoryService(DirectoryService):
</del><ins>+# class WikiDirectoryService(DirectoryService):
+
+
+class WikiDirectoryService(object):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     L{IDirectoryService} implementation for Wikis.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -57,81 +57,81 @@
</span><span class="cx">     UIDPrefix = &quot;wiki-&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __repr__(self):
-        return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.realmName)
</del><ins>+#     def __repr__(self):
+#         return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.realmName)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self):
-        super(WikiDirectoryService, self).__init__()
-        self.byUID = {}
-        self.byShortName = {}
</del><ins>+#     def __init__(self):
+#         super(WikiDirectoryService, self).__init__()
+#         self.byUID = {}
+#         self.byShortName = {}
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def recordTypes(self):
-        return (WikiDirectoryService.recordType_wikis,)
</del><ins>+#     def recordTypes(self):
+#         return (WikiDirectoryService.recordType_wikis,)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def listRecords(self, recordType):
-        return ()
</del><ins>+#     def listRecords(self, recordType):
+#         return ()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def recordWithShortName(self, recordType, shortName):
-        if recordType != WikiDirectoryService.recordType_wikis:
-            raise UnknownRecordTypeError(recordType)
</del><ins>+#     def recordWithShortName(self, recordType, shortName):
+#         if recordType != WikiDirectoryService.recordType_wikis:
+#             raise UnknownRecordTypeError(recordType)
</ins><span class="cx"> 
</span><del>-        if shortName in self.byShortName:
-            record = self.byShortName[shortName]
-            return record
</del><ins>+#         if shortName in self.byShortName:
+#             record = self.byShortName[shortName]
+#             return record
</ins><span class="cx"> 
</span><del>-        record = self._addRecord(shortName)
-        return record
</del><ins>+#         record = self._addRecord(shortName)
+#         return record
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def recordWithUID(self, uid):
</del><ins>+#     def recordWithUID(self, uid):
</ins><span class="cx"> 
</span><del>-        if uid in self.byUID:
-            record = self.byUID[uid]
-            return record
</del><ins>+#         if uid in self.byUID:
+#             record = self.byUID[uid]
+#             return record
</ins><span class="cx"> 
</span><del>-        if uid.startswith(self.UIDPrefix):
-            shortName = uid[len(self.UIDPrefix):]
-            record = self._addRecord(shortName)
-            return record
-        else:
-            return None
</del><ins>+#         if uid.startswith(self.UIDPrefix):
+#             shortName = uid[len(self.UIDPrefix):]
+#             record = self._addRecord(shortName)
+#             return record
+#         else:
+#             return None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _addRecord(self, shortName):
</del><ins>+#     def _addRecord(self, shortName):
</ins><span class="cx"> 
</span><del>-        record = WikiDirectoryRecord(
-            self,
-            WikiDirectoryService.recordType_wikis,
-            shortName,
-            None
-        )
-        self.byUID[record.uid] = record
-        self.byShortName[shortName] = record
-        return record
</del><ins>+#         record = WikiDirectoryRecord(
+#             self,
+#             WikiDirectoryService.recordType_wikis,
+#             shortName,
+#             None
+#         )
+#         self.byUID[record.uid] = record
+#         self.byShortName[shortName] = record
+#         return record
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class WikiDirectoryRecord(DirectoryRecord):
-    &quot;&quot;&quot;
-    L{DirectoryRecord} implementation for Wikis.
-    &quot;&quot;&quot;
</del><ins>+# class WikiDirectoryRecord(DirectoryRecord):
+#     &quot;&quot;&quot;
+#     L{DirectoryRecord} implementation for Wikis.
+#     &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-    def __init__(self, service, recordType, shortName, entry):
-        super(WikiDirectoryRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            guid=None,
-            shortNames=(shortName,),
-            fullName=shortName,
-            enabledForCalendaring=True,
-            uid=&quot;%s%s&quot; % (WikiDirectoryService.UIDPrefix, shortName),
-        )
-        # Wiki enabling doesn't come from augments db, so enable here...
-        self.enabled = True
</del><ins>+#     def __init__(self, service, recordType, shortName, entry):
+#         super(WikiDirectoryRecord, self).__init__(
+#             service=service,
+#             recordType=recordType,
+#             guid=None,
+#             shortNames=(shortName,),
+#             fullName=shortName,
+#             enabledForCalendaring=True,
+#             uid=&quot;%s%s&quot; % (WikiDirectoryService.UIDPrefix, shortName),
+#         )
+#         # Wiki enabling doesn't come from augments db, so enable here...
+#         self.enabled = True
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -250,118 +250,120 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@inlineCallbacks
</del><span class="cx"> def getWikiACL(resource, request):
</span><del>-    &quot;&quot;&quot;
-    Ask the wiki server we're paired with what level of access the authnUser has.
</del><ins>+    return succeed(None)
+# @inlineCallbacks
+# def getWikiACL(resource, request):
+#     &quot;&quot;&quot;
+#     Ask the wiki server we're paired with what level of access the authnUser has.
</ins><span class="cx"> 
</span><del>-    Returns an ACL.
</del><ins>+#     Returns an ACL.
</ins><span class="cx"> 
</span><del>-    Wiki authentication is a bit tricky because the end-user accessing a group
-    calendar may not actually be enabled for calendaring.  Therefore in that
-    situation, the authzUser will have been replaced with the wiki principal
-    in locateChild( ), so that any changes the user makes will have the wiki
-    as the originator.  The authnUser will always be the end-user.
-    &quot;&quot;&quot;
-    from twistedcaldav.directory.principal import DirectoryPrincipalResource
</del><ins>+#     Wiki authentication is a bit tricky because the end-user accessing a group
+#     calendar may not actually be enabled for calendaring.  Therefore in that
+#     situation, the authzUser will have been replaced with the wiki principal
+#     in locateChild( ), so that any changes the user makes will have the wiki
+#     as the originator.  The authnUser will always be the end-user.
+#     &quot;&quot;&quot;
+#     from twistedcaldav.directory.principal import DirectoryPrincipalResource
</ins><span class="cx"> 
</span><del>-    if (not hasattr(resource, &quot;record&quot;) or
-        resource.record.recordType != WikiDirectoryService.recordType_wikis):
-        returnValue(None)
</del><ins>+#     if (not hasattr(resource, &quot;record&quot;) or
+#         resource.record.recordType != WikiDirectoryService.recordType_wikis):
+#         returnValue(None)
</ins><span class="cx"> 
</span><del>-    if hasattr(request, 'wikiACL'):
-        returnValue(request.wikiACL)
</del><ins>+#     if hasattr(request, 'wikiACL'):
+#         returnValue(request.wikiACL)
</ins><span class="cx"> 
</span><del>-    userID = &quot;unauthenticated&quot;
-    wikiID = resource.record.shortNames[0]
</del><ins>+#     userID = &quot;unauthenticated&quot;
+#     wikiID = resource.record.shortNames[0]
</ins><span class="cx"> 
</span><del>-    try:
-        url = str(request.authnUser.children[0])
-        principal = (yield request.locateResource(url))
-        if isinstance(principal, DirectoryPrincipalResource):
-            userID = principal.record.guid
-    except:
-        # TODO: better error handling
-        pass
</del><ins>+#     try:
+#         url = str(request.authnUser.children[0])
+#         principal = (yield request.locateResource(url))
+#         if isinstance(principal, DirectoryPrincipalResource):
+#             userID = principal.record.guid
+#     except:
+#         # TODO: better error handling
+#         pass
</ins><span class="cx"> 
</span><del>-    try:
-        access = (yield getWikiAccess(userID, wikiID))
</del><ins>+#     try:
+#         access = (yield getWikiAccess(userID, wikiID))
</ins><span class="cx"> 
</span><del>-        # The ACL we returns has ACEs for the end-user and the wiki principal
-        # in case authzUser is the wiki principal.
-        if access == &quot;read&quot;:
-            request.wikiACL = davxml.ACL(
-                davxml.ACE(
-                    request.authnUser,
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
</del><ins>+#         # The ACL we returns has ACEs for the end-user and the wiki principal
+#         # in case authzUser is the wiki principal.
+#         if access == &quot;read&quot;:
+#             request.wikiACL = davxml.ACL(
+#                 davxml.ACE(
+#                     request.authnUser,
+#                     davxml.Grant(
+#                         davxml.Privilege(davxml.Read()),
+#                         davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
</ins><span class="cx"> 
</span><del>-                        # We allow write-properties so that direct sharees can change
-                        # e.g. calendar color properties
-                        davxml.Privilege(davxml.WriteProperties()),
-                    ),
-                    TwistedACLInheritable(),
-                ),
-                davxml.ACE(
-                    davxml.Principal(
-                        davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiID,))
-                    ),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                    ),
-                    TwistedACLInheritable(),
-                )
-            )
-            returnValue(request.wikiACL)
</del><ins>+#                         # We allow write-properties so that direct sharees can change
+#                         # e.g. calendar color properties
+#                         davxml.Privilege(davxml.WriteProperties()),
+#                     ),
+#                     TwistedACLInheritable(),
+#                 ),
+#                 davxml.ACE(
+#                     davxml.Principal(
+#                         davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiID,))
+#                     ),
+#                     davxml.Grant(
+#                         davxml.Privilege(davxml.Read()),
+#                         davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+#                     ),
+#                     TwistedACLInheritable(),
+#                 )
+#             )
+#             returnValue(request.wikiACL)
</ins><span class="cx"> 
</span><del>-        elif access in (&quot;write&quot;, &quot;admin&quot;):
-            request.wikiACL = davxml.ACL(
-                davxml.ACE(
-                    request.authnUser,
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Privilege(davxml.Write()),
-                    ),
-                    TwistedACLInheritable(),
-                ),
-                davxml.ACE(
-                    davxml.Principal(
-                        davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiID,))
-                    ),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Privilege(davxml.Write()),
-                    ),
-                    TwistedACLInheritable(),
-                )
-            )
-            returnValue(request.wikiACL)
</del><ins>+#         elif access in (&quot;write&quot;, &quot;admin&quot;):
+#             request.wikiACL = davxml.ACL(
+#                 davxml.ACE(
+#                     request.authnUser,
+#                     davxml.Grant(
+#                         davxml.Privilege(davxml.Read()),
+#                         davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+#                         davxml.Privilege(davxml.Write()),
+#                     ),
+#                     TwistedACLInheritable(),
+#                 ),
+#                 davxml.ACE(
+#                     davxml.Principal(
+#                         davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiID,))
+#                     ),
+#                     davxml.Grant(
+#                         davxml.Privilege(davxml.Read()),
+#                         davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+#                         davxml.Privilege(davxml.Write()),
+#                     ),
+#                     TwistedACLInheritable(),
+#                 )
+#             )
+#             returnValue(request.wikiACL)
</ins><span class="cx"> 
</span><del>-        else: # &quot;no-access&quot;:
</del><ins>+#         else: # &quot;no-access&quot;:
</ins><span class="cx"> 
</span><del>-            if userID == &quot;unauthenticated&quot;:
-                # Return a 401 so they have an opportunity to log in
-                response = (yield UnauthorizedResponse.makeResponse(
-                    request.credentialFactories,
-                    request.remoteAddr,
-                ))
-                raise HTTPError(response)
</del><ins>+#             if userID == &quot;unauthenticated&quot;:
+#                 # Return a 401 so they have an opportunity to log in
+#                 response = (yield UnauthorizedResponse.makeResponse(
+#                     request.credentialFactories,
+#                     request.remoteAddr,
+#                 ))
+#                 raise HTTPError(response)
</ins><span class="cx"> 
</span><del>-            raise HTTPError(
-                StatusResponse(
-                    responsecode.FORBIDDEN,
-                    &quot;You are not allowed to access this wiki&quot;
-                )
-            )
</del><ins>+#             raise HTTPError(
+#                 StatusResponse(
+#                     responsecode.FORBIDDEN,
+#                     &quot;You are not allowed to access this wiki&quot;
+#                 )
+#             )
</ins><span class="cx"> 
</span><del>-    except HTTPError:
-        # pass through the HTTPError we might have raised above
-        raise
</del><ins>+#     except HTTPError:
+#         # pass through the HTTPError we might have raised above
+#         raise
</ins><span class="cx"> 
</span><del>-    except Exception, e:
-        log.error(&quot;Wiki ACL lookup failed: %s&quot; % (e,))
-        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Wiki ACL lookup failed&quot;))
</del><ins>+#     except Exception, e:
+#         log.error(&quot;Wiki ACL lookup failed: %s&quot; % (e,))
+#         raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Wiki ACL lookup failed&quot;))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavextensionspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/extensions.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/extensions.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/extensions.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -66,8 +66,8 @@
</span><span class="cx"> from twistedcaldav.method.report import http_REPORT
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twext.who.expression import Operand, MatchType, MatchFlags
</ins><span class="cx"> 
</span><del>-
</del><span class="cx"> thisModule = getModule(__name__)
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -95,7 +95,7 @@
</span><span class="cx">             msg = &quot;Bad XML: unknown value for test attribute: %s&quot; % (testMode,)
</span><span class="cx">             log.warn(msg)
</span><span class="cx">             raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
</span><del>-        operand = &quot;and&quot; if testMode == &quot;allof&quot; else &quot;or&quot;
</del><ins>+        operand = Operand.AND if testMode == &quot;allof&quot; else Operand.OR
</ins><span class="cx"> 
</span><span class="cx">         # Are we narrowing results down to a single CUTYPE?
</span><span class="cx">         cuType = principal_property_search.attributes.get(&quot;type&quot;, None)
</span><span class="lines">@@ -144,10 +144,18 @@
</span><span class="cx">                     log.warn(msg)
</span><span class="cx">                     raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
</span><span class="cx"> 
</span><ins>+                # Convert to twext.who.expression form
+                matchType = {
+                    &quot;starts-with&quot;: MatchType.startsWith,
+                    &quot;contains&quot;: MatchType.contains,
+                    &quot;equals&quot;: MatchType.equals
+                }.get(matchType)
+                matchFlags = MatchFlags.caseInsensitive if caseless else MatchFlags.none
+
</ins><span class="cx">                 # Ignore any query strings under three letters
</span><del>-                matchText = str(match)
</del><ins>+                matchText = match.toString()  # gives us unicode
</ins><span class="cx">                 if len(matchText) &gt;= 3:
</span><del>-                    propertySearches.append((props.children, matchText, caseless, matchType))
</del><ins>+                    propertySearches.append((props.children, matchText, matchFlags, matchType))
</ins><span class="cx"> 
</span><span class="cx">             elif child.qname() == (calendarserver_namespace, &quot;limit&quot;):
</span><span class="cx">                 try:
</span><span class="lines">@@ -182,7 +190,7 @@
</span><span class="cx">         # See if we can take advantage of the directory
</span><span class="cx">         fields = []
</span><span class="cx">         nonDirectorySearches = []
</span><del>-        for props, match, caseless, matchType in propertySearches:
</del><ins>+        for props, match, matchFlags, matchType in propertySearches:
</ins><span class="cx">             nonDirectoryProps = []
</span><span class="cx">             for prop in props:
</span><span class="cx">                 try:
</span><span class="lines">@@ -191,12 +199,12 @@
</span><span class="cx">                 except ValueError, e:
</span><span class="cx">                     raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
</span><span class="cx">                 if fieldName:
</span><del>-                    fields.append((fieldName, match, caseless, matchType))
</del><ins>+                    fields.append((fieldName, match, matchFlags, matchType))
</ins><span class="cx">                 else:
</span><span class="cx">                     nonDirectoryProps.append(prop)
</span><span class="cx">             if nonDirectoryProps:
</span><span class="cx">                 nonDirectorySearches.append((nonDirectoryProps, match,
</span><del>-                    caseless, matchType))
</del><ins>+                    matchFlags, matchType))
</ins><span class="cx"> 
</span><span class="cx">         matchingResources = []
</span><span class="cx">         matchcount = 0
</span><span class="lines">@@ -208,7 +216,7 @@
</span><span class="cx">                 operand=operand, cuType=cuType))
</span><span class="cx"> 
</span><span class="cx">             for record in records:
</span><del>-                resource = principalCollection.principalForRecord(record)
</del><ins>+                resource = yield principalCollection.principalForRecord(record)
</ins><span class="cx">                 if resource:
</span><span class="cx">                     matchingResources.append(resource)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1016,8 +1016,6 @@
</span><span class="cx">         &quot;Enabled&quot;: True,
</span><span class="cx">         &quot;MemcachedPool&quot; : &quot;Default&quot;,
</span><span class="cx">         &quot;UpdateSeconds&quot; : 300,
</span><del>-        &quot;ExpireSeconds&quot; : 86400,
-        &quot;LockSeconds&quot;   : 600,
</del><span class="cx">         &quot;EnableUpdater&quot; : True,
</span><span class="cx">         &quot;UseExternalProxies&quot; : False,
</span><span class="cx">     },
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/storebridge.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/storebridge.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/storebridge.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -1989,7 +1989,7 @@
</span><span class="cx">                 # Access level comes from what the wiki has granted to the
</span><span class="cx">                 # sharee
</span><span class="cx">                 sharee = self.principalForUID(shareeUID)
</span><del>-                userID = sharee.record.guid
</del><ins>+                userID = sharee.record.uid
</ins><span class="cx">                 wikiID = owner.record.shortNames[0]
</span><span class="cx">                 access = (yield getWikiAccess(userID, wikiID))
</span><span class="cx">                 if access == &quot;read&quot;:
</span><span class="lines">@@ -2866,7 +2866,7 @@
</span><span class="cx">                 principalURL = str(authz_principal)
</span><span class="cx">                 if principalURL:
</span><span class="cx">                     authz = (yield request.locateResource(principalURL))
</span><del>-                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.guid
</del><ins>+                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.uid
</ins><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 response = (yield self.storeComponent(component, smart_merge=schedule_tag_match))
</span><span class="lines">@@ -3587,7 +3587,7 @@
</span><span class="cx">                 principalURL = str(authz_principal)
</span><span class="cx">                 if principalURL:
</span><span class="cx">                     authz = (yield request.locateResource(principalURL))
</span><del>-                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.guid
</del><ins>+                    self._parentResource._newStoreObject._txn._authz_uid = authz.record.uid
</ins><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 response = (yield self.storeComponent(component))
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_addressbookmultigetpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookmultiget.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookmultiget.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookmultiget.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -31,7 +31,10 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from txdav.xml import element as davxml
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class AddressBookMultiget (StoreTestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     addressbook-multiget REPORT
</span><span class="lines">@@ -39,6 +42,13 @@
</span><span class="cx">     data_dir = os.path.join(os.path.dirname(__file__), &quot;data&quot;)
</span><span class="cx">     vcards_dir = os.path.join(data_dir, &quot;vCards&quot;)
</span><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def setUp(self):
+        yield StoreTestCase.setUp(self)
+        self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+
+
</ins><span class="cx">     def test_multiget_some_vcards(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         All vcards.
</span><span class="lines">@@ -207,7 +217,7 @@
</span><span class="cx"> &lt;/D:set&gt;
</span><span class="cx"> &lt;/D:mkcol&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><del>-            response = yield self.send(SimpleStoreRequest(self, &quot;MKCOL&quot;, addressbook_uri, content=mkcol, authid=&quot;wsanchez&quot;))
</del><ins>+            response = yield self.send(SimpleStoreRequest(self, &quot;MKCOL&quot;, addressbook_uri, content=mkcol, authRecord=self.authRecord))
</ins><span class="cx"> 
</span><span class="cx">             response = IResponse(response)
</span><span class="cx"> 
</span><span class="lines">@@ -221,7 +231,7 @@
</span><span class="cx">                         &quot;PUT&quot;,
</span><span class="cx">                         joinURL(addressbook_uri, filename + &quot;.vcf&quot;),
</span><span class="cx">                         headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/vcard&quot;)}),
</span><del>-                        authid=&quot;wsanchez&quot;
</del><ins>+                        authRecord=self.authRecord
</ins><span class="cx">                     )
</span><span class="cx">                     request.stream = MemoryStream(icaldata)
</span><span class="cx">                     yield self.send(request)
</span><span class="lines">@@ -235,12 +245,12 @@
</span><span class="cx">                         &quot;PUT&quot;,
</span><span class="cx">                         joinURL(addressbook_uri, child.basename()),
</span><span class="cx">                         headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/vcard&quot;)}),
</span><del>-                        authid=&quot;wsanchez&quot;
</del><ins>+                        authRecord=self.authRecord
</ins><span class="cx">                     )
</span><span class="cx">                     request.stream = MemoryStream(child.getContent())
</span><span class="cx">                     yield self.send(request)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;REPORT&quot;, addressbook_uri, authid=&quot;wsanchez&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;REPORT&quot;, addressbook_uri, authRecord=self.authRecord)
</ins><span class="cx">         request.stream = MemoryStream(query.toxml())
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_addressbookquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookquery.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookquery.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_addressbookquery.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -27,7 +27,10 @@
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.python.filepath import FilePath
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class AddressBookQuery(StoreTestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     addressbook-query REPORT
</span><span class="lines">@@ -67,6 +70,7 @@
</span><span class="cx"> 
</span><span class="cx">         oldValue = config.MaxQueryWithDataResults
</span><span class="cx">         config.MaxQueryWithDataResults = 1
</span><ins>+
</ins><span class="cx">         def _restoreValueOK(f):
</span><span class="cx">             config.MaxQueryWithDataResults = oldValue
</span><span class="cx">             return None
</span><span class="lines">@@ -89,6 +93,7 @@
</span><span class="cx"> 
</span><span class="cx">         oldValue = config.MaxQueryWithDataResults
</span><span class="cx">         config.MaxQueryWithDataResults = 1
</span><ins>+
</ins><span class="cx">         def _restoreValueOK(f):
</span><span class="cx">             config.MaxQueryWithDataResults = oldValue
</span><span class="cx">             return None
</span><span class="lines">@@ -191,15 +196,16 @@
</span><span class="cx">         if response.code != responsecode.CREATED:
</span><span class="cx">             self.fail(&quot;MKCOL failed: %s&quot; % (response.code,))
</span><span class="cx">         '''
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         # Add vCards to addressbook
</span><span class="cx">         for child in FilePath(self.vcards_dir).children():
</span><span class="cx">             if os.path.splitext(child.basename())[1] != &quot;.vcf&quot;:
</span><span class="cx">                 continue
</span><del>-            request = SimpleStoreRequest(self, &quot;PUT&quot;, joinURL(addressbook_uri, child.basename()), authid=&quot;wsanchez&quot;)
</del><ins>+            request = SimpleStoreRequest(self, &quot;PUT&quot;, joinURL(addressbook_uri, child.basename()), authRecord=authRecord)
</ins><span class="cx">             request.stream = MemoryStream(child.getContent())
</span><span class="cx">             yield self.send(request)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;REPORT&quot;, addressbook_uri, authid=&quot;wsanchez&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;REPORT&quot;, addressbook_uri, authRecord=authRecord)
</ins><span class="cx">         request.stream = MemoryStream(query.toxml())
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_calendarquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_calendarquery.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_calendarquery.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_calendarquery.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -34,8 +34,8 @@
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState
</span><del>-from twistedcaldav.directory.directory import DirectoryService
</del><span class="cx"> from txdav.caldav.datastore.query.filter import TimeRange
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -79,7 +79,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Put the contents of the Holidays directory into the store.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        record = self.directory.recordWithShortName(DirectoryService.recordType_users, &quot;wsanchez&quot;)
</del><ins>+        record = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         yield self.transactionUnderTest().calendarHomeWithUID(record.uid, create=True)
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=record.uid)
</span><span class="cx">         for f in os.listdir(self.holidays_dir):
</span><span class="lines">@@ -248,6 +248,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.patch(config, &quot;MaxQueryWithDataResults&quot;, 1)
</span><ins>+
</ins><span class="cx">         def _restoreValueOK(f):
</span><span class="cx">             self.fail(&quot;REPORT must fail with 403&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -268,6 +269,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.patch(config, &quot;MaxQueryWithDataResults&quot;, 1)
</span><ins>+
</ins><span class="cx">         def _restoreValueError(f):
</span><span class="cx">             self.fail(&quot;REPORT must not fail with 403&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -343,7 +345,8 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def calendar_query(self, query, got_xml):
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;REPORT&quot;, &quot;/calendars/users/wsanchez/calendar/&quot;, authid=&quot;wsanchez&quot;)
</del><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+        request = SimpleStoreRequest(self, &quot;REPORT&quot;, &quot;/calendars/users/wsanchez/calendar/&quot;, authRecord=authRecord)
</ins><span class="cx">         request.stream = MemoryStream(query.toxml())
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_collectioncontentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_collectioncontents.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_collectioncontents.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_collectioncontents.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -14,22 +14,22 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><del>-from txweb2 import responsecode
-from txweb2.iweb import IResponse
-from txweb2.stream import MemoryStream, FileStream
-from txweb2.http_headers import MimeType
-
</del><ins>+from twext.who.idirectory import RecordType
+from twisted.internet.defer import inlineCallbacks
</ins><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><del>-
-
</del><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><del>-from txweb2.dav.util import joinURL
</del><span class="cx"> from txdav.caldav.datastore.sql import CalendarObject
</span><ins>+from txweb2 import responsecode
+from txweb2.dav.util import joinURL
+from txweb2.http_headers import MimeType
+from txweb2.iweb import IResponse
+from txweb2.stream import MemoryStream, FileStream
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class CollectionContents(StoreTestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     PUT request
</span><span class="lines">@@ -52,7 +52,7 @@
</span><span class="cx">         def _fakeDoImplicitScheduling(self, component, inserting, internal_state):
</span><span class="cx">             return False, None, False, None
</span><span class="cx"> 
</span><del>-        self.patch(CalendarObject , &quot;doImplicitScheduling&quot;,
</del><ins>+        self.patch(CalendarObject, &quot;doImplicitScheduling&quot;,
</ins><span class="cx">                    _fakeDoImplicitScheduling)
</span><span class="cx"> 
</span><span class="cx">         # Tests in this suite assume that the root resource is a calendar home.
</span><span class="lines">@@ -61,31 +61,27 @@
</span><span class="cx">         return super(CollectionContents, self).setUp()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_collection_in_calendar(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make (regular) collection in calendar
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calendar_uri = &quot;/calendars/users/wsanchez/collection_in_calendar/&quot;
</span><span class="cx"> 
</span><del>-        def mkcalendar_cb(response):
-            response = IResponse(response)
-
-            if response.code != responsecode.CREATED:
-                self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
-
-            def mkcol_cb(response):
-                response = IResponse(response)
-
-                if response.code != responsecode.FORBIDDEN:
-                    self.fail(&quot;Incorrect response to nested MKCOL: %s&quot; % (response.code,))
-
</del><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=authRecord)
+        response = yield self.send(request)
+        response = IResponse(response)
+        if response.code != responsecode.CREATED:
+            self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</ins><span class="cx">             nested_uri = joinURL(calendar_uri, &quot;nested&quot;)
</span><span class="cx"> 
</span><del>-            request = SimpleStoreRequest(self, &quot;MKCOL&quot;, nested_uri, authid=&quot;wsanchez&quot;)
-            return self.send(request, mkcol_cb)
</del><ins>+            request = SimpleStoreRequest(self, &quot;MKCOL&quot;, nested_uri, authRecord=authRecord)
+            response = yield self.send(request)
+            response = IResponse(response)
</ins><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;wsanchez&quot;)
-        return self.send(request, mkcalendar_cb)
</del><ins>+            if response.code != responsecode.FORBIDDEN:
+                self.fail(&quot;Incorrect response to nested MKCOL: %s&quot; % (response.code,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_bogus_file(self):
</span><span class="lines">@@ -163,6 +159,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _test_file_in_calendar(self, what, *work):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Creates a calendar collection, then PUTs a resource into that collection
</span><span class="lines">@@ -171,68 +168,58 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calendar_uri = &quot;/calendars/users/wsanchez/testing_calendar/&quot;
</span><span class="cx"> 
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=authRecord)
+        response = yield self.send(request)
+        response = IResponse(response)
+        if response.code != responsecode.CREATED:
+            self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</ins><span class="cx"> 
</span><del>-        @inlineCallbacks
-        def mkcalendar_cb(response):
</del><ins>+        c = 0
+        for stream, response_code in work:
+            dst_uri = joinURL(calendar_uri, &quot;dst%d.ics&quot; % (c,))
+            request = SimpleStoreRequest(self, &quot;PUT&quot;, dst_uri, authRecord=authRecord)
+            request.headers.setHeader(&quot;if-none-match&quot;, &quot;*&quot;)
+            request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;calendar&quot;))
+            request.stream = stream
+            response = yield self.send(request)
</ins><span class="cx">             response = IResponse(response)
</span><span class="cx"> 
</span><del>-            if response.code != responsecode.CREATED:
-                self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</del><ins>+            if response.code != response_code:
+                self.fail(&quot;Incorrect response to %s: %s (!= %s)&quot; % (what, response.code, response_code))
</ins><span class="cx"> 
</span><del>-            c = 0
</del><ins>+            c += 1
</ins><span class="cx"> 
</span><del>-            for stream, response_code in work:
</del><span class="cx"> 
</span><del>-                dst_uri = joinURL(calendar_uri, &quot;dst%d.ics&quot; % (c,))
-                request = SimpleStoreRequest(self, &quot;PUT&quot;, dst_uri, authid=&quot;wsanchez&quot;)
-                request.headers.setHeader(&quot;if-none-match&quot;, &quot;*&quot;)
-                request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;calendar&quot;))
-                request.stream = stream
-                response = yield self.send(request)
-                response = IResponse(response)
</del><span class="cx"> 
</span><del>-                if response.code != response_code:
-                    self.fail(&quot;Incorrect response to %s: %s (!= %s)&quot; % (what, response.code, response_code))
-
-                c += 1
-
-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;wsanchez&quot;)
-        return self.send(request, mkcalendar_cb)
-
-
</del><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_fail_dot_file_put_in_calendar(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make (regular) collection in calendar
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calendar_uri = &quot;/calendars/users/wsanchez/dot_file_in_calendar/&quot;
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=authRecord)
+        response = yield self.send(request)
+        response = IResponse(response)
+        if response.code != responsecode.CREATED:
+            self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</ins><span class="cx"> 
</span><del>-        def mkcalendar_cb(response):
-            response = IResponse(response)
</del><ins>+        stream = self.dataPath.child(
+            &quot;Holidays&quot;).child(
+            &quot;C318AA54-1ED0-11D9-A5E0-000A958A3252.ics&quot;
+        ).open()
+        try:
+            calendar = str(Component.fromStream(stream))
+        finally:
+            stream.close()
</ins><span class="cx"> 
</span><del>-            if response.code != responsecode.CREATED:
-                self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</del><ins>+        event_uri = &quot;/&quot;.join([calendar_uri, &quot;.event.ics&quot;])
</ins><span class="cx"> 
</span><del>-            def put_cb(response):
-                response = IResponse(response)
-
-                if response.code != responsecode.FORBIDDEN:
-                    self.fail(&quot;Incorrect response to dot file PUT: %s&quot; % (response.code,))
-
-            stream = self.dataPath.child(
-                &quot;Holidays&quot;).child(
-                &quot;C318AA54-1ED0-11D9-A5E0-000A958A3252.ics&quot;
-            ).open()
-            try:
-                calendar = str(Component.fromStream(stream))
-            finally:
-                stream.close()
-
-            event_uri = &quot;/&quot;.join([calendar_uri, &quot;.event.ics&quot;])
-
-            request = SimpleStoreRequest(self, &quot;PUT&quot;, event_uri, authid=&quot;wsanchez&quot;)
-            request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;calendar&quot;))
-            request.stream = MemoryStream(calendar)
-            return self.send(request, put_cb)
-
-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;wsanchez&quot;)
-        return self.send(request, mkcalendar_cb)
</del><ins>+        request = SimpleStoreRequest(self, &quot;PUT&quot;, event_uri, authRecord=authRecord)
+        request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;calendar&quot;))
+        request.stream = MemoryStream(calendar)
+        response = yield self.send(request)
+        response = IResponse(response)
+        if response.code != responsecode.FORBIDDEN:
+            self.fail(&quot;Incorrect response to dot file PUT: %s&quot; % (response.code,))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_mkcalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_mkcalendar.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_mkcalendar.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_mkcalendar.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -27,6 +27,10 @@
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
+
+
+
</ins><span class="cx"> class MKCALENDAR (StoreTestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     MKCALENDAR request
</span><span class="lines">@@ -35,6 +39,12 @@
</span><span class="cx">     # Try nesting calendars (should fail)
</span><span class="cx">     # HEAD request on calendar: resourcetype = (collection, calendar)
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def setUp(self):
+        yield StoreTestCase.setUp(self)
+        self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;user01&quot;)
+
+
</ins><span class="cx">     def test_make_calendar(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make calendar
</span><span class="lines">@@ -45,7 +55,7 @@
</span><span class="cx">         if os.path.exists(path):
</span><span class="cx">             rmdir(path)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authRecord=self.authRecord)
</ins><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def do_test(response):
</span><span class="lines">@@ -146,7 +156,7 @@
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authRecord=self.authRecord)
</ins><span class="cx">         request.stream = MemoryStream(mk.toxml())
</span><span class="cx">         return self.send(request, do_test)
</span><span class="cx"> 
</span><span class="lines">@@ -165,7 +175,7 @@
</span><span class="cx"> 
</span><span class="cx">             # FIXME: Check for DAV:resource-must-be-null element
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, uri, authRecord=self.authRecord)
</ins><span class="cx">         return self.send(request, do_test)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -190,8 +200,8 @@
</span><span class="cx"> 
</span><span class="cx">             nested_uri = os.path.join(first_uri, &quot;nested&quot;)
</span><span class="cx"> 
</span><del>-            request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, nested_uri, authid=&quot;user01&quot;)
</del><ins>+            request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, nested_uri, authRecord=self.authRecord)
</ins><span class="cx">             yield self.send(request, do_test)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, first_uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, first_uri, authRecord=self.authRecord)
</ins><span class="cx">         return self.send(request, next)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_multigetpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_multiget.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_multiget.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_multiget.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -14,6 +14,7 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> from txweb2 import responsecode
</span><span class="cx"> from txweb2.dav.util import davXMLFromStream, joinURL
</span><span class="cx"> from txweb2.http_headers import Headers, MimeType
</span><span class="lines">@@ -38,6 +39,12 @@
</span><span class="cx">     data_dir = os.path.join(os.path.dirname(__file__), &quot;data&quot;)
</span><span class="cx">     holidays_dir = os.path.join(data_dir, &quot;Holidays&quot;)
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def setUp(self):
+        yield StoreTestCase.setUp(self)
+        self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+
+
</ins><span class="cx">     def test_multiget_some_events(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         All events.
</span><span class="lines">@@ -262,7 +269,7 @@
</span><span class="cx">     def calendar_query(self, calendar_uri, query, got_xml, data, no_init):
</span><span class="cx"> 
</span><span class="cx">         if not no_init:
</span><del>-            response = yield self.send(SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;wsanchez&quot;))
</del><ins>+            response = yield self.send(SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=self.authRecord))
</ins><span class="cx">             response = IResponse(response)
</span><span class="cx">             if response.code != responsecode.CREATED:
</span><span class="cx">                 self.fail(&quot;MKCALENDAR failed: %s&quot; % (response.code,))
</span><span class="lines">@@ -274,7 +281,7 @@
</span><span class="cx">                         &quot;PUT&quot;,
</span><span class="cx">                         joinURL(calendar_uri, filename + &quot;.ics&quot;),
</span><span class="cx">                         headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/calendar&quot;)}),
</span><del>-                        authid=&quot;wsanchez&quot;
</del><ins>+                        authRecord=self.authRecord
</ins><span class="cx">                     )
</span><span class="cx">                     request.stream = MemoryStream(icaldata)
</span><span class="cx">                     yield self.send(request)
</span><span class="lines">@@ -288,12 +295,12 @@
</span><span class="cx">                         &quot;PUT&quot;,
</span><span class="cx">                         joinURL(calendar_uri, child.basename()),
</span><span class="cx">                         headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/calendar&quot;)}),
</span><del>-                        authid=&quot;wsanchez&quot;
</del><ins>+                        authRecord=self.authRecord
</ins><span class="cx">                     )
</span><span class="cx">                     request.stream = MemoryStream(child.getContent())
</span><span class="cx">                     yield self.send(request)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;REPORT&quot;, calendar_uri, authid=&quot;wsanchez&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;REPORT&quot;, calendar_uri, authRecord=self.authRecord)
</ins><span class="cx">         request.stream = MemoryStream(query.toxml())
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_propspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_props.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_props.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_props.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -19,21 +19,35 @@
</span><span class="cx"> from txweb2.iweb import IResponse
</span><span class="cx"> from txweb2.stream import MemoryStream
</span><span class="cx"> 
</span><ins>+from twisted.internet.defer import inlineCallbacks
+
</ins><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
+
+
+
</ins><span class="cx"> class Properties(StoreTestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalDAV properties
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+
+    @inlineCallbacks
+    def setUp(self):
+        yield StoreTestCase.setUp(self)
+        self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;user01&quot;)
+
+
</ins><span class="cx">     def test_live_props(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Live CalDAV properties
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calendar_uri = &quot;/calendars/users/user01/test/&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">         def mkcalendar_cb(response):
</span><span class="cx">             response = IResponse(response)
</span><span class="cx"> 
</span><span class="lines">@@ -123,24 +137,24 @@
</span><span class="cx">                 return davXMLFromStream(response.stream).addCallback(got_xml)
</span><span class="cx"> 
</span><span class="cx">             query = davxml.PropertyFind(
</span><del>-                        davxml.PropertyContainer(
-                            caldavxml.SupportedCalendarData(),
-                            caldavxml.SupportedCalendarComponentSet(),
-                            davxml.SupportedReportSet(),
-                        ),
-                    )
</del><ins>+                davxml.PropertyContainer(
+                    caldavxml.SupportedCalendarData(),
+                    caldavxml.SupportedCalendarComponentSet(),
+                    davxml.SupportedReportSet(),
+                ),
+            )
</ins><span class="cx"> 
</span><span class="cx">             request = SimpleStoreRequest(
</span><span class="cx">                 self,
</span><span class="cx">                 &quot;PROPFIND&quot;,
</span><span class="cx">                 calendar_uri,
</span><span class="cx">                 headers=http_headers.Headers({&quot;Depth&quot;: &quot;0&quot;}),
</span><del>-                authid=&quot;user01&quot;,
</del><ins>+                authRecord=self.authRecord,
</ins><span class="cx">             )
</span><span class="cx">             request.stream = MemoryStream(query.toxml())
</span><span class="cx">             return self.send(request, propfind_cb)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=self.authRecord)
</ins><span class="cx">         return self.send(request, mkcalendar_cb)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -207,10 +221,10 @@
</span><span class="cx">                 &quot;PROPFIND&quot;,
</span><span class="cx">                 calendar_uri,
</span><span class="cx">                 headers=http_headers.Headers({&quot;Depth&quot;: &quot;0&quot;}),
</span><del>-                authid=&quot;user01&quot;,
</del><ins>+                authRecord=self.authRecord,
</ins><span class="cx">             )
</span><span class="cx">             request.stream = MemoryStream(query.toxml())
</span><span class="cx">             return self.send(request, propfind_cb)
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authid=&quot;user01&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;MKCALENDAR&quot;, calendar_uri, authRecord=self.authRecord)
</ins><span class="cx">         return self.send(request, mkcalendar_cb)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_resourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_resource.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_resource.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_resource.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -14,22 +14,25 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav import carddavxml
+from twistedcaldav.config import config
+from twistedcaldav.notifications import NotificationCollectionResource
+from twistedcaldav.resource import (
+    CalDAVResource, CommonHomeResource,
+    CalendarHomeResource, AddressBookHomeResource
+)
+from twistedcaldav.test.util import (
+    InMemoryPropertyStore, StoreTestCase, SimpleStoreRequest
+)
+from twistedcaldav.test.util import TestCase
</ins><span class="cx"> from txdav.xml.element import HRef, Principal, Unauthenticated
</span><span class="cx"> from txweb2.http import HTTPError
</span><span class="cx"> from txweb2.test.test_server import SimpleRequest
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><span class="cx"> 
</span><del>-from twistedcaldav import carddavxml
-from twistedcaldav.config import config
-from twistedcaldav.resource import CalDAVResource, CommonHomeResource, \
- CalendarHomeResource, AddressBookHomeResource
-from twistedcaldav.test.util import InMemoryPropertyStore, StoreTestCase, \
-    SimpleStoreRequest
-from twistedcaldav.test.util import TestCase
-from twistedcaldav.notifications import NotificationCollectionResource
</del><span class="cx"> 
</span><del>-
</del><span class="cx"> class StubProperty(object):
</span><span class="cx">     def qname(self):
</span><span class="cx">         return &quot;StubQnamespace&quot;, &quot;StubQname&quot;
</span><span class="lines">@@ -185,13 +188,20 @@
</span><span class="cx"> 
</span><span class="cx"> class DefaultAddressBook (StoreTestCase):
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><ins>+    def setUp(self):
+        yield StoreTestCase.setUp(self)
+        self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_pick_default_addressbook(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get adbk
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/addressbooks/users/wsanchez/&quot;, authid=&quot;wsanchez&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/addressbooks/users/wsanchez/&quot;, authRecord=self.authRecord)
</ins><span class="cx">         home = yield request.locateResource(&quot;/addressbooks/users/wsanchez&quot;)
</span><span class="cx"> 
</span><span class="cx">         # default property initially not present
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_sharing.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_sharing.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_sharing.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -185,7 +185,8 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _doPOST(self, body, resultcode=responsecode.OK):
</span><del>-        request = SimpleStoreRequest(self, &quot;POST&quot;, &quot;/calendars/__uids__/user01/calendar/&quot;, content=body, authid=&quot;user01&quot;)
</del><ins>+        authRecord = yield self.directory.recordWithUID(u&quot;user01&quot;)
+        request = SimpleStoreRequest(self, &quot;POST&quot;, &quot;/calendars/__uids__/user01/calendar/&quot;, content=body, authRecord=authRecord)
</ins><span class="cx">         request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;xml&quot;))
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx">         response = IResponse(response)
</span><span class="lines">@@ -210,7 +211,8 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK):
</span><del>-        request = SimpleStoreRequest(self, &quot;POST&quot;, &quot;/calendars/__uids__/user02/&quot;, content=body, authid=&quot;user02&quot;)
</del><ins>+        authRecord = yield self.directory.recordWithUID(u&quot;user02&quot;)
+        request = SimpleStoreRequest(self, &quot;POST&quot;, &quot;/calendars/__uids__/user02/&quot;, content=body, authRecord=authRecord)
</ins><span class="cx">         request.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;xml&quot;))
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx">         response = IResponse(response)
</span><span class="lines">@@ -732,6 +734,7 @@
</span><span class="cx">         self.assertEquals(propInvite, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # MOVE2WHO Fix wiki
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def wikiSetup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -798,7 +801,8 @@
</span><span class="cx">         self.patch(sharing, &quot;getWikiAccess&quot;, stubWikiAccessMethod)
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def listChildrenViaPropfind():
</span><del>-            request = SimpleStoreRequest(self, &quot;PROPFIND&quot;, &quot;/calendars/__uids__/user01/&quot;, authid=&quot;user01&quot;)
</del><ins>+            authRecord = yield self.directory.recordWithUID(u&quot;user01&quot;)
+            request = SimpleStoreRequest(self, &quot;PROPFIND&quot;, &quot;/calendars/__uids__/user01/&quot;, authRecord=authRecord)
</ins><span class="cx">             request.headers.setHeader(&quot;depth&quot;, &quot;1&quot;)
</span><span class="cx">             response = yield self.send(request)
</span><span class="cx">             response = IResponse(response)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtesttest_wrappingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_wrapping.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_wrapping.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/test_wrapping.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -28,11 +28,12 @@
</span><span class="cx"> from txweb2.responsecode import UNAUTHORIZED
</span><span class="cx"> from txweb2.stream import MemoryStream
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
+
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.defer import maybeDeferred
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.test.test_xmlfile import XMLFileBase
</del><span class="cx"> from twistedcaldav.ical import Component as VComponent
</span><span class="cx"> from twistedcaldav.storebridge import DropboxCollection, \
</span><span class="cx">     CalendarCollectionResource
</span><span class="lines">@@ -51,11 +52,14 @@
</span><span class="cx"> 
</span><span class="cx"> import hashlib
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def _todo(f, why):
</span><span class="cx">     f.todo = why
</span><span class="cx">     return f
</span><span class="cx"> rewriteOrRemove = lambda f: _todo(f, &quot;Rewrite or remove&quot;)
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class FakeChanRequest(object):
</span><span class="cx">     code = 'request-not-finished'
</span><span class="cx"> 
</span><span class="lines">@@ -113,7 +117,7 @@
</span><span class="cx">         @param objectText: Some iCalendar text to populate it with.
</span><span class="cx">         @type objectText: str
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        record = self.directory.recordWithShortName(&quot;users&quot;, &quot;wsanchez&quot;)
</del><ins>+        record = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         uid = record.uid
</span><span class="cx">         txn = self.transactionUnderTest()
</span><span class="cx">         home = yield txn.calendarHomeWithUID(uid, True)
</span><span class="lines">@@ -132,7 +136,7 @@
</span><span class="cx">         @param objectText: Some iVcard text to populate it with.
</span><span class="cx">         @type objectText: str
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        record = self.directory.recordWithShortName(&quot;users&quot;, &quot;wsanchez&quot;)
</del><ins>+        record = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         uid = record.uid
</span><span class="cx">         txn = self.transactionUnderTest()
</span><span class="cx">         home = yield txn.addressbookHomeWithUID(uid, True)
</span><span class="lines">@@ -171,9 +175,10 @@
</span><span class="cx">             &quot;http://localhost:8008/&quot; + path
</span><span class="cx">         )
</span><span class="cx">         if user is not None:
</span><del>-            guid = XMLFileBase.users[user][&quot;guid&quot;]
</del><ins>+            record = yield self.directory.recordWithShortName(RecordType.user, user)
+            uid = record.uid
</ins><span class="cx">             req.authnUser = req.authzUser = (
</span><del>-                davxml.Principal(davxml.HRef('/principals/__uids__/' + guid + '/'))
</del><ins>+                davxml.Principal(davxml.HRef('/principals/__uids__/' + uid + '/'))
</ins><span class="cx">             )
</span><span class="cx">         returnValue(aResource)
</span><span class="cx"> 
</span><span class="lines">@@ -271,7 +276,7 @@
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertIsInstance(dropBoxResource, DropboxCollection)
</span><del>-        dropboxHomeType = davxml.ResourceType.dropboxhome #@UndefinedVariable
</del><ins>+        dropboxHomeType = davxml.ResourceType.dropboxhome  # @UndefinedVariable
</ins><span class="cx">         self.assertEquals(dropBoxResource.resourceType(),
</span><span class="cx">                           dropboxHomeType)
</span><span class="cx"> 
</span><span class="lines">@@ -285,7 +290,7 @@
</span><span class="cx">         C{CalendarHome.calendarWithName}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calDavFile = yield self.getResource(&quot;calendars/users/wsanchez/calendar&quot;)
</span><del>-        regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
</del><ins>+        regularCalendarType = davxml.ResourceType.calendar  # @UndefinedVariable
</ins><span class="cx">         self.assertEquals(calDavFile.resourceType(),
</span><span class="cx">                           regularCalendarType)
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -344,8 +349,11 @@
</span><span class="cx">             self.assertIdentical(
</span><span class="cx">                 homeChild._associatedTransaction,
</span><span class="cx">                 homeTransaction,
</span><del>-                &quot;transaction mismatch on %s; %r is not %r &quot; %
-                    (name, homeChild._associatedTransaction, homeTransaction))
</del><ins>+                &quot;transaction mismatch on {n}; {at} is not {ht} &quot;.format(
+                    n=name, at=homeChild._associatedTransaction,
+                    ht=homeTransaction
+                )
+            )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -575,12 +583,13 @@
</span><span class="cx">         yield NamedLock.acquire(txn, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(&quot;uid1&quot;).hexdigest(),))
</span><span class="cx"> 
</span><span class="cx">         # PUT fails
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         request = SimpleStoreRequest(
</span><span class="cx">             self,
</span><span class="cx">             &quot;PUT&quot;,
</span><span class="cx">             &quot;/calendars/users/wsanchez/calendar/1.ics&quot;,
</span><span class="cx">             headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/calendar&quot;)}),
</span><del>-            authid=&quot;wsanchez&quot;
</del><ins>+            authRecord=authRecord
</ins><span class="cx">         )
</span><span class="cx">         request.stream = MemoryStream(&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -606,12 +615,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # PUT works
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         request = SimpleStoreRequest(
</span><span class="cx">             self,
</span><span class="cx">             &quot;PUT&quot;,
</span><span class="cx">             &quot;/calendars/users/wsanchez/calendar/1.ics&quot;,
</span><span class="cx">             headers=Headers({&quot;content-type&quot;: MimeType.fromString(&quot;text/calendar&quot;)}),
</span><del>-            authid=&quot;wsanchez&quot;
</del><ins>+            authRecord=authRecord
</ins><span class="cx">         )
</span><span class="cx">         request.stream = MemoryStream(&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -635,11 +645,12 @@
</span><span class="cx">         txn = self.transactionUnderTest()
</span><span class="cx">         yield NamedLock.acquire(txn, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(&quot;uid1&quot;).hexdigest(),))
</span><span class="cx"> 
</span><ins>+        authRecord = yield self.directory.recordWithShortName(RecordType.user, u&quot;wsanchez&quot;)
</ins><span class="cx">         request = SimpleStoreRequest(
</span><span class="cx">             self,
</span><span class="cx">             &quot;DELETE&quot;,
</span><span class="cx">             &quot;/calendars/users/wsanchez/calendar/1.ics&quot;,
</span><del>-            authid=&quot;wsanchez&quot;
</del><ins>+            authRecord=authRecord
</ins><span class="cx">         )
</span><span class="cx">         response = yield self.send(request)
</span><span class="cx">         self.assertEqual(response.code, responsecode.SERVICE_UNAVAILABLE)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavtestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -28,17 +28,13 @@
</span><span class="cx"> from twisted.python.failure import Failure
</span><span class="cx"> from twistedcaldav import memcacher
</span><span class="cx"> from twistedcaldav.bind import doBind
</span><del>-from twistedcaldav.directory import augment
</del><span class="cx"> from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
</span><del>-from twistedcaldav.directory.aggregate import AggregateDirectoryService
</del><span class="cx"> from twistedcaldav.directory.calendar import (
</span><span class="cx">     DirectoryCalendarHomeProvisioningResource
</span><span class="cx"> )
</span><del>-from twistedcaldav.directory.directory import DirectoryService
</del><span class="cx"> from twistedcaldav.directory.principal import (
</span><span class="cx">     DirectoryPrincipalProvisioningResource)
</span><span class="cx"> from twistedcaldav.directory.util import transactionFromRequest
</span><del>-from twistedcaldav.directory.xmlfile import XMLDirectoryService
</del><span class="cx"> from twistedcaldav.memcacheclient import ClientFactory
</span><span class="cx"> from twistedcaldav.stdconfig import config
</span><span class="cx"> from txdav.caldav.datastore.test.util import buildCalendarStore
</span><span class="lines">@@ -81,92 +77,21 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryFixture(object):
-    &quot;&quot;&quot;
-    Test fixture for creating various parts of the resource hierarchy related
-    to directories.
-    &quot;&quot;&quot;
</del><span class="cx"> 
</span><del>-    def __init__(self):
-        def _setUpPrincipals(ds):
-            # FIXME: see FIXME in
-            # DirectoryPrincipalProvisioningResource.__init__; this performs a
-            # necessary modification to any directory service object for it to
-            # be fully functional.
-            self.principalsResource = DirectoryPrincipalProvisioningResource(
-                &quot;/principals/&quot;, ds
-            )
-        self._directoryChangeHooks = [_setUpPrincipals]
</del><span class="cx"> 
</span><del>-    directoryService = None
-    principalsResource = None
-
-    def addDirectoryService(self, newService):
-        &quot;&quot;&quot;
-        Add an L{IDirectoryService} to this test case.
-
-        If this test case does not have a directory service yet, create it and
-        assign C{directoryService} and C{principalsResource} attributes to this
-        test case.
-
-        If the test case already has a directory service, create an
-        L{AggregateDirectoryService} and re-assign the C{self.directoryService}
-        attribute to point at it instead, while setting the C{realmName} of the
-        new service to match the old one.
-
-        If the test already has an L{AggregateDirectoryService}, create a
-        I{new} L{AggregateDirectoryService} with the same list of services,
-        after adjusting the new service's realm to match the existing ones.
-        &quot;&quot;&quot;
-
-        if self.directoryService is None:
-            directoryService = newService
-        else:
-            newService.realmName = self.directoryService.realmName
-            if isinstance(self.directoryService, AggregateDirectoryService):
-                directories = set(self.directoryService._recordTypes.items())
-                directories.add(newService)
-            else:
-                directories = [newService, self.directoryService]
-            directoryService = AggregateDirectoryService(directories, None)
-
-        self.directoryService = directoryService
-        # FIXME: see FIXME in DirectoryPrincipalProvisioningResource.__init__;
-        # this performs a necessary modification to the directory service object
-        # for it to be fully functional.
-        for hook in self._directoryChangeHooks:
-            hook(directoryService)
-
-
-    def whenDirectoryServiceChanges(self, callback):
-        &quot;&quot;&quot;
-        When the C{directoryService} attribute is changed by
-        L{TestCase.addDirectoryService}, call the given callback in order to
-        update any state which relies upon that service.
-
-        If there's already a directory, invoke the callback immediately.
-        &quot;&quot;&quot;
-        self._directoryChangeHooks.append(callback)
-        if self.directoryService is not None:
-            callback(self.directoryService)
-
-
-
</del><span class="cx"> class SimpleStoreRequest(SimpleRequest):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A SimpleRequest that automatically grabs the proper transaction for a test.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, test, method, uri, headers=None, content=None, authid=None):
</del><ins>+    def __init__(self, test, method, uri, headers=None, content=None, authRecord=None):
</ins><span class="cx">         super(SimpleStoreRequest, self).__init__(test.site, method, uri, headers, content)
</span><span class="cx">         self._test = test
</span><span class="cx">         self._newStoreTransaction = test.transactionUnderTest(txn=transactionFromRequest(self, test.storeUnderTest()))
</span><span class="cx">         self.credentialFactories = {}
</span><span class="cx"> 
</span><span class="cx">         # Fake credentials if auth needed
</span><del>-        if authid is not None:
-            record = self._test.directory.recordWithShortName(DirectoryService.recordType_users, authid)
-            if record:
-                self.authzUser = self.authnUser = element.Principal(element.HRef(&quot;/principals/__uids__/%s/&quot; % (record.uid,)))
</del><ins>+        if authRecord is not None:
+            self.authzUser = self.authnUser = element.Principal(element.HRef(&quot;/principals/__uids__/%s/&quot; % (authRecord.uid,)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -261,16 +186,7 @@
</span><span class="cx">         accounts.setContent(xmlFile.getContent())
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @property
-    def directoryService(self):
-        &quot;&quot;&quot;
-        Read-only alias for L{DirectoryFixture.directoryService} for
-        compatibility with older tests.  TODO: remove this.
-        &quot;&quot;&quot;
-        return self.directory
</del><span class="cx"> 
</span><del>-
-
</del><span class="cx"> class TestCase(txweb2.dav.test.util.TestCase):
</span><span class="cx">     resource_class = RootResource
</span><span class="cx"> 
</span><span class="lines">@@ -284,24 +200,6 @@
</span><span class="cx">                                quota=deriveQuota(self))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def createStockDirectoryService(self):
-        &quot;&quot;&quot;
-        Create a stock C{directoryService} attribute and assign it.
-        &quot;&quot;&quot;
-        self.xmlFile = FilePath(config.DataRoot).child(&quot;accounts.xml&quot;)
-        self.xmlFile.setContent(xmlFile.getContent())
-        self.directoryFixture.addDirectoryService(
-            XMLDirectoryService(
-                {
-                    &quot;xmlFile&quot;: &quot;accounts.xml&quot;,
-                    &quot;augmentService&quot;: augment.AugmentXMLDB(
-                        xmlFiles=(augmentsFile.path,)
-                    ),
-                }
-            )
-        )
-
-
</del><span class="cx">     def setupCalendars(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         When a directory service exists, set up the resources at C{/calendars}
</span><span class="lines">@@ -352,20 +250,10 @@
</span><span class="cx">         config.UsePackageTimezones = True
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @property
-    def directoryService(self):
-        &quot;&quot;&quot;
-        Read-only alias for L{DirectoryFixture.directoryService} for
-        compatibility with older tests.  TODO: remove this.
-        &quot;&quot;&quot;
-        return self.directoryFixture.directoryService
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def setUp(self):
</span><span class="cx">         super(TestCase, self).setUp()
</span><span class="cx"> 
</span><del>-        self.directoryFixture = DirectoryFixture()
-
</del><span class="cx">         # FIXME: this is only here to workaround circular imports
</span><span class="cx">         doBind()
</span><span class="cx"> 
</span><span class="lines">@@ -564,7 +452,6 @@
</span><span class="cx">         that stores the data for that L{CalendarHomeResource}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(HomeTestCase, self).setUp()
</span><del>-        self.createStockDirectoryService()
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         @self.directoryFixture.whenDirectoryServiceChanges
</span><span class="lines">@@ -650,7 +537,6 @@
</span><span class="cx">         file.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(AddressBookHomeTestCase, self).setUp()
</span><del>-        self.createStockDirectoryService()
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         @self.directoryFixture.whenDirectoryServiceChanges
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2twistedcaldavupgradepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/upgrade.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/upgrade.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/upgrade.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -39,11 +39,8 @@
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav.directory import calendaruserproxy
</span><span class="cx"> from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
</span><del>-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.directory import GroupMembershipCacheUpdater
</del><span class="cx"> from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
</span><span class="cx"> from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
</span><del>-from twistedcaldav.directory.xmlfile import XMLDirectoryService
</del><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.mailgateway import MailGatewayTokensDatabase
</span><span class="lines">@@ -61,7 +58,6 @@
</span><span class="cx"> from twisted.protocols.amp import AMP, Command, String, Boolean
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tap.util import getRootResource, FakeRequest
</span><del>-from calendarserver.tools.util import getDirectory
</del><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.mailgateway import migrateTokensToStore
</span><span class="cx"> 
</span><span class="lines">@@ -909,31 +905,31 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-# Deferred
-def migrateFromOD(config, directory):
-    #
-    # Migrates locations and resources from OD
-    #
-    try:
-        from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-        from calendarserver.tools.resources import migrateResources
-    except ImportError:
-        return succeed(None)
</del><ins>+# # Deferred
+# def migrateFromOD(config, directory):
+#     #
+#     # Migrates locations and resources from OD
+#     #
+#     try:
+#         from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
+#         from calendarserver.tools.resources import migrateResources
+#     except ImportError:
+#         return succeed(None)
</ins><span class="cx"> 
</span><del>-    log.warn(&quot;Migrating locations and resources&quot;)
</del><ins>+#     log.warn(&quot;Migrating locations and resources&quot;)
</ins><span class="cx"> 
</span><del>-    userService = directory.serviceForRecordType(&quot;users&quot;)
-    resourceService = directory.serviceForRecordType(&quot;resources&quot;)
-    if (
-        not isinstance(userService, OpenDirectoryService) or
-        not isinstance(resourceService, XMLDirectoryService)
-    ):
-        # Configuration requires no migration
-        return succeed(None)
</del><ins>+#     userService = directory.serviceForRecordType(&quot;users&quot;)
+#     resourceService = directory.serviceForRecordType(&quot;resources&quot;)
+#     if (
+#         not isinstance(userService, OpenDirectoryService) or
+#         not isinstance(resourceService, XMLDirectoryService)
+#     ):
+#         # Configuration requires no migration
+#         return succeed(None)
</ins><span class="cx"> 
</span><del>-    # Create internal copies of resources and locations based on what is
-    # found in OD
-    return migrateResources(userService, resourceService)
</del><ins>+#     # Create internal copies of resources and locations based on what is
+#     # found in OD
+#     return migrateResources(userService, resourceService)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1042,27 +1038,28 @@
</span><span class="cx">                 loader = XMLCalendarUserProxyLoader(self.config.ProxyLoadFromFile)
</span><span class="cx">                 yield loader.updateProxyDB()
</span><span class="cx"> 
</span><del>-            # Populate the group membership cache
-            if (self.config.GroupCaching.Enabled and
-                self.config.GroupCaching.EnableUpdater):
-                proxydb = calendaruserproxy.ProxyDBService
-                if proxydb is None:
-                    proxydbClass = namedClass(self.config.ProxyDBService.type)
-                    proxydb = proxydbClass(**self.config.ProxyDBService.params)
</del><ins>+            # # Populate the group membership cache
+            # if (self.config.GroupCaching.Enabled and
+            #     self.config.GroupCaching.EnableUpdater):
+            #     proxydb = calendaruserproxy.ProxyDBService
+            #     if proxydb is None:
+            #         proxydbClass = namedClass(self.config.ProxyDBService.type)
+            #         proxydb = proxydbClass(**self.config.ProxyDBService.params)
</ins><span class="cx"> 
</span><del>-                updater = GroupMembershipCacheUpdater(proxydb,
-                    directory,
-                    self.config.GroupCaching.UpdateSeconds,
-                    self.config.GroupCaching.ExpireSeconds,
-                    self.config.GroupCaching.LockSeconds,
-                    namespace=self.config.GroupCaching.MemcachedPool,
-                    useExternalProxies=self.config.GroupCaching.UseExternalProxies)
-                yield updater.updateCache(fast=True)
</del><ins>+            #     # MOVE2WHO FIXME: port to new group cacher
+            #     updater = GroupMembershipCacheUpdater(proxydb,
+            #         directory,
+            #         self.config.GroupCaching.UpdateSeconds,
+            #         self.config.GroupCaching.ExpireSeconds,
+            #         self.config.GroupCaching.LockSeconds,
+            #         namespace=self.config.GroupCaching.MemcachedPool,
+            #         useExternalProxies=self.config.GroupCaching.UseExternalProxies)
+            #     yield updater.updateCache(fast=True)
</ins><span class="cx"> 
</span><del>-                uid, gid = getCalendarServerIDs(self.config)
-                dbPath = os.path.join(self.config.DataRoot, &quot;proxies.sqlite&quot;)
-                if os.path.exists(dbPath):
-                    os.chown(dbPath, uid, gid)
</del><ins>+            uid, gid = getCalendarServerIDs(self.config)
+            dbPath = os.path.join(self.config.DataRoot, &quot;proxies.sqlite&quot;)
+            if os.path.exists(dbPath):
+                os.chown(dbPath, uid, gid)
</ins><span class="cx"> 
</span><span class="cx">             # Process old inbox items
</span><span class="cx">             self.store.setMigrating(True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txdavdpsclientpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -23,7 +23,6 @@
</span><span class="cx"> from twext.who.idirectory import RecordType, IDirectoryService
</span><span class="cx"> import twext.who.idirectory
</span><span class="cx"> from twext.who.util import ConstantsContainer
</span><del>-from twisted.cred.credentials import UsernamePassword
</del><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> from twisted.internet.protocol import ClientCreator
</span><span class="lines">@@ -43,7 +42,6 @@
</span><span class="cx"> )
</span><span class="cx"> import txdav.who.delegates
</span><span class="cx"> import txdav.who.idirectory
</span><del>-from txweb2.auth.digest import DigestedCredentials
</del><span class="cx"> from zope.interface import implementer
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -78,22 +76,20 @@
</span><span class="cx">          txdav.who.idirectory.FieldName)
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+
+    # MOVE2WHO: we talked about passing these in instead:
</ins><span class="cx">     # def __init__(self, fieldNames, recordTypes):
</span><span class="cx">     #     self.fieldName = fieldNames
</span><span class="cx">     #     self.recordType = recordTypes
</span><span class="cx"> 
</span><del>-    # MOVE2WHO
</del><ins>+
+    # MOVE2WHO needed?
</ins><span class="cx">     def getGroups(self, guids=None):
</span><span class="cx">         return succeed(set())
</span><del>-
-
-    guid = &quot;1332A615-4D3A-41FE-B636-FBE25BFB982E&quot;
-
</del><span class="cx">     # END MOVE2WHO
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx">     def _dictToRecord(self, serializedFields):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Turn a dictionary of fields sent from the server into a directory
</span><span class="lines">@@ -186,6 +182,13 @@
</span><span class="cx">         # temporary hack until we can fix all callers not to pass strings:
</span><span class="cx">         if isinstance(recordType, (str, unicode)):
</span><span class="cx">             recordType = self.recordType.lookupByName(recordType)
</span><ins>+
+        # MOVE2WHO, REMOVE THIS HACK TOO:
+        if not isinstance(shortName, unicode):
+            log.warn(&quot;Need to change shortName to unicode&quot;)
+            shortName = shortName.decode(&quot;utf-8&quot;)
+
+
</ins><span class="cx">         return self._call(
</span><span class="cx">             RecordWithShortNameCommand,
</span><span class="cx">             self._processSingleRecord,
</span><span class="lines">@@ -195,6 +198,11 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recordWithUID(self, uid):
</span><ins>+        # MOVE2WHO, REMOVE THIS:
+        if not isinstance(uid, unicode):
+            log.warn(&quot;Need to change uid to unicode&quot;)
+            uid = uid.decode(&quot;utf-8&quot;)
+
</ins><span class="cx">         return self._call(
</span><span class="cx">             RecordWithUIDCommand,
</span><span class="cx">             self._processSingleRecord,
</span><span class="lines">@@ -236,45 +244,19 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None):
+        # MOVE2WHO FIXME: Need to add an AMP command
+        raise NotImplementedError
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @implementer(ICalendarStoreDirectoryRecord)
</span><span class="cx"> class DirectoryRecord(BaseDirectoryRecord, CalendarDirectoryRecordMixin):
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def verifyCredentials(self, credentials):
-
-        # XYZZY REMOVE THIS, it bypasses all authentication!:
-        returnValue(True)
-
-        if isinstance(credentials, UsernamePassword):
-            log.debug(&quot;UsernamePassword&quot;)
-            returnValue(
-                (yield self.verifyPlaintextPassword(credentials.password))
-            )
-
-        elif isinstance(credentials, DigestedCredentials):
-            log.debug(&quot;DigestedCredentials&quot;)
-            returnValue(
-                (yield self.verifyHTTPDigest(
-                    self.shortNames[0],
-                    self.service.realmName,
-                    credentials.fields[&quot;uri&quot;],
-                    credentials.fields[&quot;nonce&quot;],
-                    credentials.fields.get(&quot;cnonce&quot;, &quot;&quot;),
-                    credentials.fields[&quot;algorithm&quot;],
-                    credentials.fields.get(&quot;nc&quot;, &quot;&quot;),
-                    credentials.fields.get(&quot;qop&quot;, &quot;&quot;),
-                    credentials.fields[&quot;response&quot;],
-                    credentials.method
-                ))
-            )
-
-
</del><span class="cx">     def verifyPlaintextPassword(self, password):
</span><span class="cx">         return self.service._call(
</span><span class="cx">             VerifyPlaintextPasswordCommand,
</span><span class="lines">@@ -305,6 +287,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def members(self):
</span><span class="cx">         return self.service._call(
</span><span class="cx">             MembersCommand,
</span><span class="lines">@@ -332,8 +315,6 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-
</del><span class="cx">     # For scheduling/freebusy
</span><span class="cx">     # FIXME: doesn't this need to happen in the DPS?
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txdavwhoaugmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/augment.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/augment.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/augment.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -125,6 +125,11 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def recordWithUID(self, uid):
</span><ins>+        # MOVE2WHO, REMOVE THIS:
+        if not isinstance(uid, unicode):
+            log.warn(&quot;Need to change uid to unicode&quot;)
+            uid = uid.decode(&quot;utf-8&quot;)
+
</ins><span class="cx">         record = yield self._directory.recordWithUID(uid)
</span><span class="cx">         record = yield self.augment(record)
</span><span class="cx">         returnValue(record)
</span><span class="lines">@@ -149,6 +154,11 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def recordWithShortName(self, recordType, shortName):
</span><ins>+        # MOVE2WHO, REMOVE THIS:
+        if not isinstance(shortName, unicode):
+            log.warn(&quot;Need to change shortName to unicode&quot;)
+            shortName = shortName.decode(&quot;utf-8&quot;)
+
</ins><span class="cx">         record = yield self._directory.recordWithShortName(recordType, shortName)
</span><span class="cx">         record = yield self.augment(record)
</span><span class="cx">         returnValue(record)
</span><span class="lines">@@ -156,6 +166,11 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def recordsWithEmailAddress(self, emailAddress):
</span><ins>+        # MOVE2WHO, REMOVE THIS:
+        if not isinstance(emailAddress, unicode):
+            log.warn(&quot;Need to change emailAddress to unicode&quot;)
+            emailAddress = emailAddress.decode(&quot;utf-8&quot;)
+
</ins><span class="cx">         records = yield self._directory.recordsWithEmailAddress(emailAddress)
</span><span class="cx">         augmented = []
</span><span class="cx">         for record in records:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txdavwhodirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -20,12 +20,21 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> import uuid
</span><ins>+from twext.python.log import Logger
+
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twext.who.expression import (
</span><span class="cx">     MatchType, Operand, MatchExpression, CompoundExpression, MatchFlags
</span><span class="cx"> )
</span><ins>+from twext.who.idirectory import RecordType as BaseRecordType
+from txdav.who.idirectory import RecordType as DAVRecordType
+from twisted.cred.credentials import UsernamePassword
+from txweb2.auth.digest import DigestedCredentials
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+log = Logger()
+
+
</ins><span class="cx"> __all__ = [
</span><span class="cx">     &quot;CalendarDirectoryRecordMixin&quot;,
</span><span class="cx">     &quot;CalendarDirectoryServiceMixin&quot;,
</span><span class="lines">@@ -34,6 +43,8 @@
</span><span class="cx"> 
</span><span class="cx"> class CalendarDirectoryServiceMixin(object):
</span><span class="cx"> 
</span><ins>+    guid = &quot;1332A615-4D3A-41FE-B636-FBE25BFB982E&quot;
+
</ins><span class="cx">     # Must maintain the hack for a bit longer:
</span><span class="cx">     def setPrincipalCollection(self, principalCollection):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -101,6 +112,40 @@
</span><span class="cx">         return self.recordsFromExpression(expression)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def recordsMatchingFieldsWithCUType(self, fields, operand=Operand.OR,
+                                        cuType=None):
+        if cuType:
+            recordType = CalendarDirectoryRecordMixin.fromCUType(cuType)
+        else:
+            recordType = None
+
+        return self.recordsMatchingFields(
+            fields, operand=operand, recordType=recordType
+        )
+
+
+    def recordsMatchingFields(self, fields, operand=Operand.OR, recordType=None):
+        &quot;&quot;&quot;
+        @param fields: a iterable of tuples, each tuple consisting of:
+            directory field name (C{unicode})
+            search term (C{unicode})
+            match flags (L{twext.who.expression.MatchFlags})
+            match type (L{twext.who.expression.MatchType})
+        &quot;&quot;&quot;
+        subExpressions = []
+        for fieldName, searchTerm, matchFlags, matchType in fields:
+            subExpressions.append(
+                MatchExpression(
+                    self.fieldName.lookupByName(fieldName),
+                    searchTerm,
+                    matchType,
+                    matchFlags
+                )
+            )
+        expression = CompoundExpression(subExpressions, operand)
+        return self.recordsFromExpression(expression)
+
+
</ins><span class="cx">     # FIXME: Existing code assumes record type names are plural. Is there any
</span><span class="cx">     # reason to maintain backwards compatibility?  I suppose there could be
</span><span class="cx">     # scripts referring to record type of &quot;users&quot;, &quot;locations&quot;
</span><span class="lines">@@ -115,6 +160,37 @@
</span><span class="cx"> 
</span><span class="cx"> class CalendarDirectoryRecordMixin(object):
</span><span class="cx"> 
</span><ins>+
+    @inlineCallbacks
+    def verifyCredentials(self, credentials):
+
+        # XYZZY REMOVE THIS, it bypasses all authentication!:
+        returnValue(True)
+
+        if isinstance(credentials, UsernamePassword):
+            log.debug(&quot;UsernamePassword&quot;)
+            returnValue(
+                (yield self.verifyPlaintextPassword(credentials.password))
+            )
+
+        elif isinstance(credentials, DigestedCredentials):
+            log.debug(&quot;DigestedCredentials&quot;)
+            returnValue(
+                (yield self.verifyHTTPDigest(
+                    self.shortNames[0],
+                    self.service.realmName,
+                    credentials.fields[&quot;uri&quot;],
+                    credentials.fields[&quot;nonce&quot;],
+                    credentials.fields.get(&quot;cnonce&quot;, &quot;&quot;),
+                    credentials.fields[&quot;algorithm&quot;],
+                    credentials.fields.get(&quot;nc&quot;, &quot;&quot;),
+                    credentials.fields.get(&quot;qop&quot;, &quot;&quot;),
+                    credentials.fields[&quot;response&quot;],
+                    credentials.method
+                ))
+            )
+
+
</ins><span class="cx">     @property
</span><span class="cx">     def calendarUserAddresses(self):
</span><span class="cx">         if not self.hasCalendars:
</span><span class="lines">@@ -146,18 +222,45 @@
</span><span class="cx">         return frozenset(cuas)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # Mapping from directory record.recordType to RFC2445 CUTYPE values
+    _cuTypes = {
+        BaseRecordType.user: 'INDIVIDUAL',
+        BaseRecordType.group: 'GROUP',
+        DAVRecordType.resource: 'RESOURCE',
+        DAVRecordType.location: 'ROOM',
+    }
+
+
</ins><span class="cx">     def getCUType(self):
</span><del>-        # Mapping from directory record.recordType to RFC2445 CUTYPE values
-        self._cuTypes = {
-            self.service.recordType.user: 'INDIVIDUAL',
-            self.service.recordType.group: 'GROUP',
-            self.service.recordType.resource: 'RESOURCE',
-            self.service.recordType.location: 'ROOM',
-        }
-
</del><span class="cx">         return self._cuTypes.get(self.recordType, &quot;UNKNOWN&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    def fromCUType(cls, cuType):
+        for key, val in cls._cuTypes.iteritems():
+            if val == cuType:
+                return key
+        return None
+
+
+    def applySACLs(self):
+        &quot;&quot;&quot;
+        Disable calendaring and addressbooks as dictated by SACLs
+        &quot;&quot;&quot;
+
+        # FIXME: need to re-implement SACLs
+        # if config.EnableSACLs and self.CheckSACL:
+        #     username = self.shortNames[0]
+        #     if self.CheckSACL(username, &quot;calendar&quot;) != 0:
+        #         self.log.debug(&quot;%s is not enabled for calendaring due to SACL&quot;
+        #                        % (username,))
+        #         self.enabledForCalendaring = False
+        #     if self.CheckSACL(username, &quot;addressbook&quot;) != 0:
+        #         self.log.debug(&quot;%s is not enabled for addressbooks due to SACL&quot;
+        #                        % (username,))
+        #         self.enabledForAddressBooks = False
+
+
</ins><span class="cx">     @property
</span><span class="cx">     def displayName(self):
</span><span class="cx">         return self.fullNames[0]
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txdavwhogroupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/groups.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/groups.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/groups.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -45,17 +45,14 @@
</span><span class="cx">         # Delete all other work items
</span><span class="cx">         yield Delete(From=self.table, Where=None).on(self.transaction)
</span><span class="cx"> 
</span><del>-        oldGroupCacher = getattr(self.transaction, &quot;_groupCacher&quot;, None)
-        newGroupCacher = getattr(self.transaction, &quot;_newGroupCacher&quot;, None)
-        if oldGroupCacher is not None or newGroupCacher is not None:
</del><ins>+        groupCacher = getattr(self.transaction, &quot;_groupCacher&quot;, None)
+        if groupCacher is not None:
</ins><span class="cx"> 
</span><span class="cx">             # Schedule next update
</span><span class="cx"> 
</span><del>-            # TODO: Be sure to move updateSeconds to the new cacher
-            # implementation
</del><span class="cx">             notBefore = (
</span><span class="cx">                 datetime.datetime.utcnow() +
</span><del>-                datetime.timedelta(seconds=oldGroupCacher.updateSeconds)
</del><ins>+                datetime.timedelta(seconds=groupCacher.updateSeconds)
</ins><span class="cx">             )
</span><span class="cx">             log.debug(
</span><span class="cx">                 &quot;Scheduling next group cacher update: {when}&quot;, when=notBefore
</span><span class="lines">@@ -67,22 +64,13 @@
</span><span class="cx"> 
</span><span class="cx">             # New implmementation
</span><span class="cx">             try:
</span><del>-                yield newGroupCacher.update(self.transaction)
</del><ins>+                yield groupCacher.update(self.transaction)
</ins><span class="cx">             except Exception, e:
</span><span class="cx">                 log.error(
</span><span class="cx">                     &quot;Failed to update new group membership cache ({error})&quot;,
</span><span class="cx">                     error=e
</span><span class="cx">                 )
</span><span class="cx"> 
</span><del>-            # Old implmementation
-            # try:
-            #     oldGroupCacher.updateCache()
-            # except Exception, e:
-            #     log.error(
-            #         &quot;Failed to update old group membership cache ({error})&quot;,
-            #         error=e
-            #     )
-
</del><span class="cx">         else:
</span><span class="cx">             notBefore = (
</span><span class="cx">                 datetime.datetime.utcnow() +
</span><span class="lines">@@ -136,11 +124,11 @@
</span><span class="cx">             From=self.table, Where=(self.table.GROUP_GUID == self.groupGuid)
</span><span class="cx">         ).on(self.transaction)
</span><span class="cx"> 
</span><del>-        newGroupCacher = getattr(self.transaction, &quot;_newGroupCacher&quot;, None)
-        if newGroupCacher is not None:
</del><ins>+        groupCacher = getattr(self.transaction, &quot;_groupCacher&quot;, None)
+        if groupCacher is not None:
</ins><span class="cx"> 
</span><span class="cx">             try:
</span><del>-                yield newGroupCacher.refreshGroup(
</del><ins>+                yield groupCacher.refreshGroup(
</ins><span class="cx">                     self.transaction, self.groupGuid.decode(&quot;utf-8&quot;)
</span><span class="cx">                 )
</span><span class="cx">             except Exception, e:
</span><span class="lines">@@ -255,13 +243,16 @@
</span><span class="cx"> 
</span><span class="cx">     def __init__(
</span><span class="cx">         self, directory,
</span><del>-        useExternalProxies=False, externalProxiesSource=None
</del><ins>+        updateSeconds=600,
+        useExternalProxies=False,
+        externalProxiesSource=None
</ins><span class="cx">     ):
</span><span class="cx">         self.directory = directory
</span><span class="cx">         self.useExternalProxies = useExternalProxies
</span><span class="cx">         if useExternalProxies and externalProxiesSource is None:
</span><span class="cx">             externalProxiesSource = self.directory.getExternalProxyAssignments
</span><span class="cx">         self.externalProxiesSource = externalProxiesSource
</span><ins>+        self.updateSeconds = updateSeconds
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txweb2channelhttppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txweb2/channel/http.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txweb2/channel/http.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txweb2/channel/http.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -137,10 +137,10 @@
</span><span class="cx">     subclass, it can parse either the client side or the server side of the
</span><span class="cx">     connection.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    
</del><ins>+
</ins><span class="cx">     # Class config:
</span><span class="cx">     parseCloseAsEnd = False
</span><del>-    
</del><ins>+
</ins><span class="cx">     # Instance vars
</span><span class="cx">     chunkedIn = False
</span><span class="cx">     headerlen = 0
</span><span class="lines">@@ -173,12 +173,12 @@
</span><span class="cx">     #  channel.pauseProducing()
</span><span class="cx">     #  channel.resumeProducing()
</span><span class="cx">     #  channel.stopProducing()
</span><del>-    
-    
</del><ins>+
+
</ins><span class="cx">     def __init__(self, channel):
</span><span class="cx">         self.inHeaders = http_headers.Headers()
</span><span class="cx">         self.channel = channel
</span><del>-        
</del><ins>+
</ins><span class="cx">     def lineReceived(self, line):
</span><span class="cx">         if self.chunkedIn:
</span><span class="cx">             # Parsing a chunked input
</span><span class="lines">@@ -208,7 +208,7 @@
</span><span class="cx">                 self.chunkedIn = 1
</span><span class="cx">             elif self.chunkedIn == 3:
</span><span class="cx">                 # TODO: support Trailers (maybe! but maybe not!)
</span><del>-                
</del><ins>+
</ins><span class="cx">                 # After getting the final &quot;0&quot; chunk we're here, and we *EAT MERCILESSLY*
</span><span class="cx">                 # any trailer headers sent, and wait for the blank line to terminate the
</span><span class="cx">                 # request.
</span><span class="lines">@@ -237,7 +237,7 @@
</span><span class="cx">             self.headerlen += len(line)
</span><span class="cx">             if self.headerlen &gt; self.channel.maxHeaderLength:
</span><span class="cx">                 self._abortWithError(responsecode.BAD_REQUEST, 'Headers too long.')
</span><del>-            
</del><ins>+
</ins><span class="cx">             if line[0] in ' \t':
</span><span class="cx">                 # Append a header continuation
</span><span class="cx">                 self.partialHeader += line
</span><span class="lines">@@ -262,7 +262,7 @@
</span><span class="cx">                 # NOTE: in chunked mode, self.length is the size of the current chunk,
</span><span class="cx">                 # so we still have more to read.
</span><span class="cx">                 self.chunkedIn = 2 # Read next chunksize
</span><del>-            
</del><ins>+
</ins><span class="cx">             channel.setLineMode(extraneous)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -293,13 +293,13 @@
</span><span class="cx">         # Set connection parameters from headers
</span><span class="cx">         self.setConnectionParams(connHeaders)
</span><span class="cx">         self.connHeaders = connHeaders
</span><del>-        
</del><ins>+
</ins><span class="cx">     def allContentReceived(self):
</span><span class="cx">         self.finishedReading = True
</span><span class="cx">         self.channel.requestReadFinished(self)
</span><span class="cx">         self.handleContentComplete()
</span><del>-        
-        
</del><ins>+
+
</ins><span class="cx">     def splitConnectionHeaders(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Split off connection control headers from normal headers.
</span><span class="lines">@@ -382,7 +382,7 @@
</span><span class="cx">         # Okay, now implement section 4.4 Message Length to determine
</span><span class="cx">         # how to find the end of the incoming HTTP message.
</span><span class="cx">         transferEncoding = connHeaders.getHeader('transfer-encoding')
</span><del>-        
</del><ins>+
</ins><span class="cx">         if transferEncoding:
</span><span class="cx">             if transferEncoding[-1] == 'chunked':
</span><span class="cx">                 # Chunked
</span><span class="lines">@@ -394,7 +394,7 @@
</span><span class="cx">                 # client-&gt;server data. (Well..it could actually, since TCP has half-close
</span><span class="cx">                 # but the HTTP spec says it can't, so we'll pretend it's right.)
</span><span class="cx">                 self._abortWithError(responsecode.BAD_REQUEST, &quot;Transfer-Encoding received without chunked in last position.&quot;)
</span><del>-            
</del><ins>+
</ins><span class="cx">             # TODO: support gzip/etc encodings.
</span><span class="cx">             # FOR NOW: report an error if the client uses any encodings.
</span><span class="cx">             # They shouldn't, because we didn't send a TE: header saying it's okay.
</span><span class="lines">@@ -423,23 +423,23 @@
</span><span class="cx"> 
</span><span class="cx">         # Set the calculated persistence
</span><span class="cx">         self.channel.setReadPersistent(readPersistent)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def abortParse(self):
</span><span class="cx">         # If we're erroring out while still reading the request
</span><span class="cx">         if not self.finishedReading:
</span><span class="cx">             self.finishedReading = True
</span><span class="cx">             self.channel.setReadPersistent(False)
</span><span class="cx">             self.channel.requestReadFinished(self)
</span><del>-        
</del><ins>+
</ins><span class="cx">     # producer interface
</span><span class="cx">     def pauseProducing(self):
</span><span class="cx">         if not self.finishedReading:
</span><span class="cx">             self.channel.pauseProducing()
</span><del>-        
</del><ins>+
</ins><span class="cx">     def resumeProducing(self):
</span><span class="cx">         if not self.finishedReading:
</span><span class="cx">             self.channel.resumeProducing()
</span><del>-       
</del><ins>+
</ins><span class="cx">     def stopProducing(self):
</span><span class="cx">         if not self.finishedReading:
</span><span class="cx">             self.channel.stopProducing()
</span><span class="lines">@@ -449,13 +449,13 @@
</span><span class="cx">     It is responsible for all the low-level connection oriented behavior.
</span><span class="cx">     Thus, it takes care of keep-alive, de-chunking, etc., and passes
</span><span class="cx">     the non-connection headers on to the user-level Request object.&quot;&quot;&quot;
</span><del>-    
</del><ins>+
</ins><span class="cx">     command = path = version = None
</span><span class="cx">     queued = 0
</span><span class="cx">     request = None
</span><del>-    
</del><ins>+
</ins><span class="cx">     out_version = &quot;HTTP/1.1&quot;
</span><del>-    
</del><ins>+
</ins><span class="cx">     def __init__(self, channel, queued=0):
</span><span class="cx">         HTTPParser.__init__(self, channel)
</span><span class="cx">         self.queued=queued
</span><span class="lines">@@ -466,14 +466,14 @@
</span><span class="cx">             self.transport = StringTransport()
</span><span class="cx">         else:
</span><span class="cx">             self.transport = self.channel.transport
</span><del>-        
</del><ins>+
</ins><span class="cx">         # set the version to a fallback for error generation
</span><span class="cx">         self.version = (1,0)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def gotInitialLine(self, initialLine):
</span><span class="cx">         parts = initialLine.split()
</span><del>-        
</del><ins>+
</ins><span class="cx">         # Parse the initial request line
</span><span class="cx">         if len(parts) != 3:
</span><span class="cx">             if len(parts) == 1:
</span><span class="lines">@@ -490,9 +490,9 @@
</span><span class="cx">                 raise ValueError()
</span><span class="cx">         except ValueError:
</span><span class="cx">             self._abortWithError(responsecode.BAD_REQUEST, &quot;Unknown protocol: %s&quot; % strversion)
</span><del>-        
</del><ins>+
</ins><span class="cx">         self.version = protovers[1:3]
</span><del>-        
</del><ins>+
</ins><span class="cx">         # Ensure HTTP 0 or HTTP 1.
</span><span class="cx">         if self.version[0] &gt; 1:
</span><span class="cx">             self._abortWithError(responsecode.HTTP_VERSION_NOT_SUPPORTED, 'Only HTTP 0.9 and HTTP 1.x are supported.')
</span><span class="lines">@@ -511,18 +511,18 @@
</span><span class="cx"> 
</span><span class="cx">     def processRequest(self):
</span><span class="cx">         self.request.process()
</span><del>-        
</del><ins>+
</ins><span class="cx">     def handleContentChunk(self, data):
</span><span class="cx">         self.request.handleContentChunk(data)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def handleContentComplete(self):
</span><span class="cx">         self.request.handleContentComplete()
</span><del>-        
</del><ins>+
</ins><span class="cx"> ############## HTTPChannelRequest *RESPONSE* methods #############
</span><span class="cx">     producer = None
</span><span class="cx">     chunkedOut = False
</span><span class="cx">     finished = False
</span><del>-    
</del><ins>+
</ins><span class="cx">     ##### Request Callbacks #####
</span><span class="cx">     def writeIntermediateResponse(self, code, headers=None):
</span><span class="cx">         if self.version &gt;= (1,1):
</span><span class="lines">@@ -530,15 +530,15 @@
</span><span class="cx"> 
</span><span class="cx">     def writeHeaders(self, code, headers):
</span><span class="cx">         self._writeHeaders(code, headers, True)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def _writeHeaders(self, code, headers, addConnectionHeaders):
</span><span class="cx">         # HTTP 0.9 doesn't have headers.
</span><span class="cx">         if self.version[0] == 0:
</span><span class="cx">             return
</span><del>-        
</del><ins>+
</ins><span class="cx">         l = []
</span><span class="cx">         code_message = responsecode.RESPONSES.get(code, &quot;Unknown Status&quot;)
</span><del>-        
</del><ins>+
</ins><span class="cx">         l.append('%s %s %s\r\n' % (self.out_version, code,
</span><span class="cx">                                    code_message))
</span><span class="cx">         if headers is not None:
</span><span class="lines">@@ -557,16 +557,16 @@
</span><span class="cx">                 else:
</span><span class="cx">                     # Cannot use persistent connections if we can't do chunking
</span><span class="cx">                     self.channel.dropQueuedRequests()
</span><del>-            
</del><ins>+
</ins><span class="cx">             if self.channel.isLastRequest(self):
</span><span class="cx">                 l.append(&quot;%s: %s\r\n&quot; % ('Connection', 'close'))
</span><span class="cx">             elif self.version &lt; (1,1):
</span><span class="cx">                 l.append(&quot;%s: %s\r\n&quot; % ('Connection', 'Keep-Alive'))
</span><del>-        
</del><ins>+
</ins><span class="cx">         l.append(&quot;\r\n&quot;)
</span><span class="cx">         self.transport.writeSequence(l)
</span><del>-        
-    
</del><ins>+
+
</ins><span class="cx">     def write(self, data):
</span><span class="cx">         if not data:
</span><span class="cx">             return
</span><span class="lines">@@ -574,17 +574,17 @@
</span><span class="cx">             self.transport.writeSequence((&quot;%X\r\n&quot; % len(data), data, &quot;\r\n&quot;))
</span><span class="cx">         else:
</span><span class="cx">             self.transport.write(data)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def finish(self):
</span><span class="cx">         &quot;&quot;&quot;We are finished writing data.&quot;&quot;&quot;
</span><span class="cx">         if self.finished:
</span><span class="cx">             warnings.warn(&quot;Warning! request.finish called twice.&quot;, stacklevel=2)
</span><span class="cx">             return
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.chunkedOut:
</span><span class="cx">             # write last chunk and closing CRLF
</span><span class="cx">             self.transport.write(&quot;0\r\n\r\n&quot;)
</span><del>-        
</del><ins>+
</ins><span class="cx">         self.finished = True
</span><span class="cx">         if not self.queued:
</span><span class="cx">             self._cleanup()
</span><span class="lines">@@ -596,7 +596,7 @@
</span><span class="cx">         the writing side alone. This is mostly for internal use by
</span><span class="cx">         the HTTP request parsing logic, so that it can call an error
</span><span class="cx">         page generator.
</span><del>-        
</del><ins>+
</ins><span class="cx">         Otherwise, completely shut down the connection.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.abortParse()
</span><span class="lines">@@ -604,7 +604,7 @@
</span><span class="cx">             if self.producer:
</span><span class="cx">                 self.producer.stopProducing()
</span><span class="cx">                 self.unregisterProducer()
</span><del>-            
</del><ins>+
</ins><span class="cx">             self.finished = True
</span><span class="cx">             if self.queued:
</span><span class="cx">                 self.transport.reset()
</span><span class="lines">@@ -617,14 +617,14 @@
</span><span class="cx"> 
</span><span class="cx">     def getRemoteHost(self):
</span><span class="cx">         return self.channel.transport.getPeer()
</span><del>-    
</del><ins>+
</ins><span class="cx">     ##### End Request Callbacks #####
</span><span class="cx"> 
</span><span class="cx">     def _abortWithError(self, errorcode, text=''):
</span><span class="cx">         &quot;&quot;&quot;Handle low level protocol errors.&quot;&quot;&quot;
</span><span class="cx">         headers = http_headers.Headers()
</span><span class="cx">         headers.setHeader('content-length', len(text)+1)
</span><del>-        
</del><ins>+
</ins><span class="cx">         self.abortConnection(closeWrite=False)
</span><span class="cx">         self.writeHeaders(errorcode, headers)
</span><span class="cx">         self.write(text)
</span><span class="lines">@@ -632,7 +632,7 @@
</span><span class="cx">         self.finish()
</span><span class="cx">         log.warn(&quot;Aborted request (%d) %s&quot; % (errorcode, text))
</span><span class="cx">         raise AbortedException
</span><del>-    
</del><ins>+
</ins><span class="cx">     def _cleanup(self):
</span><span class="cx">         &quot;&quot;&quot;Called when have finished responding and are no longer queued.&quot;&quot;&quot;
</span><span class="cx">         if self.producer:
</span><span class="lines">@@ -640,7 +640,7 @@
</span><span class="cx">             self.unregisterProducer()
</span><span class="cx">         self.channel.requestWriteFinished(self)
</span><span class="cx">         del self.transport
</span><del>-        
</del><ins>+
</ins><span class="cx">     # methods for channel - end users should not use these
</span><span class="cx"> 
</span><span class="cx">     def noLongerQueued(self):
</span><span class="lines">@@ -674,12 +674,12 @@
</span><span class="cx">     def registerProducer(self, producer, streaming):
</span><span class="cx">         &quot;&quot;&quot;Register a producer.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.producer:
</span><span class="cx">             raise ValueError, &quot;registering producer %s before previous one (%s) was unregistered&quot; % (producer, self.producer)
</span><del>-        
</del><ins>+
</ins><span class="cx">         self.producer = producer
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.queued:
</span><span class="cx">             producer.pauseProducing()
</span><span class="cx">         else:
</span><span class="lines">@@ -698,7 +698,7 @@
</span><span class="cx">             self.producer = None
</span><span class="cx">         if self.request:
</span><span class="cx">             self.request.connectionLost(reason)
</span><del>-    
</del><ins>+
</ins><span class="cx"> class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin, object):
</span><span class="cx">     &quot;&quot;&quot;A receiver for HTTP requests. Handles splitting up the connection
</span><span class="cx">     for the multiple HTTPChannelRequests that may be in progress on this
</span><span class="lines">@@ -714,11 +714,11 @@
</span><span class="cx">     the client.
</span><span class="cx"> 
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    
</del><ins>+
</ins><span class="cx">     implements(interfaces.IHalfCloseableProtocol)
</span><del>-    
</del><ins>+
</ins><span class="cx">     ## Configuration parameters. Set in instances or subclasses.
</span><del>-    
</del><ins>+
</ins><span class="cx">     # How many simultaneous requests to handle.
</span><span class="cx">     maxPipeline = 4
</span><span class="cx"> 
</span><span class="lines">@@ -736,35 +736,35 @@
</span><span class="cx"> 
</span><span class="cx">     # Allow persistent connections?
</span><span class="cx">     allowPersistentConnections = True
</span><del>-    
</del><ins>+
</ins><span class="cx">     # ChannelRequest
</span><span class="cx">     chanRequestFactory = HTTPChannelRequest
</span><span class="cx">     requestFactory = http.Request
</span><del>-    
-    
</del><ins>+
+
</ins><span class="cx">     _first_line = 2
</span><span class="cx">     readPersistent = PERSIST_PIPELINE
</span><del>-    
</del><ins>+
</ins><span class="cx">     _readLost = False
</span><span class="cx">     _writeLost = False
</span><del>-    
</del><ins>+
</ins><span class="cx">     _abortTimer = None
</span><span class="cx">     chanRequest = None
</span><span class="cx"> 
</span><span class="cx">     def _callLater(self, secs, fun):
</span><span class="cx">         reactor.callLater(secs, fun)
</span><del>-    
</del><ins>+
</ins><span class="cx">     def __init__(self):
</span><span class="cx">         # the request queue
</span><span class="cx">         self.requests = []
</span><del>-        
</del><ins>+
</ins><span class="cx">     def connectionMade(self):
</span><span class="cx">         self._secure = interfaces.ISSLTransport(self.transport, None) is not None
</span><span class="cx">         address = self.transport.getHost()
</span><span class="cx">         self._host = _cachedGetHostByAddr(address.host)
</span><span class="cx">         self.setTimeout(self.inputTimeOut)
</span><span class="cx">         self.factory.addConnectedChannel(self)
</span><del>-    
</del><ins>+
</ins><span class="cx">     def lineReceived(self, line):
</span><span class="cx">         if self._first_line:
</span><span class="cx">             self.setTimeout(self.inputTimeOut)
</span><span class="lines">@@ -779,13 +779,13 @@
</span><span class="cx">             if not line and self._first_line == 1:
</span><span class="cx">                 self._first_line = 2
</span><span class="cx">                 return
</span><del>-            
</del><ins>+
</ins><span class="cx">             self._first_line = 0
</span><del>-            
</del><ins>+
</ins><span class="cx">             if not self.allowPersistentConnections:
</span><span class="cx">                 # Don't allow a second request
</span><span class="cx">                 self.readPersistent = False
</span><del>-                
</del><ins>+
</ins><span class="cx">             try:
</span><span class="cx">                 self.chanRequest = self.chanRequestFactory(self, len(self.requests))
</span><span class="cx">                 self.requests.append(self.chanRequest)
</span><span class="lines">@@ -801,7 +801,7 @@
</span><span class="cx">     def lineLengthExceeded(self, line):
</span><span class="cx">         if self._first_line:
</span><span class="cx">             # Fabricate a request object to respond to the line length violation.
</span><del>-            self.chanRequest = self.chanRequestFactory(self, 
</del><ins>+            self.chanRequest = self.chanRequestFactory(self,
</ins><span class="cx">                                                        len(self.requests))
</span><span class="cx">             self.requests.append(self.chanRequest)
</span><span class="cx">             self.chanRequest.gotInitialLine(&quot;GET fake HTTP/1.0&quot;)
</span><span class="lines">@@ -809,7 +809,7 @@
</span><span class="cx">             self.chanRequest.lineLengthExceeded(line, self._first_line)
</span><span class="cx">         except AbortedException:
</span><span class="cx">             pass
</span><del>-            
</del><ins>+
</ins><span class="cx">     def rawDataReceived(self, data):
</span><span class="cx">         self.setTimeout(self.inputTimeOut)
</span><span class="cx">         try:
</span><span class="lines">@@ -821,17 +821,17 @@
</span><span class="cx">         if(self.readPersistent is PERSIST_NO_PIPELINE or
</span><span class="cx">            len(self.requests) &gt;= self.maxPipeline):
</span><span class="cx">             self.pauseProducing()
</span><del>-        
</del><ins>+
</ins><span class="cx">         # reset state variables
</span><span class="cx">         self._first_line = 1
</span><span class="cx">         self.chanRequest = None
</span><span class="cx">         self.setLineMode()
</span><del>-        
</del><ins>+
</ins><span class="cx">         # Set an idle timeout, in case this request takes a long
</span><span class="cx">         # time to finish generating output.
</span><span class="cx">         if len(self.requests) &gt; 0:
</span><span class="cx">             self.setTimeout(self.idleTimeOut)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def _startNextRequest(self):
</span><span class="cx">         # notify next request, if present, it can start writing
</span><span class="cx">         del self.requests[0]
</span><span class="lines">@@ -840,7 +840,7 @@
</span><span class="cx">             self.transport.loseConnection()
</span><span class="cx">         elif self.requests:
</span><span class="cx">             self.requests[0].noLongerQueued()
</span><del>-            
</del><ins>+
</ins><span class="cx">             # resume reading if allowed to
</span><span class="cx">             if(not self._readLost and
</span><span class="cx">                self.readPersistent is not PERSIST_NO_PIPELINE and
</span><span class="lines">@@ -866,11 +866,11 @@
</span><span class="cx">         for request in self.requests[1:]:
</span><span class="cx">             request.connectionLost(None)
</span><span class="cx">         del self.requests[1:]
</span><del>-    
</del><ins>+
</ins><span class="cx">     def isLastRequest(self, request):
</span><span class="cx">         # Is this channel handling the last possible request
</span><span class="cx">         return not self.readPersistent and self.requests[-1] == request
</span><del>-    
</del><ins>+
</ins><span class="cx">     def requestWriteFinished(self, request):
</span><span class="cx">         &quot;&quot;&quot;Called by first request in queue when it is done.&quot;&quot;&quot;
</span><span class="cx">         if request != self.requests[0]: raise TypeError
</span><span class="lines">@@ -878,7 +878,7 @@
</span><span class="cx">         # Don't del because we haven't finished cleanup, so,
</span><span class="cx">         # don't want queue len to be 0 yet.
</span><span class="cx">         self.requests[0] = None
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.readPersistent or len(self.requests) &gt; 1:
</span><span class="cx">             # Do this in the next reactor loop so as to
</span><span class="cx">             # not cause huge call stacks with fast
</span><span class="lines">@@ -910,26 +910,26 @@
</span><span class="cx">             self._abortTimer = None
</span><span class="cx">             self.transport.loseConnection()
</span><span class="cx">             return
</span><del>-        
</del><ins>+
</ins><span class="cx">         # If between requests, drop connection
</span><span class="cx">         # when all current requests have written their data.
</span><span class="cx">         self._readLost = True
</span><span class="cx">         if not self.requests:
</span><span class="cx">             # No requests in progress, lose now.
</span><span class="cx">             self.transport.loseConnection()
</span><del>-            
</del><ins>+
</ins><span class="cx">         # If currently in the process of reading a request, this is
</span><span class="cx">         # probably a client abort, so lose the connection.
</span><span class="cx">         if self.chanRequest:
</span><span class="cx">             self.transport.loseConnection()
</span><del>-        
</del><ins>+
</ins><span class="cx">     def connectionLost(self, reason):
</span><span class="cx">         self.factory.removeConnectedChannel(self)
</span><span class="cx"> 
</span><span class="cx">         self._writeLost = True
</span><span class="cx">         self.readConnectionLost()
</span><span class="cx">         self.setTimeout(None)
</span><del>-        
</del><ins>+
</ins><span class="cx">         # Tell all requests to abort.
</span><span class="cx">         for request in self.requests:
</span><span class="cx">             if request is not None:
</span><span class="lines">@@ -963,7 +963,7 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     protocol = HTTPChannel
</span><del>-    
</del><ins>+
</ins><span class="cx">     protocolArgs = None
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, requestFactory, maxRequests=600, **kwargs):
</span><span class="lines">@@ -977,9 +977,9 @@
</span><span class="cx">     def buildProtocol(self, addr):
</span><span class="cx">         if self.outstandingRequests &gt;= self.maxRequests:
</span><span class="cx">             return OverloadedServerProtocol()
</span><del>-        
</del><ins>+
</ins><span class="cx">         p = protocol.ServerFactory.buildProtocol(self, addr)
</span><del>-        
</del><ins>+
</ins><span class="cx">         for arg,value in self.protocolArgs.iteritems():
</span><span class="cx">             setattr(p, arg, value)
</span><span class="cx">         return p
</span><span class="lines">@@ -1050,19 +1050,19 @@
</span><span class="cx">         return p
</span><span class="cx"> 
</span><span class="cx"> class HTTPLoggingChannelRequest(HTTPChannelRequest):
</span><del>-    
</del><ins>+
</ins><span class="cx">     class TransportLoggingWrapper(object):
</span><del>-        
</del><ins>+
</ins><span class="cx">         def __init__(self, transport, logData):
</span><del>-            
</del><ins>+
</ins><span class="cx">             self.transport = transport
</span><span class="cx">             self.logData = logData
</span><del>-            
</del><ins>+
</ins><span class="cx">         def write(self, data):
</span><span class="cx">             if self.logData is not None and data:
</span><span class="cx">                 self.logData.append(data)
</span><span class="cx">             self.transport.write(data)
</span><del>-            
</del><ins>+
</ins><span class="cx">         def writeSequence(self, seq):
</span><span class="cx">             if self.logData is not None and seq:
</span><span class="cx">                 self.logData.append(''.join(seq))
</span><span class="lines">@@ -1075,7 +1075,7 @@
</span><span class="cx">         def __init__(self):
</span><span class="cx">             self.request = []
</span><span class="cx">             self.response = []
</span><del>-            
</del><ins>+
</ins><span class="cx">     def __init__(self, channel, queued=0):
</span><span class="cx">         super(HTTPLoggingChannelRequest, self).__init__(channel, queued)
</span><span class="cx"> 
</span><span class="lines">@@ -1093,7 +1093,7 @@
</span><span class="cx">         super(HTTPLoggingChannelRequest, self).gotInitialLine(initialLine)
</span><span class="cx"> 
</span><span class="cx">     def lineReceived(self, line):
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.logData is not None:
</span><span class="cx">             # We don't want to log basic credentials
</span><span class="cx">             loggedLine = line
</span><span class="lines">@@ -1105,13 +1105,13 @@
</span><span class="cx">         super(HTTPLoggingChannelRequest, self).lineReceived(line)
</span><span class="cx"> 
</span><span class="cx">     def handleContentChunk(self, data):
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.logData is not None:
</span><span class="cx">             self.logData.request.append(data)
</span><span class="cx">         super(HTTPLoggingChannelRequest, self).handleContentChunk(data)
</span><del>-        
</del><ins>+
</ins><span class="cx">     def handleContentComplete(self):
</span><del>-        
</del><ins>+
</ins><span class="cx">         if self.logData is not None:
</span><span class="cx">             doneTime = time.time()
</span><span class="cx">             self.logData.request.append(&quot;\r\n\r\n&gt;&gt;&gt;&gt; Request complete at: %.3f (elapsed: %.1f ms)&quot; % (doneTime, 1000 * (doneTime - self.startTime),))
</span><span class="lines">@@ -1124,7 +1124,7 @@
</span><span class="cx">         super(HTTPLoggingChannelRequest, self).writeHeaders(code, headers)
</span><span class="cx"> 
</span><span class="cx">     def finish(self):
</span><del>-        
</del><ins>+
</ins><span class="cx">         super(HTTPLoggingChannelRequest, self).finish()
</span><span class="cx"> 
</span><span class="cx">         if self.logData is not None:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who2txweb2serverpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-2/txweb2/server.py (12880 => 12881)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-2/txweb2/server.py        2014-03-12 18:28:29 UTC (rev 12880)
+++ CalendarServer/branches/users/sagen/move2who-2/txweb2/server.py        2014-03-12 18:49:18 UTC (rev 12881)
</span><span class="lines">@@ -192,7 +192,7 @@
</span><span class="cx">                        error.defaultErrorHandler, defaultHeadersFilter]
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, *args, **kw):
</span><del>-        
</del><ins>+
</ins><span class="cx">         self.timeStamps = [(&quot;t&quot;, time.time(),)]
</span><span class="cx"> 
</span><span class="cx">         if kw.has_key('site'):
</span><span class="lines">@@ -308,10 +308,10 @@
</span><span class="cx">         clients into using an inappropriate scheme for subsequent requests. What we should do is
</span><span class="cx">         take the port number from the Host header or request-URI and map that to the scheme that
</span><span class="cx">         matches the service we configured to listen on that port.
</span><del>- 
</del><ins>+
</ins><span class="cx">         @param port: the port number to test
</span><span class="cx">         @type port: C{int}
</span><del>-        
</del><ins>+
</ins><span class="cx">         @return: C{True} if scheme is https (secure), C{False} otherwise
</span><span class="cx">         @rtype: C{bool}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -322,7 +322,7 @@
</span><span class="cx">                 return True
</span><span class="cx">             elif port in self.site.BindSSLPorts:
</span><span class="cx">                 return True
</span><del>-        
</del><ins>+
</ins><span class="cx">         return False
</span><span class="cx"> 
</span><span class="cx">     def _fixupURLParts(self):
</span><span class="lines">@@ -558,7 +558,7 @@
</span><span class="cx">                 break
</span><span class="cx">             else:
</span><span class="cx">                 postSegments.insert(0, preSegments.pop())
</span><del>-        
</del><ins>+
</ins><span class="cx">         if cachedParent is None:
</span><span class="cx">             cachedParent = self.site.resource
</span><span class="cx">             postSegments = segments[1:]
</span></span></pre>
</div>
</div>

</body>
</html>