<!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>[13158] CalendarServer/branches/users/sagen/move2who-5</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/13158">13158</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-04-04 10:20:27 -0700 (Fri, 04 Apr 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>pull up from trunk</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserveraccesslogpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/accesslog.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverprovisionrootpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/root.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverprovisiontesttest_rootpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/test/test_root.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverpushapplepushpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/push/applepush.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertapcaldavpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/caldav.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertaptesttest_caldavpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_caldav.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertaptesttest_utilpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertaputilpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsagentpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/agent.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolscalverifypy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/calverify.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsexportpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/export.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsgatewaypy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/gateway.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsmigratepy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/migrate.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsprincipalspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/principals.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolspurgepy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/purge.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolspushpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/push.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsresourcespy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsshelldirectorypy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsshellterminalpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/terminal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsshelltesttest_vfspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/test/test_vfs.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsshellvfspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/vfs.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayaugmentsxml">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/augments.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewaycaldavdplist">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/caldavd.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayresourceslocationsxml">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/resources-locations.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayusersgroupsxml">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/users-groups.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalscaldavdplist">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/caldavd.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalsresourceslocationsxml">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/resources-locations.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalsusersgroupsxml">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/users-groups.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_agentpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_agent.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_calverifypy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_calverify.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_gatewaypy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_gateway.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_principalspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_principals.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_purgepy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_purge_old_eventspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge_old_events.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_resourcespy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarservertoolsutilpy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverwebadminprincipalspy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/webadmin/principals.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverwebcalresourcepy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/webcal/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confauthaccountstestxml">CalendarServer/branches/users/sagen/move2who-5/conf/auth/accounts-test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confauthaugmentstestxml">CalendarServer/branches/users/sagen/move2who-5/conf/auth/augments-test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confauthproxiestestxml">CalendarServer/branches/users/sagen/move2who-5/conf/auth/proxies-test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confauthresourcestestxml">CalendarServer/branches/users/sagen/move2who-5/conf/auth/resources-test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confcaldavdtestplist">CalendarServer/branches/users/sagen/move2who-5/conf/caldavd-test.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5contribperformanceloadtesttest_simpy">CalendarServer/branches/users/sagen/move2who-5/contrib/performance/loadtest/test_sim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5requirementspy_developtxt">CalendarServer/branches/users/sagen/move2who-5/requirements/py_develop.txt</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavcachepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/cache.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavcustomxmlpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/customxml.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaddressbookpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/addressbook.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaugmentpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendarpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendaruserproxypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxy.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendaruserproxyloaderpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxyloader.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycommonpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/common.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorydirectoryprincipalresourcehtml">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory-principal-resource.html</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryprincipalpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/principal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestaccountsxml">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestaugmentsxml">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/augments.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestresourcesxml">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/resources.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_augmentpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_principalpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_principal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestutilpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryutilpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorybackedaddressbookpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directorybackedaddressbook.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavextensionspy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/extensions.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavfreebusyurlpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/freebusyurl.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavicalpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreportpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_addressbook_querypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_addressbook_query.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_commonpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_common.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_multiget_commonpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_multiget_common.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavresourcepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavscheduling_storecaldavresourcepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/scheduling_store/caldav/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavsharingpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavstdconfigpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavstorebridgepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_addressbookmultigetpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookmultiget.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_addressbookquerypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_cachepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_cache.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_calendarquerypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_calendarquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_collectioncontentspy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_collectioncontents.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_configpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_config.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_icalendarpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_icalendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_mkcalendarpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_mkcalendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_multigetpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_multiget.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_propspy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_props.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_resourcepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_sharingpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_upgradepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_upgrade.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_wrappingpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_wrapping.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtestutilpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtimezoneservicepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezoneservice.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavtimezonestdservicepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezonestdservice.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavupgradepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/upgrade.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavutilpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingcaldavschedulerpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/caldav/scheduler.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingfreebusypy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/freebusy.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingimipinboundpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/imip/inbound.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingimplicitpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/implicit.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduledeliverypy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/delivery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduleresourcepy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduleschedulerpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/scheduler.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingitippy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/itip.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingprocessingpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingschedulerpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/scheduler.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingworkpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/work.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoresqlpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestattachmentsaccountsxml">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestattachmentsresourcesxml">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/resources.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretesttest_attachmentspy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/test_attachments.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestutilpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreutilpy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcaldavicalendardirectoryservicepy">CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/icalendardirectoryservice.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcarddavdatastorequeryfilterpy">CalendarServer/branches/users/sagen/move2who-5/txdav/carddav/datastore/query/filter.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastorefilepy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/file.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingresourcepy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastoresqlpy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavcommondatastoretestutilpy">CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpsclientpy">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/client.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpscommandspy">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/commands.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpsserverpy">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/server.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpstesttestxml">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpstesttest_clientpy">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test_client.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhodelegatespy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/delegates.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhogroupspy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/groups.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhoidirectorypy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/idirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsaccountsxml">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsresourcesxml">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/resources.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_delegatespy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_delegates.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_groupspy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_groups.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhoxmlpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/xml.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txweb2channelhttppy">CalendarServer/branches/users/sagen/move2who-5/txweb2/channel/http.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txweb2davmethodreport_expandpy">CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/method/report_expand.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txweb2davresourcepy">CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txweb2davutilpy">CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txweb2serverpy">CalendarServer/branches/users/sagen/move2who-5/txweb2/server.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/od/</li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5confauthgenerate_test_accountspy">CalendarServer/branches/users/sagen/move2who-5/conf/auth/generate_test_accounts.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavdpsjsonpy">CalendarServer/branches/users/sagen/move2who-5/txdav/dps/json.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhoaugmentpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhodirectorypy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsaugmentsxml">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/augments.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_augmentpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_directorypy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_utilpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_wikipy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhoutilpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhovcardpy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/vcard.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5txdavwhowikipy">CalendarServer/branches/users/sagen/move2who-5/txdav/who/wiki.py</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li>CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/od/</li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5calendarserverplatformdarwinwikipy">CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/wiki.py</a></li>
<li>CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/calverify/</li>
<li>CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/purge/</li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaggregatepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/aggregate.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryappleopendirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/appleopendirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycachingdirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/cachingdirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorydirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryidirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/idirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryldapdirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/ldapdirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryopendirectorybackerpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/opendirectorybacker.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_aggregatepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_aggregate.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_buildquerypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_buildquery.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_cachedirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_cachedirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_directorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_guidchangepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_guidchange.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_ldapdirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_ldapdirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_livedirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_livedirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_modifypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_modify.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_opendirectorypy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_opendirectorybackerpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectorybacker.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_proxyprincipalmemberspy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_proxyprincipalmembers.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_resourcespy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_wikipy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_xmlfilepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_xmlfile.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorywikipy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryxmlaccountsparserpy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlaccountsparser.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryxmlfilepy">CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlfile.py</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who5">CalendarServer/branches/users/sagen/move2who-5/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagenmove2who5"></a>
<div class="propset"><h4>Property changes: CalendarServer/branches/users/sagen/move2who-5</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4>Modified: svn:mergeinfo</h4></div>
<span class="cx">/CalendarServer/branches/config-separation:4379-4443
</span><span class="cx">/CalendarServer/branches/egg-info-351:4589-4625
</span><span class="cx">/CalendarServer/branches/generic-sqlstore:6167-6191
</span><span class="cx">/CalendarServer/branches/new-store:5594-5934
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile:5911-5935
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.2-dev:11972,12357-12358,12794,12814
</span><span class="cx">/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cross-pod-sharing:12038-12191
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/json:11622-11912
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
</span><span class="cx">/CalendarServer/branches/users/cdaboo/scheduling-queue-refresh:11783-12557
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</span><span class="cx">/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
</span><span class="cx">/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
</span><span class="cx">/CalendarServer/branches/users/cdaboo/timezones:7443-7699
</span><span class="cx">/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
</span><span class="cx">/CalendarServer/branches/users/gaya/cleanrevisions:12152-12334
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroupfixes:12120-12142
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><span class="cx">   + /CalDAVTester/trunk:11193-11198
</span><span class="cx">/CalendarServer/branches/config-separation:4379-4443
</span><span class="cx">/CalendarServer/branches/egg-info-351:4589-4625
</span><span class="cx">/CalendarServer/branches/generic-sqlstore:6167-6191
</span><span class="cx">/CalendarServer/branches/new-store:5594-5934
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile:5911-5935
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.2-dev:11972,12357-12358,12794,12814
</span><span class="cx">/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cross-pod-sharing:12038-12191
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/json:11622-11912
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
</span><span class="cx">/CalendarServer/branches/users/cdaboo/scheduling-queue-refresh:11783-12557
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</span><span class="cx">/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
</span><span class="cx">/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
</span><span class="cx">/CalendarServer/branches/users/cdaboo/timezones:7443-7699
</span><span class="cx">/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
</span><span class="cx">/CalendarServer/branches/users/gaya/cleanrevisions:12152-12334
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroupfixes:12120-12142
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who:12819-12860
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-2:12861-12898
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-3:12899-12913
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-4:12914-13157
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><a id="CalendarServerbranchesuserssagenmove2who5calendarserveraccesslogpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/accesslog.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/accesslog.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/accesslog.py        2014-04-04 17:20:27 UTC (rev 13158)
</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,22 +90,27 @@
</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><del>-                    uidn = convertUIDtoShortName(uidn)
-                    if uidz:
-                        uidz = convertUIDtoShortName(uidz)
</del><ins>+                    # MOVE2WHO
+                    # Better to stick the records directly on the request at
+                    # an earlier point, since we can't do anything deferred
+                    # in here.
</ins><span class="cx"> 
</span><ins>+                    # uidn = convertUIDtoShortName(uidn)
+                    # if uidz:
+                    #     uidz = convertUIDtoShortName(uidz)
+
</ins><span class="cx">                     if uidn and uidz:
</span><span class="cx">                         uid = '&quot;%s as %s&quot;' % (uidn, uidz,)
</span><span class="cx">                     else:
</span><span class="lines">@@ -151,8 +155,9 @@
</span><span class="cx">                 format += ' i=%(serverInstance)s'
</span><span class="cx">                 formatArgs[&quot;serverInstance&quot;] = config.LogID if config.LogID else &quot;0&quot;
</span><span class="cx"> 
</span><del>-                format += ' or=%(outstandingRequests)s'
-                formatArgs[&quot;outstandingRequests&quot;] = request.chanRequest.channel.factory.outstandingRequests
</del><ins>+                if request.chanRequest:  # This can be None during tests
+                    format += ' or=%(outstandingRequests)s'
+                    formatArgs[&quot;outstandingRequests&quot;] = request.chanRequest.channel.factory.outstandingRequests
</ins><span class="cx"> 
</span><span class="cx">                 # Tags for time stamps collected along the way - the first one in the list is the initial
</span><span class="cx">                 # time for request creation - we use that to track the entire request/response time
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverplatformdarwinwikipy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/wiki.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/wiki.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/platform/darwin/wiki.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,102 +0,0 @@
</span><del>-##
-# Copyright (c) 2012-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.log import Logger
-from twext.internet.gaiendpoint import GAIEndpoint
-from twext.internet.adaptendpoint import connect
-
-from twisted.web.client import HTTPPageGetter, HTTPClientFactory
-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-import json
-
-log = Logger()
-
-@inlineCallbacks
-def guidForAuthToken(token, host=&quot;localhost&quot;, port=80):
-    &quot;&quot;&quot;
-    Send a GET request to the web auth service to retrieve the user record
-    guid associated with the provided auth token.
-
-    @param token: An auth token, usually passed in via cookie when webcal
-        makes a request.
-    @type token: C{str}
-    @return: deferred returning a guid (C{str}) if successful, or
-        will raise WebAuthError otherwise.
-    &quot;&quot;&quot;
-    url = &quot;http://%s:%d/auth/verify?auth_token=%s&quot; % (host, port, token,)
-    jsonResponse = (yield _getPage(url, host, port))
-    try:
-        response = json.loads(jsonResponse)
-    except Exception, e:
-        log.error(&quot;Error parsing JSON response from webauth: %s (%s)&quot; %
-            (jsonResponse, str(e)))
-        raise WebAuthError(&quot;Could not look up token: %s&quot; % (token,))
-    if response[&quot;succeeded&quot;]:
-        returnValue(response[&quot;generated_uid&quot;])
-    else:
-        raise WebAuthError(&quot;Could not look up token: %s&quot; % (token,))
-
-
-
-def accessForUserToWiki(user, wiki, host=&quot;localhost&quot;, port=4444):
-    &quot;&quot;&quot;
-    Send a GET request to the wiki collabd service to retrieve the access level
-    the given user (in GUID form) has to the given wiki (in wiki short-name
-    form).
-
-    @param user: The GUID of the user
-    @type user: C{str}
-    @param wiki: The short name of the wiki
-    @type wiki: C{str}
-    @return: deferred returning a access level (C{str}) if successful, or
-        if the user is not recognized a twisted.web.error.Error with
-        status FORBIDDEN will errBack; an unknown wiki will have a status
-        of NOT_FOUND
-    &quot;&quot;&quot;
-    url = &quot;http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s&quot; % (host, port,
-        user, wiki)
-    return _getPage(url, host, port)
-
-
-
-def _getPage(url, host, port):
-    &quot;&quot;&quot;
-    Fetch the body of the given url via HTTP, connecting to the given host
-    and port.
-
-    @param url: The URL to GET
-    @type url: C{str}
-    @param host: The hostname to connect to
-    @type host: C{str}
-    @param port: The port number to connect to
-    @type port: C{int}
-    @return: A deferred; upon 200 success the body of the response is returned,
-        otherwise a twisted.web.error.Error is the result.
-    &quot;&quot;&quot;
-    factory = HTTPClientFactory(url)
-    factory.protocol = HTTPPageGetter
-    connect(GAIEndpoint(reactor, host, port), factory)
-    return factory.deferred
-
-
-
-class WebAuthError(RuntimeError):
-    &quot;&quot;&quot;
-    Error in web auth
-    &quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverprovisionrootpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/root.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/root.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/root.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,33 +20,34 @@
</span><span class="cx"> ]
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><del>-from txweb2 import responsecode
-from txweb2.auth.wrapper import UnauthorizedResponse
-from txdav.xml import element as davxml
-from txweb2.dav.xattrprops import xattrPropertyStore
-from txweb2.http import HTTPError, StatusResponse, RedirectResponse
-
</del><span class="cx"> from twisted.cred.error import LoginFailed, UnauthorizedLogin
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> from twisted.python.reflect import namedClass
</span><del>-from twisted.web.xmlrpc import Proxy
</del><span class="cx"> from twisted.web.error import Error as WebError
</span><del>-
</del><ins>+from twistedcaldav.cache import DisabledCache
+from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
</ins><span class="cx"> from twistedcaldav.cache import _CachedResponseResource
</span><del>-from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
-from twistedcaldav.cache import DisabledCache
</del><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twistedcaldav.directory.principal import DirectoryPrincipalResource
</ins><span class="cx"> from twistedcaldav.extensions import DAVFile, CachingPropertyStore
</span><span class="cx"> from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
</span><span class="cx"> from twistedcaldav.extensions import ReadOnlyResourceMixIn
</span><span class="cx"> from twistedcaldav.resource import CalDAVComplianceMixIn
</span><del>-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-from calendarserver.platform.darwin.wiki import guidForAuthToken
</del><ins>+from txdav.who.wiki import DirectoryService as WikiDirectoryService
+from txdav.who.wiki import uidForAuthToken
+from txdav.xml import element as davxml
+from txweb2 import responsecode
+from txweb2.auth.wrapper import UnauthorizedResponse
+from txweb2.dav.xattrprops import xattrPropertyStore
+from txweb2.http import HTTPError, StatusResponse, RedirectResponse
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class RootResource (ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn, CalDAVComplianceMixIn, DAVFile):
</del><ins>+class RootResource(
+    ReadOnlyResourceMixIn, DirectoryPrincipalPropertySearchMixIn,
+    CalDAVComplianceMixIn, DAVFile
+):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A special root resource that contains support checking SACLs
</span><span class="cx">     as well as adding responseFilters.
</span><span class="lines">@@ -58,17 +59,17 @@
</span><span class="cx">     # starts with any of these, then the list of SACLs are checked.  If the
</span><span class="cx">     # request path does not start with any of these, then no SACLs are checked.
</span><span class="cx">     saclMap = {
</span><del>-        &quot;addressbooks&quot; : (&quot;addressbook&quot;,),
-        &quot;calendars&quot; : (&quot;calendar&quot;,),
-        &quot;directory&quot; : (&quot;addressbook&quot;,),
-        &quot;principals&quot; : (&quot;addressbook&quot;, &quot;calendar&quot;),
-        &quot;webcal&quot; : (&quot;calendar&quot;,),
</del><ins>+        &quot;addressbooks&quot;: (&quot;addressbook&quot;,),
+        &quot;calendars&quot;: (&quot;calendar&quot;,),
+        &quot;directory&quot;: (&quot;addressbook&quot;,),
+        &quot;principals&quot;: (&quot;addressbook&quot;, &quot;calendar&quot;),
+        &quot;webcal&quot;: (&quot;calendar&quot;,),
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     # If a top-level resource path starts with any of these, an unauthenticated
</span><span class="cx">     # request is redirected to the auth url (config.WebCalendarAuthPath)
</span><span class="cx">     authServiceMap = {
</span><del>-        &quot;webcal&quot; : True,
</del><ins>+        &quot;webcal&quot;: True,
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, path, *args, **kwargs):
</span><span class="lines">@@ -82,11 +83,17 @@
</span><span class="cx"> 
</span><span class="cx">         self.contentFilters = []
</span><span class="cx"> 
</span><del>-        if config.EnableResponseCache and config.Memcached.Pools.Default.ClientEnabled:
</del><ins>+        if (
+            config.EnableResponseCache and
+            config.Memcached.Pools.Default.ClientEnabled
+        ):
</ins><span class="cx">             self.responseCache = MemcacheResponseCache(self.fp)
</span><span class="cx"> 
</span><del>-            # These class attributes need to be setup with our memcache notifier
-            DirectoryPrincipalResource.cacheNotifierFactory = MemcacheChangeNotifier
</del><ins>+            # These class attributes need to be setup with our memcache\
+            # notifier
+            DirectoryPrincipalResource.cacheNotifierFactory = (
+                MemcacheChangeNotifier
+            )
</ins><span class="cx">         else:
</span><span class="cx">             self.responseCache = DisabledCache()
</span><span class="cx"> 
</span><span class="lines">@@ -98,7 +105,9 @@
</span><span class="cx">     def deadProperties(self):
</span><span class="cx">         if not hasattr(self, &quot;_dead_properties&quot;):
</span><span class="cx">             # Get the property store from super
</span><del>-            deadProperties = namedClass(config.RootResourcePropStoreClass)(self)
</del><ins>+            deadProperties = (
+                namedClass(config.RootResourcePropStoreClass)(self)
+            )
</ins><span class="cx"> 
</span><span class="cx">             # Wrap the property store in a memory store
</span><span class="cx">             if isinstance(deadProperties, xattrPropertyStore):
</span><span class="lines">@@ -110,7 +119,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return config.RootResourceACL
</del><ins>+        return succeed(config.RootResourceACL)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -160,7 +169,9 @@
</span><span class="cx">         request.checkingSACL = True
</span><span class="cx"> 
</span><span class="cx">         for collection in self.principalCollections():
</span><del>-            principal = collection._principalForURI(authzUser.children[0].children[0].data)
</del><ins>+            principal = yield collection._principalForURI(
+                authzUser.children[0].children[0].data
+            )
</ins><span class="cx">             if principal is None:
</span><span class="cx">                 response = (yield UnauthorizedResponse.makeResponse(
</span><span class="cx">                     request.credentialFactories,
</span><span class="lines">@@ -185,7 +196,10 @@
</span><span class="cx">         if access:
</span><span class="cx">             returnValue(True)
</span><span class="cx"> 
</span><del>-        log.warn(&quot;User %r is not enabled with the %r SACL(s)&quot; % (username, saclServices,))
</del><ins>+        log.warn(
+            &quot;User {user!r} is not enabled with the {sacl!r} SACL(s)&quot;,
+            user=username, sacl=saclServices
+        )
</ins><span class="cx">         raise HTTPError(responsecode.FORBIDDEN)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -229,54 +243,71 @@
</span><span class="cx">                     token = None
</span><span class="cx"> 
</span><span class="cx">                 if token is not None and token != &quot;unauthenticated&quot;:
</span><del>-                    log.debug(&quot;Wiki sessionID cookie value: %s&quot; % (token,))
</del><ins>+                    log.debug(
+                        &quot;Wiki sessionID cookie value: {token}&quot;, token=token
+                    )
</ins><span class="cx"> 
</span><span class="cx">                     record = None
</span><span class="cx">                     try:
</span><del>-                        if wikiConfig.LionCompatibility:
-                            guid = None
-                            proxy = Proxy(wikiConfig[&quot;URL&quot;])
-                            username = (yield proxy.callRemote(wikiConfig[&quot;UserMethod&quot;], token))
-                            directory = request.site.resource.getDirectory()
-                            record = directory.recordWithShortName(&quot;users&quot;, username)
-                            if record is not None:
-                                guid = record.guid
-                        else:
-                            guid = (yield guidForAuthToken(token))
-                            if guid == &quot;unauthenticated&quot;:
-                                guid = None
</del><ins>+                        uid = yield uidForAuthToken(token)
+                        if uid == &quot;unauthenticated&quot;:
+                            uid = None
</ins><span class="cx"> 
</span><del>-                    except WebError, w:
-                        guid = None
</del><ins>+                    except WebError as w:
+                        uid = None
</ins><span class="cx">                         # FORBIDDEN status means it's an unknown token
</span><span class="cx">                         if int(w.status) == responsecode.NOT_FOUND:
</span><del>-                            log.debug(&quot;Unknown wiki token: %s&quot; % (token,))
</del><ins>+                            log.debug(
+                                &quot;Unknown wiki token: {token}&quot;, token=token
+                            )
</ins><span class="cx">                         else:
</span><del>-                            log.error(&quot;Failed to look up wiki token %s: %s&quot; %
-                                (token, w.message,))
</del><ins>+                            log.error(
+                                &quot;Failed to look up wiki token {token}: &quot;
+                                &quot;{message}&quot;,
+                                token=token, message=w.message
+                            )
</ins><span class="cx"> 
</span><del>-                    except Exception, e:
-                        log.error(&quot;Failed to look up wiki token (%s)&quot; % (e,))
-                        guid = None
</del><ins>+                    except Exception as e:
+                        log.error(
+                            &quot;Failed to look up wiki token: {error}&quot;,
+                            error=e
+                        )
+                        uid = None
</ins><span class="cx"> 
</span><del>-                    if guid is not None:
-                        log.debug(&quot;Wiki lookup returned guid: %s&quot; % (guid,))
</del><ins>+                    if uid is not None:
+                        log.debug(
+                            &quot;Wiki lookup returned uid: {uid}&quot;, uid=uid
+                        )
</ins><span class="cx">                         principal = None
</span><span class="cx">                         directory = request.site.resource.getDirectory()
</span><del>-                        record = directory.recordWithGUID(guid)
</del><ins>+                        record = yield directory.recordWithUID(uid)
</ins><span class="cx">                         if record is not None:
</span><span class="cx">                             username = record.shortNames[0]
</span><del>-                            log.debug(&quot;Wiki user record for user %s : %s&quot; % (username, record))
</del><ins>+                            log.debug(
+                                &quot;Wiki user record for user {user}: {record}&quot;,
+                                user=username, record=record
+                            )
</ins><span class="cx">                             for collection in self.principalCollections():
</span><del>-                                principal = collection.principalForRecord(record)
</del><ins>+                                principal = (
+                                    yield collection.principalForRecord(record)
+                                )
</ins><span class="cx">                                 if principal is not None:
</span><span class="cx">                                     break
</span><span class="cx"> 
</span><span class="cx">                         if principal:
</span><del>-                            log.debug(&quot;Wiki-authenticated principal %s being assigned to authnUser and authzUser&quot; % (record.uid,))
-                            request.authzUser = request.authnUser = davxml.Principal(
-                                davxml.HRef.fromString(&quot;/principals/__uids__/%s/&quot; % (record.uid,))
</del><ins>+                            log.debug(
+                                &quot;Wiki-authenticated principal {record.uid} &quot;
+                                &quot;being assigned to authnUser and authzUser&quot;,
+                                record=record
</ins><span class="cx">                             )
</span><ins>+                            request.authzUser = request.authnUser = (
+                                davxml.Principal(
+                                    davxml.HRef.fromString(
+                                        &quot;/principals/__uids__/{}/&quot;
+                                        .format(record.uid)
+                                    )
+                                )
+                            )
</ins><span class="cx"> 
</span><span class="cx">         if not hasattr(request, &quot;authzUser&quot;) and config.WebCalendarAuthPath:
</span><span class="cx">             topLevel = request.path.strip(&quot;/&quot;).split(&quot;/&quot;)[0]
</span><span class="lines">@@ -286,25 +317,27 @@
</span><span class="cx"> 
</span><span class="cx">                 # Use config.ServerHostName if no x-forwarded-host header,
</span><span class="cx">                 # otherwise use the final hostname in x-forwarded-host.
</span><del>-                host = request.headers.getRawHeaders(&quot;x-forwarded-host&quot;,
-                    [config.ServerHostName])[-1].split(&quot;,&quot;)[-1].strip()
</del><ins>+                host = request.headers.getRawHeaders(
+                    &quot;x-forwarded-host&quot;,
+                    [config.ServerHostName]
+                )[-1].split(&quot;,&quot;)[-1].strip()
</ins><span class="cx">                 port = 443 if config.EnableSSL else 80
</span><span class="cx">                 scheme = &quot;https&quot; if config.EnableSSL else &quot;http&quot;
</span><span class="cx"> 
</span><span class="cx">                 response = RedirectResponse(
</span><del>-                        request.unparseURL(
-                            host=host,
-                            port=port,
-                            scheme=scheme,
-                            path=config.WebCalendarAuthPath,
-                            querystring=&quot;redirect=%s://%s%s&quot; % (
-                                scheme,
-                                host,
-                                request.path
-                            )
-                        ),
-                        temporary=True
-                    )
</del><ins>+                    request.unparseURL(
+                        host=host,
+                        port=port,
+                        scheme=scheme,
+                        path=config.WebCalendarAuthPath,
+                        querystring=&quot;redirect={}://{}{}&quot;.format(
+                            scheme,
+                            host,
+                            request.path
+                        )
+                    ),
+                    temporary=True
+                )
</ins><span class="cx">                 raise HTTPError(response)
</span><span class="cx"> 
</span><span class="cx">         # We don't want the /inbox resource to pay attention to SACLs because
</span><span class="lines">@@ -314,10 +347,17 @@
</span><span class="cx">         if segments[0] in (&quot;inbox&quot;, &quot;timezones&quot;):
</span><span class="cx">             request.checkedSACL = True
</span><span class="cx"> 
</span><del>-        elif (len(segments) &gt; 2 and segments[0] in (&quot;calendars&quot;, &quot;principals&quot;) and
</del><ins>+        elif (
</ins><span class="cx">             (
</span><del>-                segments[1] == &quot;wikis&quot; or
-                (segments[1] == &quot;__uids__&quot; and segments[2].startswith(&quot;wiki-&quot;))
</del><ins>+                len(segments) &gt; 2 and
+                segments[0] in (&quot;calendars&quot;, &quot;principals&quot;) and
+                (
+                    segments[1] == &quot;wikis&quot; or
+                    (
+                        segments[1] == &quot;__uids__&quot; and
+                        segments[2].startswith(WikiDirectoryService.uidPrefix)
+                    )
+                )
</ins><span class="cx">             )
</span><span class="cx">         ):
</span><span class="cx">             # This is a wiki-related calendar resource. SACLs are not checked.
</span><span class="lines">@@ -332,12 +372,21 @@
</span><span class="cx">                 else:
</span><span class="cx">                     wikiName = segments[2][5:]
</span><span class="cx">                 if wikiName:
</span><del>-                    log.debug(&quot;Wiki principal %s being assigned to authzUser&quot; % (wikiName,))
</del><ins>+                    log.debug(
+                        &quot;Wiki principal {name} being assigned to authzUser&quot;,
+                        name=wikiName
+                    )
</ins><span class="cx">                     request.authzUser = davxml.Principal(
</span><del>-                        davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiName,))
</del><ins>+                        davxml.HRef.fromString(
+                            &quot;/principals/wikis/{}/&quot;.format(wikiName)
+                        )
</ins><span class="cx">                     )
</span><span class="cx"> 
</span><del>-        elif self.useSacls and not hasattr(request, &quot;checkedSACL&quot;) and not hasattr(request, &quot;checkingSACL&quot;):
</del><ins>+        elif (
+            self.useSacls and
+            not hasattr(request, &quot;checkedSACL&quot;) and
+            not hasattr(request, &quot;checkingSACL&quot;)
+        ):
</ins><span class="cx">             yield self.checkSacl(request)
</span><span class="cx"> 
</span><span class="cx">         if config.RejectClients:
</span><span class="lines">@@ -348,28 +397,37 @@
</span><span class="cx">             if agent is not None:
</span><span class="cx">                 for reject in config.RejectClients:
</span><span class="cx">                     if reject.search(agent) is not None:
</span><del>-                        log.info(&quot;Rejecting user-agent: %s&quot; % (agent,))
</del><ins>+                        log.info(&quot;Rejecting user-agent: {agent}&quot;, agent=agent)
</ins><span class="cx">                         raise HTTPError(StatusResponse(
</span><span class="cx">                             responsecode.FORBIDDEN,
</span><del>-                            &quot;Your client software (%s) is not allowed to access this service.&quot; % (agent,)
</del><ins>+                            &quot;Your client software ({}) is not allowed to &quot;
+                            &quot;access this service.&quot;
+                            .format(agent)
</ins><span class="cx">                         ))
</span><span class="cx"> 
</span><del>-        if config.EnableResponseCache and request.method == &quot;PROPFIND&quot; and not getattr(request, &quot;notInCache&quot;, False) and len(segments) &gt; 1:
</del><ins>+        if (
+            config.EnableResponseCache and
+            request.method == &quot;PROPFIND&quot; and
+            not getattr(request, &quot;notInCache&quot;, False) and
+            len(segments) &gt; 1
+        ):
</ins><span class="cx">             try:
</span><del>-                authnUser, authzUser = (yield self.authenticate(request))
</del><ins>+                authnUser, authzUser = yield self.authenticate(request)
</ins><span class="cx">                 request.authnUser = authnUser
</span><span class="cx">                 request.authzUser = authzUser
</span><span class="cx">             except (UnauthorizedLogin, LoginFailed):
</span><del>-                response = (yield UnauthorizedResponse.makeResponse(
</del><ins>+                response = yield UnauthorizedResponse.makeResponse(
</ins><span class="cx">                     request.credentialFactories,
</span><span class="cx">                     request.remoteAddr
</span><del>-                ))
</del><ins>+                )
</ins><span class="cx">                 raise HTTPError(response)
</span><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 if not getattr(request, &quot;checkingCache&quot;, False):
</span><span class="cx">                     request.checkingCache = True
</span><del>-                    response = (yield self.responseCache.getResponseForRequest(request))
</del><ins>+                    response = yield self.responseCache.getResponseForRequest(
+                        request
+                    )
</ins><span class="cx">                     if response is None:
</span><span class="cx">                         request.notInCache = True
</span><span class="cx">                         raise KeyError(&quot;Not found in cache.&quot;)
</span><span class="lines">@@ -378,7 +436,9 @@
</span><span class="cx">             except KeyError:
</span><span class="cx">                 pass
</span><span class="cx"> 
</span><del>-        child = (yield super(RootResource, self).locateChild(request, segments))
</del><ins>+        child = yield super(RootResource, self).locateChild(
+            request, segments
+        )
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverprovisiontesttest_rootpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/test/test_root.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/test/test_root.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/provision/test/test_root.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -14,29 +14,22 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-import os
</del><span class="cx"> 
</span><del>-from twisted.cred.portal import Portal
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks, maybeDeferred, returnValue
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> from txweb2 import http_headers
</span><span class="cx"> from txweb2 import responsecode
</span><del>-from txweb2 import server
-from txweb2.auth import basic
-from txweb2.dav import auth
</del><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txweb2.http import HTTPError
</span><span class="cx"> from txweb2.iweb import IResponse
</span><del>-from txweb2.test.test_server import SimpleRequest
</del><span class="cx"> 
</span><del>-from twistedcaldav.test.util import TestCase
</del><ins>+from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</ins><span class="cx"> from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
</span><del>-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
</del><span class="cx"> 
</span><span class="cx"> from calendarserver.provision.root import RootResource
</span><del>-from twistedcaldav.directory import augment
</del><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class FakeCheckSACL(object):
</span><span class="cx">     def __init__(self, sacls=None):
</span><span class="cx">         self.sacls = sacls or {}
</span><span class="lines">@@ -53,48 +46,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class RootTests(TestCase):
</del><ins>+class RootTests(StoreTestCase):
</ins><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def setUp(self):
</span><del>-        super(RootTests, self).setUp()
</del><ins>+        yield super(RootTests, self).setUp()
</ins><span class="cx"> 
</span><del>-        self.docroot = self.mktemp()
-        os.mkdir(self.docroot)
-
</del><span class="cx">         RootResource.CheckSACL = FakeCheckSACL(sacls={&quot;calendar&quot;: [&quot;dreid&quot;]})
</span><span class="cx"> 
</span><del>-        directory = XMLDirectoryService(
-            {
-                &quot;xmlFile&quot; : xmlFile,
-                &quot;augmentService&quot; :
-                    augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
-            }
-        )
</del><span class="cx"> 
</span><del>-        principals = DirectoryPrincipalProvisioningResource(
-            &quot;/principals/&quot;,
-            directory
-        )
</del><span class="cx"> 
</span><del>-        root = RootResource(self.docroot, principalCollections=[principals])
-
-        root.putChild(&quot;principals&quot;,
-                      principals)
-
-        portal = Portal(auth.DavRealm())
-        portal.registerChecker(directory)
-
-        self.root = auth.AuthenticationWrapper(
-            root,
-            portal,
-            (basic.BasicCredentialFactory(&quot;Test realm&quot;),),
-            (basic.BasicCredentialFactory(&quot;Test realm&quot;),),
-            loginInterfaces=(auth.IPrincipal,))
-
-        self.site = server.Site(self.root)
-
-
-
</del><span class="cx"> class ComplianceTests(RootTests):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests to verify CalDAV compliance of the root resource.
</span><span class="lines">@@ -107,8 +68,8 @@
</span><span class="cx">         Deferred which will fire with (something adaptable to) an HTTP response
</span><span class="cx">         object.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        request = SimpleRequest(self.site, method, (&quot;/&quot;.join([&quot;&quot;] + segments)))
-        rsrc = self.root
</del><ins>+        request = SimpleStoreRequest(self, method, (&quot;/&quot;.join([&quot;&quot;] + segments)))
+        rsrc = self.actualRoot
</ins><span class="cx">         while segments:
</span><span class="cx">             rsrc, segments = (yield maybeDeferred(
</span><span class="cx">                 rsrc.locateChild, request, segments
</span><span class="lines">@@ -138,18 +99,12 @@
</span><span class="cx"> 
</span><span class="cx">         should return a valid resource
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.root.resource.useSacls = False
</del><ins>+        self.actualRoot.useSacls = False
</ins><span class="cx"> 
</span><del>-        request = SimpleRequest(self.site,
-                                &quot;GET&quot;,
-                                &quot;/principals/&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/principals/&quot;)
</ins><span class="cx"> 
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            self.root.locateChild, request, [&quot;principals&quot;]
-        ))
-
</del><span class="cx">         resrc, segments = (yield maybeDeferred(
</span><del>-            resrc.locateChild, request, [&quot;principals&quot;]
</del><ins>+            self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx">         self.failUnless(
</span><span class="lines">@@ -169,26 +124,21 @@
</span><span class="cx"> 
</span><span class="cx">         should return a valid resource
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.root.resource.useSacls = True
</del><ins>+        self.actualRoot.useSacls = True
</ins><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        record = yield self.directory.recordWithShortName(
+            RecordType.user,
+            u&quot;dreid&quot;
+        )
+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;GET&quot;,
</span><span class="cx">             &quot;/principals/&quot;,
</span><del>-            headers=http_headers.Headers({
-                &quot;Authorization&quot;: [
-                    &quot;basic&quot;,
-                    &quot;%s&quot; % (&quot;dreid:dierd&quot;.encode(&quot;base64&quot;),)
-                ]
-            })
</del><ins>+            authRecord=record
</ins><span class="cx">         )
</span><span class="cx"> 
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            self.root.locateChild, request, [&quot;principals&quot;]
-        ))
-
</del><span class="cx">         resrc, segments = (yield maybeDeferred(
</span><del>-            resrc.locateChild, request, [&quot;principals&quot;]
</del><ins>+            self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx">         self.failUnless(
</span><span class="lines">@@ -218,28 +168,27 @@
</span><span class="cx"> 
</span><span class="cx">         should return a 403 forbidden response
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.root.resource.useSacls = True
</del><ins>+        self.actualRoot.useSacls = True
</ins><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        record = yield self.directory.recordWithShortName(
+            RecordType.user,
+            u&quot;wsanchez&quot;
+        )
+
+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;GET&quot;,
</span><span class="cx">             &quot;/principals/&quot;,
</span><del>-            headers=http_headers.Headers({
-                &quot;Authorization&quot;: [
-                    &quot;basic&quot;,
-                    &quot;%s&quot; % (&quot;wsanchez:zehcnasw&quot;.encode(&quot;base64&quot;),)
-                ]
-            })
</del><ins>+            authRecord=record
</ins><span class="cx">         )
</span><span class="cx"> 
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            self.root.locateChild, request, [&quot;principals&quot;]
-        ))
-
</del><span class="cx">         try:
</span><span class="cx">             resrc, _ignore_segments = (yield maybeDeferred(
</span><del>-                resrc.locateChild, request, [&quot;principals&quot;]
</del><ins>+                self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">             ))
</span><ins>+            raise AssertionError(
+                &quot;RootResource.locateChild did not return an error&quot;
+            )
</ins><span class="cx">         except HTTPError, e:
</span><span class="cx">             self.assertEquals(e.response.code, 403)
</span><span class="cx"> 
</span><span class="lines">@@ -253,20 +202,16 @@
</span><span class="cx">         should return a 401 UnauthorizedResponse
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.root.resource.useSacls = True
-        request = SimpleRequest(
-            self.site,
</del><ins>+        self.actualRoot.useSacls = True
+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;GET&quot;,
</span><span class="cx">             &quot;/principals/&quot;
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            self.root.locateChild, request, [&quot;principals&quot;]
-        ))
-
</del><span class="cx">         try:
</span><span class="cx">             resrc, _ignore_segments = (yield maybeDeferred(
</span><del>-                resrc.locateChild, request, [&quot;principals&quot;]
</del><ins>+                self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">             ))
</span><span class="cx">             raise AssertionError(
</span><span class="cx">                 &quot;RootResource.locateChild did not return an error&quot;
</span><span class="lines">@@ -283,24 +228,28 @@
</span><span class="cx"> 
</span><span class="cx">         should return a 401 UnauthorizedResponse
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.root.resource.useSacls = True
</del><ins>+        self.actualRoot.useSacls = True
</ins><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;GET&quot;,
</span><span class="cx">             &quot;/principals/&quot;,
</span><del>-            headers=http_headers.Headers({
-                    &quot;Authorization&quot;: [&quot;basic&quot;, &quot;%s&quot; % (
-                            &quot;dreid:dreid&quot;.encode(&quot;base64&quot;),)]}))
</del><ins>+            headers=http_headers.Headers(
+                {
+                    &quot;Authorization&quot;: [
+                        &quot;basic&quot;, &quot;%s&quot; % (&quot;dreid:dreid&quot;.encode(&quot;base64&quot;),)
+                    ]
+                }
+            )
+        )
</ins><span class="cx"> 
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            self.root.locateChild, request, [&quot;principals&quot;]
-        ))
-
</del><span class="cx">         try:
</span><span class="cx">             resrc, _ignore_segments = (yield maybeDeferred(
</span><del>-                resrc.locateChild, request, [&quot;principals&quot;]
</del><ins>+                self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">             ))
</span><ins>+            raise AssertionError(
+                &quot;RootResource.locateChild did not return an error&quot;
+            )
</ins><span class="cx">         except HTTPError, e:
</span><span class="cx">             self.assertEquals(e.response.code, 401)
</span><span class="cx"> 
</span><span class="lines">@@ -313,7 +262,7 @@
</span><span class="cx">                 self.fail(&quot;Incorrect response for DELETE /: %s&quot;
</span><span class="cx">                           % (response.code,))
</span><span class="cx"> 
</span><del>-        request = SimpleRequest(self.site, &quot;DELETE&quot;, &quot;/&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;DELETE&quot;, &quot;/&quot;)
</ins><span class="cx">         return self.send(request, do_test)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -325,8 +274,8 @@
</span><span class="cx">                 self.fail(&quot;Incorrect response for COPY /: %s&quot;
</span><span class="cx">                           % (response.code,))
</span><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;COPY&quot;,
</span><span class="cx">             &quot;/&quot;,
</span><span class="cx">             headers=http_headers.Headers({&quot;Destination&quot;: &quot;/copy/&quot;})
</span><span class="lines">@@ -342,8 +291,8 @@
</span><span class="cx">                 self.fail(&quot;Incorrect response for MOVE /: %s&quot;
</span><span class="cx">                           % (response.code,))
</span><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;MOVE&quot;,
</span><span class="cx">             &quot;/&quot;,
</span><span class="cx">             headers=http_headers.Headers({&quot;Destination&quot;: &quot;/copy/&quot;})
</span><span class="lines">@@ -371,13 +320,15 @@
</span><span class="cx">             return response
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def setUp(self):
</span><del>-        super(SACLCacheTests, self).setUp()
-        self.root.resource.responseCache = SACLCacheTests.StubResponseCacheResource()
</del><ins>+        yield super(SACLCacheTests, self).setUp()
+        self.actualRoot.responseCache = SACLCacheTests.StubResponseCacheResource()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_PROPFIND(self):
</span><del>-        self.root.resource.useSacls = True
</del><ins>+        self.actualRoot.useSacls = True
</ins><span class="cx"> 
</span><span class="cx">         body = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
</span><span class="cx"> &lt;D:propfind xmlns:D=&quot;DAV:&quot;&gt;
</span><span class="lines">@@ -387,48 +338,46 @@
</span><span class="cx"> &lt;/D:prop&gt;
</span><span class="cx"> &lt;/D:propfind&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><ins>+        record = yield self.directory.recordWithShortName(
+            RecordType.user,
+            u&quot;dreid&quot;
+        )
</ins><span class="cx"> 
</span><del>-        request = SimpleRequest(
-            self.site,
</del><ins>+        request = SimpleStoreRequest(
+            self,
</ins><span class="cx">             &quot;PROPFIND&quot;,
</span><span class="cx">             &quot;/principals/users/dreid/&quot;,
</span><span class="cx">             headers=http_headers.Headers({
</span><del>-                    'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
-                    'Content-Type': 'application/xml; charset=&quot;utf-8&quot;',
</del><span class="cx">                     'Depth': '1',
</span><span class="cx">             }),
</span><ins>+            authRecord=record,
</ins><span class="cx">             content=body
</span><span class="cx">         )
</span><ins>+        response = yield self.send(request)
+        response = IResponse(response)
</ins><span class="cx"> 
</span><del>-        def gotResponse1(response):
-            if response.code != responsecode.MULTI_STATUS:
-                self.fail(&quot;Incorrect response for PROPFIND /principals/: %s&quot; % (response.code,))
</del><ins>+        if response.code != responsecode.MULTI_STATUS:
+            self.fail(&quot;Incorrect response for PROPFIND /principals/: %s&quot; % (response.code,))
</ins><span class="cx"> 
</span><del>-            request = SimpleRequest(
-                self.site,
-                &quot;PROPFIND&quot;,
-                &quot;/principals/users/dreid/&quot;,
-                headers=http_headers.Headers({
-                        'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
-                        'Content-Type': 'application/xml; charset=&quot;utf-8&quot;',
-                        'Depth': '1',
-                }),
-                content=body
-            )
</del><ins>+        request = SimpleStoreRequest(
+            self,
+            &quot;PROPFIND&quot;,
+            &quot;/principals/users/dreid/&quot;,
+            headers=http_headers.Headers({
+                    'Depth': '1',
+            }),
+            authRecord=record,
+            content=body
+        )
+        response = yield self.send(request)
+        response = IResponse(response)
</ins><span class="cx"> 
</span><del>-            d = self.send(request, gotResponse2)
-            return d
</del><ins>+        if response.code != responsecode.MULTI_STATUS:
+            self.fail(&quot;Incorrect response for PROPFIND /principals/: %s&quot; % (response.code,))
+        self.assertEqual(self.actualRoot.responseCache.cacheHitCount, 1)
</ins><span class="cx"> 
</span><del>-        def gotResponse2(response):
-            if response.code != responsecode.MULTI_STATUS:
-                self.fail(&quot;Incorrect response for PROPFIND /principals/: %s&quot; % (response.code,))
-            self.assertEqual(self.root.resource.responseCache.cacheHitCount, 1)
</del><span class="cx"> 
</span><del>-        d = self.send(request, gotResponse1)
-        return d
</del><span class="cx"> 
</span><del>-
-
</del><span class="cx"> class WikiTests(RootTests):
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -438,12 +387,9 @@
</span><span class="cx">         request.checkedWiki will be set to True
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        request = SimpleRequest(self.site, &quot;GET&quot;, &quot;/principals/&quot;)
</del><ins>+        request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/principals/&quot;)
</ins><span class="cx"> 
</span><span class="cx">         resrc, _ignore_segments = (yield maybeDeferred(
</span><del>-            self.root.locateChild, request, [&quot;principals&quot;]
</del><ins>+            self.actualRoot.locateChild, request, [&quot;principals&quot;]
</ins><span class="cx">         ))
</span><del>-        resrc, _ignore_segments = (yield maybeDeferred(
-            resrc.locateChild, request, [&quot;principals&quot;]
-        ))
</del><span class="cx">         self.assertTrue(request.checkedWiki)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverpushapplepushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/push/applepush.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/push/applepush.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/push/applepush.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -820,23 +820,25 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return davxml.ACL(
-            # DAV:Read for authenticated principals
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # DAV:Read for authenticated principals
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
</ins><span class="cx">                 ),
</span><del>-                davxml.Protected(),
-            ),
-            # DAV:Write for authenticated principals
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Write()),
</del><ins>+                # DAV:Write for authenticated principals
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Write()),
+                    ),
+                    davxml.Protected(),
</ins><span class="cx">                 ),
</span><del>-                davxml.Protected(),
-            ),
</del><ins>+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -869,6 +871,7 @@
</span><span class="cx"> 
</span><span class="cx">     http_GET = http_POST
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalFromRequest(self, request):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Given an authenticated request, return the principal based on
</span><span class="lines">@@ -877,9 +880,9 @@
</span><span class="cx">         principal = None
</span><span class="cx">         for collection in self.principalCollections():
</span><span class="cx">             data = request.authnUser.children[0].children[0].data
</span><del>-            principal = collection._principalForURI(data)
</del><ins>+            principal = yield collection._principalForURI(data)
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
</del><ins>+                returnValue(principal)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -910,7 +913,7 @@
</span><span class="cx">             msg = &quot;Invalid request: bad 'token' %s&quot; % (token,)
</span><span class="cx"> 
</span><span class="cx">         else:
</span><del>-            principal = self.principalFromRequest(request)
</del><ins>+            principal = yield self.principalFromRequest(request)
</ins><span class="cx">             uid = principal.record.uid
</span><span class="cx">             try:
</span><span class="cx">                 yield self.addSubscription(token, key, uid, userAgent, host)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertapcaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/caldav.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/caldav.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/caldav.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -90,17 +90,17 @@
</span><span class="cx"> from txdav.common.datastore.work.revision_cleanup import (
</span><span class="cx">     scheduleFirstFindMinRevision
</span><span class="cx"> )
</span><del>-from txdav.dps.server import DirectoryProxyServiceMaker
</del><ins>+from txdav.who.util import directoryFromConfig
</ins><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 twistedcaldav.localization import processLocalizationFiles
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
</span><del>-from twistedcaldav.upgrade import UpgradeFileSystemFormatStep, PostDBImportStep
</del><ins>+from twistedcaldav.upgrade import (
+    UpgradeFileSystemFormatStep, PostDBImportStep,
+)
</ins><span class="cx"> 
</span><span class="cx"> try:
</span><span class="cx">     from twistedcaldav.authkerb import NegotiateCredentialFactory
</span><span class="lines">@@ -124,6 +124,8 @@
</span><span class="cx">     pgServiceFromConfig, getDBPool, MemoryLimitService,
</span><span class="cx">     storeFromConfig
</span><span class="cx"> )
</span><ins>+from twisted.application.strports import service as strPortsService
+from txdav.dps.server import DirectoryProxyAMPFactory
</ins><span class="cx"> 
</span><span class="cx"> try:
</span><span class="cx">     from calendarserver.version import version
</span><span class="lines">@@ -536,10 +538,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.Enabled:
</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">@@ -863,10 +862,10 @@
</span><span class="cx">         CalDAV and CardDAV requests.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         pool, txnFactory = getDBPool(config)
</span><del>-        store = storeFromConfig(config, txnFactory)
</del><ins>+        directory = DirectoryProxyClientService(config.DirectoryRealmName)
+        store = storeFromConfig(config, txnFactory, directory)
</ins><span class="cx">         logObserver = AMPCommonAccessLoggingObserver()
</span><span class="cx">         result = self.requestProcessingService(options, store, logObserver)
</span><del>-        directory = store.directoryService()
</del><span class="cx"> 
</span><span class="cx">         if pool is not None:
</span><span class="cx">             pool.setServiceParent(result)
</span><span class="lines">@@ -938,14 +937,9 @@
</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><span class="cx">         else:
</span><span class="cx">             groupCacher = None
</span><span class="lines">@@ -1281,21 +1275,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(
-                    DirectoryProxyClientService(None)
-                )
</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">@@ -1326,17 +1311,11 @@
</span><span class="cx">                         &quot;manhole_tap could not be imported&quot;
</span><span class="cx">                     )
</span><span class="cx"> 
</span><del>-            # Optionally enable Directory Proxy
-            if config.DirectoryProxy.Enabled:
-                dps = DirectoryProxyServiceMaker().makeService(None)
-                dps.setServiceParent(result)
-
</del><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">@@ -1376,7 +1355,7 @@
</span><span class="cx">                 Popen(memcachedArgv)
</span><span class="cx"> 
</span><span class="cx">         return self.storageService(
</span><del>-            slaveSvcCreator, logObserver, uid=uid, gid=gid
</del><ins>+            slaveSvcCreator, logObserver, uid=uid, gid=gid, directory=None
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1392,7 +1371,8 @@
</span><span class="cx">             return config.UtilityServiceClass(store)
</span><span class="cx"> 
</span><span class="cx">         uid, gid = getSystemIDs(config.UserName, config.GroupName)
</span><del>-        return self.storageService(toolServiceCreator, None, uid=uid, gid=gid)
</del><ins>+        return self.storageService(toolServiceCreator, None, uid=uid, gid=gid,
+                                   directory=None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def makeService_Agent(self, options):
</span><span class="lines">@@ -1440,7 +1420,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def storageService(
</span><del>-        self, createMainService, logObserver, uid=None, gid=None
</del><ins>+        self, createMainService, logObserver, uid=None, gid=None, directory=None
</ins><span class="cx">     ):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         If necessary, create a service to be started used for storage; for
</span><span class="lines">@@ -1466,9 +1446,13 @@
</span><span class="cx">             running as root (also the gid to chown Attachments to).
</span><span class="cx">         @type gid: C{int}
</span><span class="cx"> 
</span><ins>+        @param directory: The directory service to use.
+        @type directory: L{IStoreDirectoryService} or None
+
</ins><span class="cx">         @return: the appropriate a service to start.
</span><span class="cx">         @rtype: L{IService}
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">         def createSubServiceFactory(
</span><span class="cx">             dialect=POSTGRES_DIALECT, paramstyle='pyformat'
</span><span class="cx">         ):
</span><span class="lines">@@ -1480,7 +1464,14 @@
</span><span class="cx">                     maxConnections=config.MaxDBConnectionsPerPool
</span><span class="cx">                 )
</span><span class="cx">                 cp.setServiceParent(ms)
</span><del>-                store = storeFromConfig(config, cp.connection)
</del><ins>+                store = storeFromConfig(config, cp.connection, directory)
+                if directory is None:
+                    # Create a Directory Proxy &quot;Server&quot; service and hand it to
+                    # the store.
+                    # FIXME: right now the store passed *to* the directory is the
+                    # calendar/contacts data store, but for a multi-server deployment
+                    # it will need its own separate store.
+                    store.setDirectoryService(directoryFromConfig(config, store=store))
</ins><span class="cx"> 
</span><span class="cx">                 pps = PreProcessingService(
</span><span class="cx">                     createMainService, cp, store, logObserver, storageService
</span><span class="lines">@@ -1497,7 +1488,7 @@
</span><span class="cx"> 
</span><span class="cx">                 # Still need this for Snow Leopard support
</span><span class="cx">                 pps.addStep(
</span><del>-                    UpgradeFileSystemFormatStep(config)
</del><ins>+                    UpgradeFileSystemFormatStep(config, store)
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">                 pps.addStep(
</span><span class="lines">@@ -1605,7 +1596,7 @@
</span><span class="cx">                     &quot;Unknown database type {}&quot;.format(config.DBType)
</span><span class="cx">                 )
</span><span class="cx">         else:
</span><del>-            store = storeFromConfig(config, None)
</del><ins>+            store = storeFromConfig(config, None, directory)
</ins><span class="cx">             return createMainService(None, store, logObserver, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1867,14 +1858,9 @@
</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><span class="cx">             else:
</span><span class="cx">                 groupCacher = None
</span><span class="lines">@@ -1887,9 +1873,30 @@
</span><span class="cx"> 
</span><span class="cx">             store.callWithNewTransactions(decorateTransaction)
</span><span class="cx"> 
</span><ins>+            # Set up AMP for DPS Server in the master instead of sidecar
+            if not config.DirectoryProxy.Enabled:
+                strPortsService(
+                    &quot;unix:{path}:mode=660&quot;.format(
+                        path=config.DirectoryProxy.SocketPath
+                    ),
+                    DirectoryProxyAMPFactory(store.directoryService())
+                ).setServiceParent(multi)
+
</ins><span class="cx">             return multi
</span><span class="cx"> 
</span><del>-        ssvc = self.storageService(spawnerSvcCreator, None, uid, gid)
</del><ins>+        if config.DirectoryProxy.Enabled:
+            # If the master is to act as a DPS client, and talk to the
+            # DPS sidecar:
+            directory = DirectoryProxyClientService(
+                config.DirectoryRealmName
+            )
+        else:
+            # If the master is to act as the DPS server:
+            directory = None
+
+        ssvc = self.storageService(
+            spawnerSvcCreator, None, uid, gid, directory=directory
+        )
</ins><span class="cx">         ssvc.setServiceParent(s)
</span><span class="cx">         return s
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertaptesttest_caldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_caldav.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_caldav.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_caldav.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -28,12 +28,11 @@
</span><span class="cx"> from twisted.python.threadable import isInIOThread
</span><span class="cx"> from twisted.internet.reactor import callFromThread
</span><span class="cx"> from twisted.python.usage import Options, UsageError
</span><del>-from twisted.python.reflect import namedAny
</del><span class="cx"> from twisted.python.procutils import which
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.interfaces import IProcessTransport, IReactorProcess
</span><span class="cx"> from twisted.internet.protocol import ServerFactory
</span><del>-from twisted.internet.defer import Deferred, inlineCallbacks, passthru, succeed
</del><ins>+from twisted.internet.defer import Deferred, inlineCallbacks, succeed
</ins><span class="cx"> from twisted.internet.task import Clock
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.application.service import (IService, IServiceCollection,
</span><span class="lines">@@ -42,15 +41,15 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><del>-from plistlib import writePlist #@UnresolvedImport
</del><ins>+from plistlib import writePlist  # @UnresolvedImport
</ins><span class="cx"> from txweb2.dav import auth
</span><span class="cx"> from txweb2.log import LogWrapperResource
</span><span class="cx"> from twext.internet.tcp import MaxAcceptTCPServer, MaxAcceptSSLServer
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config, ConfigDict, ConfigurationError
</span><ins>+from twistedcaldav.resource import AuthenticationWrapper
</ins><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG
</span><span class="cx"> 
</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.principal import DirectoryPrincipalProvisioningResource
</span><span class="cx"> 
</span><span class="lines">@@ -178,7 +177,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CalDAVOptionsTest (StoreTestCase):
</del><ins>+class CalDAVOptionsTest(StoreTestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test various parameters of our usage.Options subclass
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -304,111 +303,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class BaseServiceMakerTests(StoreTestCase):
-    &quot;&quot;&quot;
-    Utility class for ServiceMaker tests.
-    &quot;&quot;&quot;
-    configOptions = None
-
-    @inlineCallbacks
-    def setUp(self):
-        yield super(BaseServiceMakerTests, self).setUp()
-        self.options = TestCalDAVOptions()
-        self.options.parent = Options()
-        self.options.parent[&quot;gid&quot;] = None
-        self.options.parent[&quot;uid&quot;] = None
-        self.options.parent[&quot;nodaemon&quot;] = None
-
-        self.config = ConfigDict(DEFAULT_CONFIG)
-
-        accountsFile = os.path.join(sourceRoot, &quot;twistedcaldav/directory/test/accounts.xml&quot;)
-        resourcesFile = os.path.join(sourceRoot, &quot;twistedcaldav/directory/test/resources.xml&quot;)
-        augmentsFile = os.path.join(sourceRoot, &quot;twistedcaldav/directory/test/augments.xml&quot;)
-        pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
-
-        self.config[&quot;DirectoryService&quot;] = {
-            &quot;params&quot;: {&quot;xmlFile&quot;: accountsFile},
-            &quot;type&quot;: &quot;twistedcaldav.directory.xmlfile.XMLDirectoryService&quot;
-        }
-
-        self.config[&quot;ResourceService&quot;] = {
-            &quot;params&quot;: {&quot;xmlFile&quot;: resourcesFile},
-        }
-
-        self.config[&quot;AugmentService&quot;] = {
-            &quot;params&quot;: {&quot;xmlFiles&quot;: [augmentsFile]},
-            &quot;type&quot;: &quot;twistedcaldav.directory.augment.AugmentXMLDB&quot;
-        }
-
-        self.config.UseDatabase = False
-        self.config.ServerRoot = self.mktemp()
-        self.config.ConfigRoot = &quot;config&quot;
-        self.config.ProcessType = &quot;Single&quot;
-        self.config.SSLPrivateKey = pemFile
-        self.config.SSLCertificate = pemFile
-        self.config.EnableSSL = True
-        self.config.Memcached.Pools.Default.ClientEnabled = False
-        self.config.Memcached.Pools.Default.ServerEnabled = False
-        self.config.DirectoryAddressBook.Enabled = False
-        self.config.UsePackageTimezones = True
-
-        if self.configOptions:
-            self.config.update(self.configOptions)
-
-        os.mkdir(self.config.ServerRoot)
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot))
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot))
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot))
-
-        self.configFile = self.mktemp()
-
-        self.writeConfig()
-
-
-    def tearDown(self):
-        config.setDefaults(DEFAULT_CONFIG)
-        config.reset()
-
-
-    def writeConfig(self):
-        &quot;&quot;&quot;
-        Flush self.config out to self.configFile
-        &quot;&quot;&quot;
-        writePlist(self.config, self.configFile)
-
-
-    def makeService(self, patcher=passthru):
-        &quot;&quot;&quot;
-        Create a service by calling into CalDAVServiceMaker with
-        self.configFile
-        &quot;&quot;&quot;
-        self.options.parseOptions([&quot;-f&quot;, self.configFile])
-
-        maker = CalDAVServiceMaker()
-        maker = patcher(maker)
-        return maker.makeService(self.options)
-
-
-    def getSite(self):
-        &quot;&quot;&quot;
-        Get the server.Site from the service by finding the HTTPFactory.
-        &quot;&quot;&quot;
-        service = self.makeService()
-        for listeningService in inServiceHierarchy(
-                service,
-                # FIXME: need a better predicate for 'is this really an HTTP
-                # factory' but this works for now.
-                # NOTE: in a database 'single' configuration, PostgresService
-                # will prevent the HTTP services from actually getting added to
-                # the hierarchy until the hierarchy has started.
-                # 'underlyingSite' assigned in caldav.py
-                lambda x: hasattr(x, 'underlyingSite')
-            ):
-            return listeningService.underlyingSite
-        raise RuntimeError(&quot;No site found.&quot;)
-
-
-
</del><span class="cx"> def inServiceHierarchy(svc, predicate):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Find services in the service collection which satisfy the given predicate.
</span><span class="lines">@@ -452,44 +346,72 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CalDAVServiceMakerTests(BaseServiceMakerTests):
-    &quot;&quot;&quot;
-    Test the service maker's behavior
-    &quot;&quot;&quot;
</del><ins>+# Tests for the various makeService_ flavors:
</ins><span class="cx"> 
</span><del>-    def test_makeServiceDispatcher(self):
-        &quot;&quot;&quot;
-        Test the default options of the dispatching makeService
-        &quot;&quot;&quot;
-        validServices = [&quot;Slave&quot;, &quot;Combined&quot;]
</del><ins>+class CalDAVServiceMakerTestBase(StoreTestCase):
</ins><span class="cx"> 
</span><del>-        self.config[&quot;HTTPPort&quot;] = 0
</del><ins>+    @inlineCallbacks
+    def setUp(self):
+        yield super(CalDAVServiceMakerTestBase, self).setUp()
+        self.options = TestCalDAVOptions()
+        self.options.parent = Options()
+        self.options.parent[&quot;gid&quot;] = None
+        self.options.parent[&quot;uid&quot;] = None
+        self.options.parent[&quot;nodaemon&quot;] = None
</ins><span class="cx"> 
</span><del>-        for service in validServices:
-            self.config[&quot;ProcessType&quot;] = service
-            self.writeConfig()
-            self.makeService()
</del><span class="cx"> 
</span><del>-        self.config[&quot;ProcessType&quot;] = &quot;Unknown Service&quot;
-        self.writeConfig()
-        self.assertRaises(UsageError, self.makeService)
</del><ins>+class CalDAVServiceMakerTestSingle(CalDAVServiceMakerTestBase):
</ins><span class="cx"> 
</span><ins>+    def configure(self):
+        super(CalDAVServiceMakerTestSingle, self).configure()
+        config.ProcessType = &quot;Single&quot;
</ins><span class="cx"> 
</span><ins>+    def test_makeService(self):
+        CalDAVServiceMaker().makeService(self.options)
+        # No error
+
+
+class CalDAVServiceMakerTestSlave(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(CalDAVServiceMakerTestSlave, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+
+    def test_makeService(self):
+        CalDAVServiceMaker().makeService(self.options)
+        # No error
+
+
+class CalDAVServiceMakerTestUnknown(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(CalDAVServiceMakerTestUnknown, self).configure()
+        config.ProcessType = &quot;Unknown&quot;
+
+    def test_makeService(self):
+        self.assertRaises(UsageError, CalDAVServiceMaker().makeService, self.options)
+        # error
+
+
+
+class ModesOnUNIXSocketsTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(ModesOnUNIXSocketsTests, self).configure()
+        config.ProcessType = &quot;Combined&quot;
+        config.HTTPPort = 0
+        self.alternateGroup = determineAppropriateGroupID()
+        config.GroupName = grp.getgrgid(self.alternateGroup).gr_name
+        config.Stats.EnableUnixStatsSocket = True
+
+
</ins><span class="cx">     def test_modesOnUNIXSockets(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         The logging and stats UNIX sockets that are bound as part of the
</span><span class="cx">         'Combined' service hierarchy should have a secure mode specified: only
</span><span class="cx">         the executing user should be able to open and send to them.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-
-        self.config[&quot;HTTPPort&quot;] = 0 # Don't conflict with the test above.
-        alternateGroup = determineAppropriateGroupID()
-        self.config.GroupName = grp.getgrgid(alternateGroup).gr_name
-
-        self.config[&quot;ProcessType&quot;] = &quot;Combined&quot;
-        self.config.Stats.EnableUnixStatsSocket = True
-        self.writeConfig()
-        svc = self.makeService()
</del><ins>+        svc = CalDAVServiceMaker().makeService(self.options)
</ins><span class="cx">         for serviceName in [_CONTROL_SERVICE_NAME]:
</span><span class="cx">             socketService = svc.getServiceNamed(serviceName)
</span><span class="cx">             self.assertIsInstance(socketService, GroupOwnedUNIXServer)
</span><span class="lines">@@ -498,7 +420,7 @@
</span><span class="cx">                 m, int(&quot;660&quot;, 8),
</span><span class="cx">                 &quot;Wrong mode on %s: %s&quot; % (serviceName, oct(m))
</span><span class="cx">             )
</span><del>-            self.assertEquals(socketService.gid, alternateGroup)
</del><ins>+            self.assertEquals(socketService.gid, self.alternateGroup)
</ins><span class="cx">         for serviceName in [&quot;unix-stats&quot;]:
</span><span class="cx">             socketService = svc.getServiceNamed(serviceName)
</span><span class="cx">             self.assertIsInstance(socketService, GroupOwnedUNIXServer)
</span><span class="lines">@@ -507,84 +429,119 @@
</span><span class="cx">                 m, int(&quot;660&quot;, 8),
</span><span class="cx">                 &quot;Wrong mode on %s: %s&quot; % (serviceName, oct(m))
</span><span class="cx">             )
</span><del>-            self.assertEquals(socketService.gid, alternateGroup)
</del><ins>+            self.assertEquals(socketService.gid, self.alternateGroup)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class ProcessMonitorTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(ProcessMonitorTests, self).configure()
+        config.ProcessType = &quot;Combined&quot;
+
</ins><span class="cx">     def test_processMonitor(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         In the master, there should be exactly one
</span><span class="cx">         L{DelayedStartupProcessMonitor} in the service hierarchy so that it
</span><span class="cx">         will be started by startup.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config[&quot;ProcessType&quot;] = &quot;Combined&quot;
-        self.writeConfig()
</del><span class="cx">         self.assertEquals(
</span><span class="cx">             1,
</span><span class="cx">             len(
</span><span class="cx">                 list(inServiceHierarchy(
</span><del>-                    self.makeService(),
</del><ins>+                    CalDAVServiceMaker().makeService(self.options),
</ins><span class="cx">                     lambda x: isinstance(x, DelayedStartupProcessMonitor)))
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class StoreQueuerSetInMasterTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(StoreQueuerSetInMasterTests, self).configure()
+        config.ProcessType = &quot;Combined&quot;
+
+
</ins><span class="cx">     def test_storeQueuerSetInMaster(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         In the master, the store's queuer should be set to a
</span><span class="cx">         L{PeerConnectionPool}, so that work can be distributed to other
</span><span class="cx">         processes.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config[&quot;ProcessType&quot;] = &quot;Combined&quot;
-        self.writeConfig()
</del><span class="cx">         class NotAStore(object):
</span><span class="cx">             queuer = LocalQueuer(None)
</span><ins>+
</ins><span class="cx">             def __init__(self, directory):
</span><span class="cx">                 self.directory = directory
</span><ins>+
</ins><span class="cx">             def newTransaction(self):
</span><span class="cx">                 return None
</span><ins>+
</ins><span class="cx">             def callWithNewTransactions(self, x):
</span><span class="cx">                 pass
</span><ins>+
</ins><span class="cx">             def directoryService(self):
</span><span class="cx">                 return self.directory
</span><ins>+
+
</ins><span class="cx">         store = NotAStore(self.directory)
</span><ins>+
+
</ins><span class="cx">         def something(proposal):
</span><span class="cx">             pass
</span><ins>+
</ins><span class="cx">         store.queuer.callWithNewProposals(something)
</span><ins>+
+
</ins><span class="cx">         def patch(maker):
</span><span class="cx">             def storageServiceStandIn(createMainService, logObserver,
</span><del>-                                      uid=None, gid=None):
</del><ins>+                                      uid=None, gid=None, directory=None):
</ins><span class="cx">                 pool = None
</span><span class="cx">                 logObserver = None
</span><span class="cx">                 storageService = None
</span><del>-                svc = createMainService(pool, store, logObserver,
-                    storageService)
</del><ins>+                svc = createMainService(
+                    pool, store, logObserver, storageService
+                )
</ins><span class="cx">                 multi = MultiService()
</span><span class="cx">                 svc.setServiceParent(multi)
</span><span class="cx">                 return multi
</span><span class="cx">             self.patch(maker, &quot;storageService&quot;, storageServiceStandIn)
</span><span class="cx">             return maker
</span><del>-        self.makeService(patch)
</del><ins>+
+        maker = CalDAVServiceMaker()
+        maker = patch(maker)
+        maker.makeService(self.options)
</ins><span class="cx">         self.assertIsInstance(store.queuer, PeerConnectionPool)
</span><span class="cx">         self.assertIn(something, store.queuer.proposalCallbacks)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class SlaveServiceTest(BaseServiceMakerTests):
</del><ins>+
+
+
+
+class SlaveServiceTests(CalDAVServiceMakerTestBase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test various configurations of the Slave service
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    configOptions = {
-        &quot;HTTPPort&quot;: 8008,
-        &quot;SSLPort&quot;: 8443,
-    }
</del><ins>+    def configure(self):
+        super(SlaveServiceTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.HTTPPort = 8008
+        config.SSLPort = 8443
+        pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
+        config.SSLPrivateKey = pemFile
+        config.SSLCertificate = pemFile
+        config.EnableSSL = True
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_defaultService(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the value of a Slave service in it's simplest
</span><span class="cx">         configuration.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        service = self.makeService()
</del><ins>+        service = CalDAVServiceMaker().makeService(self.options)
</ins><span class="cx"> 
</span><span class="cx">         self.failUnless(
</span><span class="cx">             IService(service),
</span><span class="lines">@@ -606,11 +563,12 @@
</span><span class="cx">         default TCP and SSL configuration
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
</span><del>-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
</del><ins>+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><span class="cx">         expectedSubServices = dict((
</span><del>-            (MaxAcceptTCPServer, self.config[&quot;HTTPPort&quot;]),
-            (MaxAcceptSSLServer, self.config[&quot;SSLPort&quot;]),
</del><ins>+            (MaxAcceptTCPServer, config.HTTPPort),
+            (MaxAcceptSSLServer, config.SSLPort),
</ins><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx">         configuredSubServices = [(s.__class__, getattr(s, 'args', None))
</span><span class="lines">@@ -632,7 +590,9 @@
</span><span class="cx">         Test that the configuration of the SSLServer reflect the config file's
</span><span class="cx">         SSL Private Key and SSL Certificate
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><span class="cx">         sslService = None
</span><span class="cx">         for s in service.services:
</span><span class="lines">@@ -645,75 +605,115 @@
</span><span class="cx">         context = sslService.args[2]
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(
</span><del>-            self.config[&quot;SSLPrivateKey&quot;],
</del><ins>+            config.SSLPrivateKey,
</ins><span class="cx">             context.privateKeyFileName
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><del>-            self.config[&quot;SSLCertificate&quot;],
</del><ins>+            config.SSLCertificate,
</ins><span class="cx">             context.certificateFileName,
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class NoSSLTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(NoSSLTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.HTTPPort = 8008
+        # pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
+        # config.SSLPrivateKey = pemFile
+        # config.SSLCertificate = pemFile
+        # config.EnableSSL = True
+
</ins><span class="cx">     def test_noSSL(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the single service to make sure there is no SSL Service when SSL
</span><span class="cx">         is disabled
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        del self.config[&quot;SSLPort&quot;]
-        self.writeConfig()
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><del>-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
-
</del><span class="cx">         self.assertNotIn(
</span><span class="cx">             internet.SSLServer,
</span><span class="cx">             [s.__class__ for s in service.services]
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class NoHTTPTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(NoHTTPTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.SSLPort = 8443
+        pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
+        config.SSLPrivateKey = pemFile
+        config.SSLCertificate = pemFile
+        config.EnableSSL = True
+
</ins><span class="cx">     def test_noHTTP(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the single service to make sure there is no TCPServer when
</span><span class="cx">         HTTPPort is not configured
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        del self.config[&quot;HTTPPort&quot;]
-        self.writeConfig()
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><del>-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
-
</del><span class="cx">         self.assertNotIn(
</span><span class="cx">             internet.TCPServer,
</span><span class="cx">             [s.__class__ for s in service.services]
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class SingleBindAddressesTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(SingleBindAddressesTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.HTTPPort = 8008
+        config.BindAddresses = [&quot;127.0.0.1&quot;]
+
</ins><span class="cx">     def test_singleBindAddresses(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that the TCPServer and SSLServers are bound to the proper address
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config.BindAddresses = [&quot;127.0.0.1&quot;]
-        self.writeConfig()
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><del>-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
-
</del><span class="cx">         for s in service.services:
</span><span class="cx">             if isinstance(s, (internet.TCPServer, internet.SSLServer)):
</span><span class="cx">                 self.assertEquals(s.kwargs[&quot;interface&quot;], &quot;127.0.0.1&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class MultipleBindAddressesTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(MultipleBindAddressesTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.HTTPPort = 8008
+        config.SSLPort = 8443
+        pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
+        config.SSLPrivateKey = pemFile
+        config.SSLCertificate = pemFile
+        config.EnableSSL = True
+        config.BindAddresses = [
+            &quot;127.0.0.1&quot;,
+            &quot;10.0.0.2&quot;,
+            &quot;172.53.13.123&quot;,
+        ]
+
</ins><span class="cx">     def test_multipleBindAddresses(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that the TCPServer and SSLServers are bound to the proper
</span><span class="cx">         addresses.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config.BindAddresses = [
-            &quot;127.0.0.1&quot;,
-            &quot;10.0.0.2&quot;,
-            &quot;172.53.13.123&quot;,
-        ]
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><del>-        self.writeConfig()
-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
-
</del><span class="cx">         tcpServers = []
</span><span class="cx">         sslServers = []
</span><span class="cx"> 
</span><span class="lines">@@ -723,10 +723,10 @@
</span><span class="cx">             elif isinstance(s, internet.SSLServer):
</span><span class="cx">                 sslServers.append(s)
</span><span class="cx"> 
</span><del>-        self.assertEquals(len(tcpServers), len(self.config.BindAddresses))
-        self.assertEquals(len(sslServers), len(self.config.BindAddresses))
</del><ins>+        self.assertEquals(len(tcpServers), len(config.BindAddresses))
+        self.assertEquals(len(sslServers), len(config.BindAddresses))
</ins><span class="cx"> 
</span><del>-        for addr in self.config.BindAddresses:
</del><ins>+        for addr in config.BindAddresses:
</ins><span class="cx">             for s in tcpServers:
</span><span class="cx">                 if s.kwargs[&quot;interface&quot;] == addr:
</span><span class="cx">                     tcpServers.remove(s)
</span><span class="lines">@@ -739,13 +739,32 @@
</span><span class="cx">         self.assertEquals(len(sslServers), 0)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class ListenBacklogTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(ListenBacklogTests, self).configure()
+        config.ProcessType = &quot;Slave&quot;
+        config.ListenBacklog = 1024
+        config.HTTPPort = 8008
+        config.SSLPort = 8443
+        pemFile = os.path.join(sourceRoot, &quot;twistedcaldav/test/data/server.pem&quot;)
+        config.SSLPrivateKey = pemFile
+        config.SSLCertificate = pemFile
+        config.EnableSSL = True
+        config.BindAddresses = [
+            &quot;127.0.0.1&quot;,
+            &quot;10.0.0.2&quot;,
+            &quot;172.53.13.123&quot;,
+        ]
+
</ins><span class="cx">     def test_listenBacklog(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that the backlog arguments is set in TCPServer and SSLServers
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config.ListenBacklog = 1024
-        self.writeConfig()
-        service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
</del><ins>+        # Note: the listeners are bundled within a MultiService named &quot;ConnectionService&quot;
+        service = CalDAVServiceMaker().makeService(self.options)
+        service = service.getServiceNamed(CalDAVService.connectionServiceName)
</ins><span class="cx"> 
</span><span class="cx">         for s in service.services:
</span><span class="cx">             if isinstance(s, (internet.TCPServer, internet.SSLServer)):
</span><span class="lines">@@ -753,32 +772,31 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class ServiceHTTPFactoryTests(BaseServiceMakerTests):
-    &quot;&quot;&quot;
-    Test the configuration of the initial resource hierarchy of the
-    single service
-    &quot;&quot;&quot;
-    configOptions = {&quot;HTTPPort&quot;: 8008}
</del><ins>+class AuthWrapperAllEnabledTests(CalDAVServiceMakerTestBase):
</ins><span class="cx"> 
</span><ins>+    def configure(self):
+        super(AuthWrapperAllEnabledTests, self).configure()
+        config.HTTPPort = 8008
+        config.Authentication.Digest.Enabled = True
+        config.Authentication.Kerberos.Enabled = True
+        config.Authentication.Kerberos.ServicePrincipal = &quot;http/hello@bob&quot;
+        config.Authentication.Basic.Enabled = True
+
+
</ins><span class="cx">     def test_AuthWrapperAllEnabled(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the configuration of the authentication wrapper
</span><span class="cx">         when all schemes are enabled.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config.Authentication.Digest.Enabled = True
-        self.config.Authentication.Kerberos.Enabled = True
-        self.config.Authentication.Kerberos.ServicePrincipal = &quot;http/hello@bob&quot;
-        self.config.Authentication.Basic.Enabled = True
</del><span class="cx"> 
</span><del>-        self.writeConfig()
-        site = self.getSite()
</del><ins>+        authWrapper = self.rootResource.resource
+        self.failUnless(
+            isinstance(
+                authWrapper,
+                auth.AuthenticationWrapper
+            )
+        )
</ins><span class="cx"> 
</span><del>-        self.failUnless(isinstance(
-                site.resource.resource,
-                auth.AuthenticationWrapper))
-
-        authWrapper = site.resource.resource
-
</del><span class="cx">         expectedSchemes = [&quot;negotiate&quot;, &quot;digest&quot;, &quot;basic&quot;]
</span><span class="cx"> 
</span><span class="cx">         for scheme in authWrapper.credentialFactories:
</span><span class="lines">@@ -787,36 +805,39 @@
</span><span class="cx">         self.assertEquals(len(expectedSchemes),
</span><span class="cx">                           len(authWrapper.credentialFactories))
</span><span class="cx"> 
</span><ins>+        ncf = authWrapper.credentialFactories[&quot;negotiate&quot;]
</ins><span class="cx"> 
</span><ins>+        self.assertEquals(ncf.service, &quot;http@HELLO&quot;)
+        self.assertEquals(ncf.realm, &quot;bob&quot;)
+
+
+
+class ServicePrincipalNoneTests(CalDAVServiceMakerTestBase):
+
+    def configure(self):
+        super(ServicePrincipalNoneTests, self).configure()
+        config.HTTPPort = 8008
+        config.Authentication.Digest.Enabled = True
+        config.Authentication.Kerberos.Enabled = True
+        config.Authentication.Kerberos.ServicePrincipal = &quot;&quot;
+        config.Authentication.Basic.Enabled = True
+
</ins><span class="cx">     def test_servicePrincipalNone(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that the Kerberos principal look is attempted if the principal is empty.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.config.Authentication.Kerberos.ServicePrincipal = &quot;&quot;
-        self.config.Authentication.Kerberos.Enabled = True
-        self.writeConfig()
-        site = self.getSite()
-
-        authWrapper = site.resource.resource
-
</del><ins>+        authWrapper = self.rootResource.resource
</ins><span class="cx">         self.assertFalse(&quot;negotiate&quot; in authWrapper.credentialFactories)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def test_servicePrincipal(self):
-        &quot;&quot;&quot;
-        Test that the kerberos realm is the realm portion of a principal
-        in the form proto/host@realm
-        &quot;&quot;&quot;
-        self.config.Authentication.Kerberos.ServicePrincipal = &quot;http/hello@bob&quot;
-        self.config.Authentication.Kerberos.Enabled = True
-        self.writeConfig()
-        site = self.getSite()
</del><span class="cx"> 
</span><del>-        authWrapper = site.resource.resource
-        ncf = authWrapper.credentialFactories[&quot;negotiate&quot;]
</del><ins>+class AuthWrapperPartialEnabledTests(CalDAVServiceMakerTestBase):
</ins><span class="cx"> 
</span><del>-        self.assertEquals(ncf.service, &quot;http@HELLO&quot;)
-        self.assertEquals(ncf.realm, &quot;bob&quot;)
</del><ins>+    def configure(self):
+        super(AuthWrapperPartialEnabledTests, self).configure()
+        config.Authentication.Digest.Enabled = True
+        config.Authentication.Kerberos.Enabled = False
+        config.Authentication.Basic.Enabled = False
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_AuthWrapperPartialEnabled(self):
</span><span class="lines">@@ -826,14 +847,7 @@
</span><span class="cx">         enabled.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.config.Authentication.Basic.Enabled = False
-        self.config.Authentication.Kerberos.Enabled = False
-
-        self.writeConfig()
-        site = self.getSite()
-
-        authWrapper = site.resource.resource
-
</del><ins>+        authWrapper = self.rootResource.resource
</ins><span class="cx">         expectedSchemes = [&quot;digest&quot;]
</span><span class="cx"> 
</span><span class="cx">         for scheme in authWrapper.credentialFactories:
</span><span class="lines">@@ -845,102 +859,67 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+
+
+class ResourceTests(CalDAVServiceMakerTestBase):
+
</ins><span class="cx">     def test_LogWrapper(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the configuration of the log wrapper
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        site = self.getSite()
</del><ins>+        self.failUnless(isinstance(self.rootResource, LogWrapperResource))
</ins><span class="cx"> 
</span><del>-        self.failUnless(isinstance(
-                site.resource,
-                LogWrapperResource))
</del><span class="cx"> 
</span><ins>+    def test_AuthWrapper(self):
+        &quot;&quot;&quot;
+        Test the configuration of the auth wrapper
+        &quot;&quot;&quot;
+        self.failUnless(isinstance(self.rootResource.resource, AuthenticationWrapper))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_rootResource(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the root resource
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        site = self.getSite()
-        root = site.resource.resource.resource
</del><ins>+        self.failUnless(isinstance(self.rootResource.resource.resource, RootResource))
</ins><span class="cx"> 
</span><del>-        self.failUnless(isinstance(root, RootResource))
</del><span class="cx"> 
</span><del>-
</del><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalResource(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the principal resource
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        site = self.getSite()
-        root = site.resource.resource.resource
-
</del><span class="cx">         self.failUnless(isinstance(
</span><del>-            root.getChild(&quot;principals&quot;),
</del><ins>+            (yield self.actualRoot.getChild(&quot;principals&quot;)),
</ins><span class="cx">             DirectoryPrincipalProvisioningResource
</span><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_calendarResource(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test the calendar resource
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        site = self.getSite()
-        root = site.resource.resource.resource
-
</del><span class="cx">         self.failUnless(isinstance(
</span><del>-            root.getChild(&quot;calendars&quot;),
</del><ins>+            (yield self.actualRoot.getChild(&quot;calendars&quot;)),
</ins><span class="cx">             DirectoryCalendarHomeProvisioningResource
</span><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-class DirectoryServiceTest(BaseServiceMakerTests):
-    &quot;&quot;&quot;
-    Tests of the directory service
-    &quot;&quot;&quot;
-
-    configOptions = {&quot;HTTPPort&quot;: 8008}
-
</del><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_sameDirectory(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that the principal hierarchy has a reference
</span><span class="cx">         to the same DirectoryService as the calendar hierarchy
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild(&quot;principals&quot;)
-        calendars = site.resource.resource.resource.getChild(&quot;calendars&quot;)
</del><ins>+        principals = yield self.actualRoot.getChild(&quot;principals&quot;)
+        calendars = yield self.actualRoot.getChild(&quot;calendars&quot;)
</ins><span class="cx"> 
</span><span class="cx">         self.assertEquals(principals.directory, calendars.directory)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def test_aggregateDirectory(self):
-        &quot;&quot;&quot;
-        Assert that the base directory service is actually
-        an AggregateDirectoryService
-        &quot;&quot;&quot;
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild(&quot;principals&quot;)
-        directory = principals.directory
</del><span class="cx"> 
</span><del>-        self.failUnless(isinstance(directory, AggregateDirectoryService))
-
-
-    def test_configuredDirectoryService(self):
-        &quot;&quot;&quot;
-        Test that the real directory service is the directory service
-        set in the configuration file.
-        &quot;&quot;&quot;
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild(&quot;principals&quot;)
-        directory = principals.directory
-
-        realDirectory = directory.serviceForRecordType(&quot;users&quot;)
-
-        configuredDirectory = namedAny(self.config.DirectoryService.type)
-
-        self.failUnless(isinstance(realDirectory, configuredDirectory))
-
-
-
</del><span class="cx"> class DummyProcessObject(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Simple stub for Process Object API which just has an executable and some
</span><span class="lines">@@ -1012,9 +991,13 @@
</span><span class="cx">         at once, to avoid resource exhaustion.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         dspm = DelayedStartupProcessMonitor()
</span><del>-        dspm.addProcessObject(ScriptProcessObject(
-                'longlines.py', str(DelayedStartupLineLogger.MAX_LENGTH)),
-                          os.environ)
</del><ins>+        dspm.addProcessObject(
+            ScriptProcessObject(
+                'longlines.py',
+                str(DelayedStartupLineLogger.MAX_LENGTH)
+            ),
+            os.environ
+        )
</ins><span class="cx">         dspm.startService()
</span><span class="cx">         self.addCleanup(dspm.stopService)
</span><span class="cx"> 
</span><span class="lines">@@ -1038,10 +1021,11 @@
</span><span class="cx">         logging.addObserver(tempObserver)
</span><span class="cx">         self.addCleanup(logging.removeObserver, tempObserver)
</span><span class="cx">         d = Deferred()
</span><ins>+
</ins><span class="cx">         def assertions(result):
</span><span class="cx">             self.assertEquals([&quot;[Dummy] x&quot;,
</span><span class="cx">                                &quot;[Dummy] y&quot;,
</span><del>-                               &quot;[Dummy] y&quot;, # final segment
</del><ins>+                               &quot;[Dummy] y&quot;,  # final segment
</ins><span class="cx">                                &quot;[Dummy] z&quot;],
</span><span class="cx">                               [''.join(evt['message'])[:len('[Dummy]') + 2]
</span><span class="cx">                                for evt in logged])
</span><span class="lines">@@ -1066,20 +1050,27 @@
</span><span class="cx">             (&quot;a&quot;, [&quot;a&quot;]),
</span><span class="cx">             (&quot;abcde&quot;, [&quot;abcde&quot;]),
</span><span class="cx">             (&quot;abcdefghij&quot;, [&quot;abcdefghij&quot;]),
</span><del>-            (&quot;abcdefghijk&quot;,
-                [&quot;abcdefghij (truncated, continued)&quot;,
-                 &quot;k&quot;
</del><ins>+            (
+                &quot;abcdefghijk&quot;,
+                [
+                    &quot;abcdefghij (truncated, continued)&quot;,
+                    &quot;k&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             ),
</span><del>-            (&quot;abcdefghijklmnopqrst&quot;,
-                [&quot;abcdefghij (truncated, continued)&quot;,
-                 &quot;klmnopqrst&quot;
</del><ins>+            (
+                &quot;abcdefghijklmnopqrst&quot;,
+                [
+                    &quot;abcdefghij (truncated, continued)&quot;,
+                    &quot;klmnopqrst&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             ),
</span><del>-            (&quot;abcdefghijklmnopqrstuv&quot;,
-                [&quot;abcdefghij (truncated, continued)&quot;,
-                 &quot;klmnopqrst (truncated, continued)&quot;,
-                 &quot;uv&quot;]
</del><ins>+            (
+                &quot;abcdefghijklmnopqrstuv&quot;,
+                [
+                    &quot;abcdefghij (truncated, continued)&quot;,
+                    &quot;klmnopqrst (truncated, continued)&quot;,
+                    &quot;uv&quot;
+                ]
</ins><span class="cx">             ),
</span><span class="cx">         ]:
</span><span class="cx">             self.assertEquals(output, testLogger._breakLineIntoSegments(input))
</span><span class="lines">@@ -1282,9 +1273,10 @@
</span><span class="cx">         twistd = which(&quot;twistd&quot;)[0]
</span><span class="cx">         deferred = Deferred()
</span><span class="cx">         proc = reactor.spawnProcess(
</span><del>-            CapturingProcessProtocol(deferred, None), twistd,
-                [twistd, reactorArg, '-n', '-y', tacFilePath],
-                env=os.environ
</del><ins>+            CapturingProcessProtocol(deferred, None),
+            twistd,
+            [twistd, reactorArg, '-n', '-y', tacFilePath],
+            env=os.environ
</ins><span class="cx">         )
</span><span class="cx">         reactor.callLater(3, proc.signalProcess, &quot;HUP&quot;)
</span><span class="cx">         reactor.callLater(6, proc.signalProcess, &quot;TERM&quot;)
</span><span class="lines">@@ -1325,14 +1317,15 @@
</span><span class="cx">         def _getgid():
</span><span class="cx">             return 45
</span><span class="cx"> 
</span><del>-        return type(getSystemIDs)(getSystemIDs.func_code,
</del><ins>+        return type(getSystemIDs)(
+            getSystemIDs.func_code,
</ins><span class="cx">             {
</span><del>-                &quot;getpwnam&quot; : _getpwnam,
-                &quot;getgrnam&quot; : _getgrnam,
-                &quot;getuid&quot; : _getuid,
-                &quot;getgid&quot; : _getgid,
-                &quot;KeyError&quot; : KeyError,
-                &quot;ConfigurationError&quot; : ConfigurationError,
</del><ins>+                &quot;getpwnam&quot;: _getpwnam,
+                &quot;getgrnam&quot;: _getgrnam,
+                &quot;getuid&quot;: _getuid,
+                &quot;getgid&quot;: _getgid,
+                &quot;KeyError&quot;: KeyError,
+                &quot;ConfigurationError&quot;: ConfigurationError,
</ins><span class="cx">             }
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -1342,8 +1335,10 @@
</span><span class="cx">         If userName is passed in but is not found on the system, raise a
</span><span class="cx">         ConfigurationError
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.assertRaises(ConfigurationError, self._wrappedFunction(),
-            &quot;nonexistent&quot;, &quot;exists&quot;)
</del><ins>+        self.assertRaises(
+            ConfigurationError, self._wrappedFunction(),
+            &quot;nonexistent&quot;, &quot;exists&quot;
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_getSystemIDs_GroupNameNotFound(self):
</span><span class="lines">@@ -1351,8 +1346,10 @@
</span><span class="cx">         If groupName is passed in but is not found on the system, raise a
</span><span class="cx">         ConfigurationError
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.assertRaises(ConfigurationError, self._wrappedFunction(),
-            &quot;exists&quot;, &quot;nonexistent&quot;)
</del><ins>+        self.assertRaises(
+            ConfigurationError, self._wrappedFunction(),
+            &quot;exists&quot;, &quot;nonexistent&quot;
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_getSystemIDs_NamesNotSpecified(self):
</span><span class="lines">@@ -1428,8 +1425,10 @@
</span><span class="cx">     def setUp(self):
</span><span class="cx">         self.history = []
</span><span class="cx">         self.clock = Clock()
</span><del>-        self.pps = PreProcessingService(self.fakeServiceCreator, None, &quot;store&quot;,
-            None, &quot;storageService&quot;, reactor=self.clock)
</del><ins>+        self.pps = PreProcessingService(
+            self.fakeServiceCreator, None, &quot;store&quot;,
+            None, &quot;storageService&quot;, reactor=self.clock
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _record(self, value, failure):
</span><span class="lines">@@ -1447,9 +1446,13 @@
</span><span class="cx">             StepFour(self._record, False)
</span><span class="cx">         )
</span><span class="cx">         self.pps.startService()
</span><del>-        self.assertEquals(self.history,
-            ['one success', 'two success', 'three success', 'four success',
-            ('serviceCreator', 'store', 'storageService')])
</del><ins>+        self.assertEquals(
+            self.history,
+            [
+                'one success', 'two success', 'three success', 'four success',
+                ('serviceCreator', 'store', 'storageService')
+            ]
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_allFailure(self):
</span><span class="lines">@@ -1463,9 +1466,13 @@
</span><span class="cx">             StepFour(self._record, True)
</span><span class="cx">         )
</span><span class="cx">         self.pps.startService()
</span><del>-        self.assertEquals(self.history,
-            ['one success', 'two failure', 'three failure', 'four failure',
-            ('serviceCreator', None, 'storageService')])
</del><ins>+        self.assertEquals(
+            self.history,
+            [
+                'one success', 'two failure', 'three failure', 'four failure',
+                ('serviceCreator', None, 'storageService')
+            ]
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_partialFailure(self):
</span><span class="lines">@@ -1479,9 +1486,13 @@
</span><span class="cx">             StepFour(self._record, False)
</span><span class="cx">         )
</span><span class="cx">         self.pps.startService()
</span><del>-        self.assertEquals(self.history,
-            ['one success', 'two failure', 'three success', 'four failure',
-            ('serviceCreator', 'store', 'storageService')])
</del><ins>+        self.assertEquals(
+            self.history,
+            [
+                'one success', 'two failure', 'three success', 'four failure',
+                ('serviceCreator', 'store', 'storageService')
+            ]
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_quitAfterUpgradeStep(self):
</span><span class="lines">@@ -1498,9 +1509,13 @@
</span><span class="cx">         )
</span><span class="cx">         triggerFile.setContent(&quot;&quot;)
</span><span class="cx">         self.pps.startService()
</span><del>-        self.assertEquals(self.history,
-            ['one success', 'two success', 'four failure',
-            ('serviceCreator', None, 'storageService')])
</del><ins>+        self.assertEquals(
+            self.history,
+            [
+                'one success', 'two success', 'four failure',
+                ('serviceCreator', None, 'storageService')
+            ]
+        )
</ins><span class="cx">         self.assertFalse(triggerFile.exists())
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertaptesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/test/test_util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -14,11 +14,9 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from calendarserver.tap.util import directoryFromConfig, MemoryLimitService, Stepper
</del><ins>+from calendarserver.tap.util import MemoryLimitService, Stepper
</ins><span class="cx"> from twistedcaldav.util import computeProcessCount
</span><span class="cx"> from twistedcaldav.test.util import TestCase
</span><del>-from twistedcaldav.config import config
-from twistedcaldav.directory.augment import AugmentXMLDB
</del><span class="cx"> from twisted.internet.task import Clock
</span><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks
</span><span class="cx"> 
</span><span class="lines">@@ -55,21 +53,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class UtilTestCase(TestCase):
</del><span class="cx"> 
</span><del>-    def test_directoryFromConfig(self):
-        &quot;&quot;&quot;
-        Ensure augments service is on by default
-        &quot;&quot;&quot;
-        dir = directoryFromConfig(config)
-        for service in dir._recordTypes.values():
-            # all directory services belonging to the aggregate have
-            # augmentService set to AugmentXMLDB
-            if hasattr(service, &quot;augmentService&quot;):
-                self.assertTrue(isinstance(service.augmentService, AugmentXMLDB))
-
-
-
</del><span class="cx"> # Stub classes for MemoryLimitServiceTestCase
</span><span class="cx"> 
</span><span class="cx"> class StubProtocol(object):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertaputilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tap/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -28,7 +28,6 @@
</span><span class="cx"> 
</span><span class="cx"> import errno
</span><span class="cx"> import os
</span><del>-from time import sleep
</del><span class="cx"> from socket import fromfd, AF_UNIX, SOCK_STREAM, socketpair
</span><span class="cx"> import psutil
</span><span class="cx"> 
</span><span class="lines">@@ -36,6 +35,7 @@
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from txweb2.auth.basic import BasicCredentialFactory
</span><span class="cx"> from txweb2.dav import auth
</span><ins>+from txweb2.dav.util import joinURL
</ins><span class="cx"> from txweb2.http_headers import Headers
</span><span class="cx"> from txweb2.resource import Resource
</span><span class="cx"> from txweb2.static import File as FileResource
</span><span class="lines">@@ -46,32 +46,26 @@
</span><span class="cx"> from twisted.internet import reactor as _reactor
</span><span class="cx"> from twisted.internet.reactor import addSystemEventTrigger
</span><span class="cx"> from twisted.internet.tcp import Connection
</span><del>-from twisted.python.reflect import namedClass
-# from twisted.python.failure import Failure
</del><span class="cx"> 
</span><ins>+from calendarserver.push.applepush import APNSubscriptionResource
+from calendarserver.push.notifier import NotifierFactory
+from twext.enterprise.adbapi2 import ConnectionPool, ConnectionPoolConnection
+from twext.enterprise.ienterprise import ORACLE_DIALECT
+from twext.enterprise.ienterprise import POSTGRES_DIALECT
</ins><span class="cx"> from twistedcaldav.bind import doBind
</span><span class="cx"> from twistedcaldav.cache import CacheStoreNotifierFactory
</span><del>-from twistedcaldav.directory import calendaruserproxy
</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 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><del>-from twistedcaldav.directory.wiki import WikiDirectoryService
-from calendarserver.push.notifier import NotifierFactory
-from calendarserver.push.applepush import APNSubscriptionResource
</del><span class="cx"> from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
</span><span class="cx"> from twistedcaldav.resource import AuthenticationWrapper
</span><del>-from txdav.caldav.datastore.scheduling.ischedule.dkim import DKIMUtils, DomainKeyResource
-from txdav.caldav.datastore.scheduling.ischedule.resource import IScheduleInboxResource
</del><span class="cx"> from twistedcaldav.simpleresource import SimpleResource, SimpleRedirectResource
</span><span class="cx"> from twistedcaldav.timezones import TimezoneCache
</span><span class="cx"> from twistedcaldav.timezoneservice import TimezoneServiceResource
</span><span class="cx"> from twistedcaldav.timezonestdservice import TimezoneStdServiceResource
</span><del>-from twext.enterprise.ienterprise import POSTGRES_DIALECT
-from twext.enterprise.ienterprise import ORACLE_DIALECT
-from twext.enterprise.adbapi2 import ConnectionPool, ConnectionPoolConnection
</del><ins>+from txdav.caldav.datastore.scheduling.ischedule.dkim import DKIMUtils, DomainKeyResource
+from txdav.caldav.datastore.scheduling.ischedule.resource import IScheduleInboxResource
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> try:
</span><span class="lines">@@ -100,7 +94,10 @@
</span><span class="cx"> from urllib import quote
</span><span class="cx"> from twisted.python.usage import UsageError
</span><span class="cx"> 
</span><del>-
</del><ins>+from twext.who.checker import UsernamePasswordCredentialChecker
+from twext.who.checker import HTTPDigestCredentialChecker
+from twisted.cred.error import UnauthorizedLogin
+from txweb2.dav.auth import IPrincipalCredentials
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -218,7 +215,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def storeFromConfig(config, txnFactory, directoryService=None):
</del><ins>+def storeFromConfig(config, txnFactory, directoryService):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Produce an L{IDataStore} from the given configuration, transaction factory,
</span><span class="cx">     and notifier factory.
</span><span class="lines">@@ -236,17 +233,14 @@
</span><span class="cx">     if config.EnableResponseCache and config.Memcached.Pools.Default.ClientEnabled:
</span><span class="cx">         notifierFactories[&quot;cache&quot;] = CacheStoreNotifierFactory()
</span><span class="cx"> 
</span><del>-    if directoryService is None:
-        directoryService = directoryFromConfig(config)
-
</del><span class="cx">     quota = config.UserQuota
</span><span class="cx">     if quota == 0:
</span><span class="cx">         quota = None
</span><span class="cx">     if txnFactory is not None:
</span><span class="cx">         if config.EnableSSL:
</span><del>-            uri = &quot;https://%s:%s&quot; % (config.ServerHostName, config.SSLPort,)
</del><ins>+            uri = &quot;https://{config.ServerHostName}:{config.SSLPort}&quot;.format(config=config)
</ins><span class="cx">         else:
</span><del>-            uri = &quot;http://%s:%s&quot; % (config.ServerHostName, config.HTTPPort,)
</del><ins>+            uri = &quot;https://{config.ServerHostName}:{config.HTTPPort}&quot;.format(config=config)
</ins><span class="cx">         attachments_uri = uri + &quot;/calendars/__uids__/%(home)s/dropbox/%(dropbox_id)s/%(name)s&quot;
</span><span class="cx">         store = CommonSQLDataStore(
</span><span class="cx">             txnFactory, notifierFactories,
</span><span class="lines">@@ -281,96 +275,65 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def directoryFromConfig(config):
-    &quot;&quot;&quot;
-    Create an L{AggregateDirectoryService} from the given configuration.
-    &quot;&quot;&quot;
-    #
-    # Setup the Augment Service
-    #
-    if config.AugmentService.type:
-        augmentClass = namedClass(config.AugmentService.type)
-        log.info(&quot;Configuring augment service of type: {augmentClass}&quot;,
-            augmentClass=augmentClass)
-        try:
-            augmentService = augmentClass(**config.AugmentService.params)
-        except IOError:
-            log.error(&quot;Could not start augment service&quot;)
-            raise
-    else:
-        augmentService = None
</del><ins>+# MOVE2WHO -- should we move this class somewhere else?
+class PrincipalCredentialChecker(object):
+    credentialInterfaces = (IPrincipalCredentials,)
</ins><span class="cx"> 
</span><del>-    #
-    # Setup the group membership cacher
-    #
-    if config.GroupCaching.Enabled:
-        groupMembershipCache = GroupMembershipCache(
-            config.GroupCaching.MemcachedPool,
-            expireSeconds=config.GroupCaching.ExpireSeconds)
-    else:
-        groupMembershipCache = None
</del><ins>+    @inlineCallbacks
+    def requestAvatarId(self, credentials):
+        credentials = IPrincipalCredentials(credentials)
</ins><span class="cx"> 
</span><del>-    #
-    # Setup the Directory
-    #
-    directories = []
</del><ins>+        if credentials.authnPrincipal is None:
+            raise UnauthorizedLogin(
+                &quot;No such user: {user}&quot;.format(
+                    user=credentials.credentials.username
+                )
+        )
</ins><span class="cx"> 
</span><del>-    directoryClass = namedClass(config.DirectoryService.type)
-    principalResourceClass = DirectoryPrincipalProvisioningResource
</del><ins>+        # See if record is enabledForLogin
+        if not credentials.authnPrincipal.record.isLoginEnabled():
+            raise UnauthorizedLogin(
+                &quot;User not allowed to log in: {user}&quot;.format(
+                    user=credentials.credentials.username
+                )
+            )
</ins><span class="cx"> 
</span><del>-    log.info(&quot;Configuring directory service of type: {directoryType}&quot;,
-        directoryType=config.DirectoryService.type)
</del><ins>+        # Handle Kerberos as a separate behavior
+        try:
+            from twistedcaldav.authkerb import NegotiateCredentials
+        except ImportError:
+            NegotiateCredentials = None
</ins><span class="cx"> 
</span><del>-    config.DirectoryService.params.augmentService = augmentService
-    config.DirectoryService.params.groupMembershipCache = groupMembershipCache
-    baseDirectory = directoryClass(config.DirectoryService.params)
</del><ins>+        if NegotiateCredentials and isinstance(credentials.credentials,
+                                               NegotiateCredentials):
+            # If we get here with Kerberos, then authentication has already succeeded
+            returnValue(
+                (
+                    credentials.authnPrincipal.principalURL(),
+                    credentials.authzPrincipal.principalURL(),
+                    credentials.authnPrincipal,
+                    credentials.authzPrincipal,
+                )
+            )
+        else:
+            if (yield credentials.authnPrincipal.record.verifyCredentials(credentials.credentials)):
+                returnValue(
+                    (
+                        credentials.authnPrincipal.principalURL(),
+                        credentials.authzPrincipal.principalURL(),
+                        credentials.authnPrincipal,
+                        credentials.authzPrincipal,
+                    )
+                )
+            else:
+                raise UnauthorizedLogin(
+                    &quot;Incorrect credentials for user: {user}&quot;.format(
+                        user=credentials.credentials.username
+                    )
+                )
</ins><span class="cx"> 
</span><del>-    # Wait for the directory to become available
-    while not baseDirectory.isAvailable():
-        sleep(5)
</del><span class="cx"> 
</span><del>-    directories.append(baseDirectory)
</del><span class="cx"> 
</span><del>-    #
-    # Setup the Locations and Resources Service
-    #
-    if config.ResourceService.Enabled:
-        resourceClass = namedClass(config.ResourceService.type)
-
-        log.info(&quot;Configuring resource service of type: {resourceClass}&quot;,
-            resourceClass=resourceClass)
-
-        config.ResourceService.params.augmentService = augmentService
-        config.ResourceService.params.groupMembershipCache = groupMembershipCache
-        resourceDirectory = resourceClass(config.ResourceService.params)
-        resourceDirectory.realmName = baseDirectory.realmName
-        directories.append(resourceDirectory)
-
-    #
-    # Add wiki directory service
-    #
-    if config.Authentication.Wiki.Enabled:
-        wikiDirectory = WikiDirectoryService()
-        wikiDirectory.realmName = baseDirectory.realmName
-        directories.append(wikiDirectory)
-
-    directory = AggregateDirectoryService(directories, groupMembershipCache)
-
-    #
-    # Use system-wide realm on OSX
-    #
-    try:
-        import ServerFoundation
-        realmName = ServerFoundation.XSAuthenticator.defaultRealm().encode(&quot;utf-8&quot;)
-        directory.setRealm(realmName)
-    except ImportError:
-        pass
-    log.info(&quot;Setting up principal collection: {cls}&quot;, cls=principalResourceClass)
-    principalResourceClass(&quot;/principals/&quot;, directory)
-    return directory
-
-
-
</del><span class="cx"> def getRootResource(config, newStore, resources=None):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Set up directory service and resource hierarchy based on config.
</span><span class="lines">@@ -407,22 +370,26 @@
</span><span class="cx">     addressBookResourceClass = DirectoryAddressBookHomeProvisioningResource
</span><span class="cx">     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
</span><span class="cx">     apnSubscriptionResourceClass = APNSubscriptionResource
</span><ins>+    principalResourceClass = DirectoryPrincipalProvisioningResource
</ins><span class="cx"> 
</span><span class="cx">     directory = newStore.directoryService()
</span><ins>+    principalCollection = principalResourceClass(&quot;/principals/&quot;, directory)
</ins><span class="cx"> 
</span><span class="cx">     #
</span><span class="cx">     # Setup the ProxyDB Service
</span><span class="cx">     #
</span><del>-    proxydbClass = namedClass(config.ProxyDBService.type)
</del><span class="cx"> 
</span><del>-    log.info(&quot;Configuring proxydb service of type: {cls}&quot;, cls=proxydbClass)
</del><ins>+    # MOVE2WHO
+    # proxydbClass = namedClass(config.ProxyDBService.type)
</ins><span class="cx"> 
</span><del>-    try:
-        calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
-    except IOError:
-        log.error(&quot;Could not start proxydb service&quot;)
-        raise
</del><ins>+    # log.info(&quot;Configuring proxydb service of type: {cls}&quot;, cls=proxydbClass)
</ins><span class="cx"> 
</span><ins>+    # try:
+    #     calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+    # except IOError:
+    #     log.error(&quot;Could not start proxydb service&quot;)
+    #     raise
+
</ins><span class="cx">     #
</span><span class="cx">     # Configure the Site and Wrappers
</span><span class="cx">     #
</span><span class="lines">@@ -431,9 +398,11 @@
</span><span class="cx"> 
</span><span class="cx">     portal = Portal(auth.DavRealm())
</span><span class="cx"> 
</span><del>-    portal.registerChecker(directory)
</del><ins>+    portal.registerChecker(UsernamePasswordCredentialChecker(directory))
+    portal.registerChecker(HTTPDigestCredentialChecker(directory))
+    portal.registerChecker(PrincipalCredentialChecker())
</ins><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 class="lines">@@ -491,7 +460,7 @@
</span><span class="cx">     #
</span><span class="cx">     log.info(&quot;Setting up document root at: {root}&quot;, root=config.DocumentRoot)
</span><span class="cx"> 
</span><del>-    principalCollection = directory.principalCollection
</del><ins>+    # principalCollection = directory.principalCollection
</ins><span class="cx"> 
</span><span class="cx">     if config.EnableCalDAV:
</span><span class="cx">         log.info(&quot;Setting up calendar collection: {cls}&quot;, cls=calendarResourceClass)
</span><span class="lines">@@ -509,13 +478,14 @@
</span><span class="cx">             newStore,
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        directoryPath = os.path.join(config.DocumentRoot, config.DirectoryAddressBook.name)
</del><span class="cx">         if config.DirectoryAddressBook.Enabled and config.EnableSearchAddressBook:
</span><span class="cx">             log.info(&quot;Setting up directory address book: {cls}&quot;,
</span><span class="cx">                 cls=directoryBackedAddressBookResourceClass)
</span><span class="cx"> 
</span><span class="cx">             directoryBackedAddressBookCollection = directoryBackedAddressBookResourceClass(
</span><del>-                principalCollections=(principalCollection,)
</del><ins>+                principalCollections=(principalCollection,),
+                principalDirectory=directory,
+                uri=joinURL(&quot;/&quot;, config.DirectoryAddressBook.name, &quot;/&quot;)
</ins><span class="cx">             )
</span><span class="cx">             if _reactor._started:
</span><span class="cx">                 directoryBackedAddressBookCollection.provisionDirectory()
</span><span class="lines">@@ -523,6 +493,7 @@
</span><span class="cx">                 addSystemEventTrigger(&quot;after&quot;, &quot;startup&quot;, directoryBackedAddressBookCollection.provisionDirectory)
</span><span class="cx">         else:
</span><span class="cx">             # remove /directory from previous runs that may have created it
</span><ins>+            directoryPath = os.path.join(config.DocumentRoot, config.DirectoryAddressBook.name)
</ins><span class="cx">             try:
</span><span class="cx">                 FilePath(directoryPath).remove()
</span><span class="cx">                 log.info(&quot;Deleted: {path}&quot;, path=directoryPath)
</span><span class="lines">@@ -712,6 +683,7 @@
</span><span class="cx">     #
</span><span class="cx">     # Configure ancillary data
</span><span class="cx">     #
</span><ins>+    # MOVE2WHO
</ins><span class="cx">     log.info(&quot;Configuring authentication wrapper&quot;)
</span><span class="cx"> 
</span><span class="cx">     overrides = {}
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsagentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/agent.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/agent.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/agent.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,121 +18,46 @@
</span><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> A service spawned on-demand by launchd, meant to handle configuration requests
</span><del>-from Server.app.  When a request comes in on the socket specified in the launchd
-agent.plist, launchd will run &quot;caldavd -t Agent&quot; which ends up creating this
-service.  Requests are made using HTTP POSTS to /gateway, and are authenticated
-by OpenDirectory.
</del><ins>+from Server.app.  When a request comes in on the socket specified in the
+launchd agent.plist, launchd will run &quot;caldavd -t Agent&quot; which ends up creating
+this service.  Requests are made using HTTP POSTS to /gateway, and are
+authenticated by OpenDirectory.
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><ins>+__all__ = [
+    &quot;makeAgentService&quot;,
+]
+
</ins><span class="cx"> import cStringIO
</span><ins>+from plistlib import readPlistFromString, writePlistToString
</ins><span class="cx"> import socket
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tap.util import getRootResource
</span><del>-from plistlib import readPlistFromString, writePlistToString
</del><ins>+from twext.python.launchd import getLaunchDSocketFDs
+from twext.python.log import Logger
+from twext.who.checker import HTTPDigestCredentialChecker
+from twext.who.opendirectory import (
+    DirectoryService as OpenDirectoryDirectoryService,
+    NoQOPDigestCredentialFactory
+)
</ins><span class="cx"> from twisted.application.internet import StreamServerEndpointService
</span><del>-from twisted.cred.checkers import ICredentialsChecker
-from twisted.cred.credentials import IUsernameHashedPassword
-from twisted.cred.error import UnauthorizedLogin
</del><span class="cx"> from twisted.cred.portal import IRealm, Portal
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, succeed, fail
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twisted.internet.endpoints import AdoptedStreamServerEndpoint
</span><span class="cx"> from twisted.internet.protocol import Factory
</span><span class="cx"> from twisted.protocols import amp
</span><del>-from twisted.web.guard import HTTPAuthSessionWrapper, DigestCredentialFactory
</del><ins>+from twisted.web.guard import HTTPAuthSessionWrapper
</ins><span class="cx"> from twisted.web.resource import IResource, Resource, ForbiddenResource
</span><span class="cx"> from twisted.web.server import Site, NOT_DONE_YET
</span><span class="cx"> from zope.interface import implements
</span><span class="cx"> 
</span><del>-from twext.python.launchd import getLaunchDSocketFDs
-from twext.python.log import Logger
</del><ins>+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryServiceChecker:
-    &quot;&quot;&quot;
-    A checker that knows how to ask OpenDirectory to authenticate via Digest
-    &quot;&quot;&quot;
-    implements(ICredentialsChecker)
-
-    credentialInterfaces = (IUsernameHashedPassword,)
-
-    from calendarserver.platform.darwin.od import opendirectory
-    directoryModule = opendirectory
-
-    def __init__(self, node):
-        &quot;&quot;&quot;
-        @param node: the name of the OpenDirectory node to use, e.g. /Local/Default
-        &quot;&quot;&quot;
-        self.node = node
-        self.directory = self.directoryModule.odInit(node)
-
-
-    def requestAvatarId(self, credentials):
-        record = self.directoryModule.getUserRecord(self.directory, credentials.username)
-
-        if record is not None:
-            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 as e:
-                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.node, credentials.username, e, credentials.fields)
-                )
-                return fail(UnauthorizedLogin())
-
-            try:
-                if self.directoryModule.authenticateUserDigest(self.directory,
-                    self.node,
-                    credentials.username,
-                    challenge,
-                    response,
-                    credentials.method
-                ):
-                    return succeed(credentials.username)
-                else:
-                    log.error(&quot;Failed digest auth with response: %s&quot; % (response,))
-                    return fail(UnauthorizedLogin())
-            except Exception as e:
-                log.error(
-                    &quot;OpenDirectory error while performing digest authentication for user %s: %s&quot;
-                    % (credentials.username, e)
-                )
-                return fail(UnauthorizedLogin())
-
-        else:
-            return fail(UnauthorizedLogin())
-
-
-
-class CustomDigestCredentialFactory(DigestCredentialFactory):
-    &quot;&quot;&quot;
-    DigestCredentialFactory without qop, to interop with OD.
-    &quot;&quot;&quot;
-
-    def getChallenge(self, address):
-        result = DigestCredentialFactory.getChallenge(self, address)
-        del result[&quot;qop&quot;]
-        return result
-
-
-
</del><span class="cx"> class AgentRealm(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Only allow a specified list of avatar IDs to access the site
</span><span class="lines">@@ -161,7 +86,8 @@
</span><span class="cx"> 
</span><span class="cx"> class AgentGatewayResource(Resource):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    The gateway resource which forwards incoming requests through gateway.Runner.
</del><ins>+    The gateway resource which forwards incoming requests through
+    gateway.Runner.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     isLeaf = True
</span><span class="cx"> 
</span><span class="lines">@@ -202,10 +128,10 @@
</span><span class="cx">             tbString = tbStringIO.getvalue()
</span><span class="cx">             tbStringIO.close()
</span><span class="cx">             error = {
</span><del>-                &quot;Error&quot; : message,
-                &quot;Traceback&quot; : tbString,
</del><ins>+                &quot;Error&quot;: message,
+                &quot;Traceback&quot;: tbString,
</ins><span class="cx">             }
</span><del>-            log.error(&quot;command failed %s&quot; % (failure,))
</del><ins>+            log.error(&quot;command failed {error}&quot;, error=failure)
</ins><span class="cx">             request.write(writePlistToString(error))
</span><span class="cx">             request.finish()
</span><span class="cx"> 
</span><span class="lines">@@ -213,8 +139,10 @@
</span><span class="cx">         body = request.content.read()
</span><span class="cx">         command = readPlistFromString(body)
</span><span class="cx">         output = cStringIO.StringIO()
</span><del>-        runner = Runner(self.davRootResource, self.directory, self.store,
-            [command], output=output)
</del><ins>+        runner = Runner(
+            self.davRootResource, self.directory, self.store,
+            [command], output=output
+        )
</ins><span class="cx">         d = runner.run()
</span><span class="cx">         d.addCallback(onSuccess, output)
</span><span class="cx">         d.addErrback(onError)
</span><span class="lines">@@ -246,16 +174,26 @@
</span><span class="cx">         log.warn(&quot;Agent inactive; shutting down&quot;)
</span><span class="cx">         reactor.stop()
</span><span class="cx"> 
</span><del>-    inactivityDetector = InactivityDetector(reactor,
-        config.AgentInactivityTimeoutSeconds, becameInactive)
</del><ins>+    inactivityDetector = InactivityDetector(
+        reactor, config.AgentInactivityTimeoutSeconds, becameInactive
+    )
</ins><span class="cx">     root = Resource()
</span><del>-    root.putChild(&quot;gateway&quot;, AgentGatewayResource(store,
-        davRootResource, directory, inactivityDetector))
</del><ins>+    root.putChild(
+        &quot;gateway&quot;,
+        AgentGatewayResource(
+            store, davRootResource, directory, inactivityDetector
+        )
+    )
</ins><span class="cx"> 
</span><del>-    realmName = &quot;/Local/Default&quot;
-    portal = Portal(AgentRealm(root, [&quot;com.apple.calendarserver&quot;]),
-        [DirectoryServiceChecker(realmName)])
-    credentialFactory = CustomDigestCredentialFactory(&quot;md5&quot;, realmName)
</del><ins>+    directory = OpenDirectoryDirectoryService(&quot;/Local/Default&quot;)
+
+    portal = Portal(
+        AgentRealm(root, [&quot;com.apple.calendarserver&quot;]),
+        [HTTPDigestCredentialChecker(directory)]
+    )
+    credentialFactory = NoQOPDigestCredentialFactory(
+        &quot;md5&quot;, &quot;CalendarServer Agent Realm&quot;
+    )
</ins><span class="cx">     wrapper = HTTPAuthSessionWrapper(portal, [credentialFactory])
</span><span class="cx"> 
</span><span class="cx">     site = Site(wrapper)
</span><span class="lines">@@ -283,8 +221,10 @@
</span><span class="cx">         self._becameInactive = becameInactive
</span><span class="cx"> 
</span><span class="cx">         if self._timeoutSeconds &gt; 0:
</span><del>-            self._delayedCall = self._reactor.callLater(self._timeoutSeconds,
-                self._inactivityThresholdReached)
</del><ins>+            self._delayedCall = self._reactor.callLater(
+                self._timeoutSeconds,
+                self._inactivityThresholdReached
+            )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _inactivityThresholdReached(self):
</span><span class="lines">@@ -304,8 +244,10 @@
</span><span class="cx">             if self._delayedCall.active():
</span><span class="cx">                 self._delayedCall.reset(self._timeoutSeconds)
</span><span class="cx">             else:
</span><del>-                self._delayedCall = self._reactor.callLater(self._timeoutSeconds,
-                    self._inactivityThresholdReached)
</del><ins>+                self._delayedCall = self._reactor.callLater(
+                    self._timeoutSeconds,
+                    self._inactivityThresholdReached
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def stop(self):
</span><span class="lines">@@ -361,17 +303,17 @@
</span><span class="cx">         command = readPlistFromString(command)
</span><span class="cx">         output = cStringIO.StringIO()
</span><span class="cx">         from calendarserver.tools.gateway import Runner
</span><del>-        runner = Runner(self.davRootResource, self.directory, self.store,
-            [command], output=output)
</del><ins>+        runner = Runner(
+            self.davRootResource, self.directory, self.store,
+            [command], output=output
+        )
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             yield runner.run()
</span><span class="cx">             result = output.getvalue()
</span><span class="cx">             output.close()
</span><span class="cx">         except Exception as e:
</span><del>-            error = {
-                &quot;Error&quot; : str(e),
-            }
</del><ins>+            error = {&quot;Error&quot;: str(e)}
</ins><span class="cx">             result = writePlistToString(error)
</span><span class="cx"> 
</span><span class="cx">         output.close()
</span><span class="lines">@@ -396,8 +338,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def buildProtocol(self, addr):
</span><del>-        return GatewayAMPProtocol(self.store, self.davRootResource,
-            self.directory)
</del><ins>+        return GatewayAMPProtocol(
+            self.store, self.davRootResource, self.directory
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -406,7 +349,8 @@
</span><span class="cx"> #
</span><span class="cx"> 
</span><span class="cx"> command = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
</span><del>-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
</del><ins>+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot;
+ &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
</ins><span class="cx"> &lt;plist version=&quot;1.0&quot;&gt;
</span><span class="cx"> &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="lines">@@ -414,6 +358,7 @@
</span><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;&quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def getList():
</span><span class="cx">     # For the sample client, below:
</span><span class="cx">     from twisted.internet import reactor
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolscalverifypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/calverify.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/calverify.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/calverify.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -47,41 +47,39 @@
</span><span class="cx"> import traceback
</span><span class="cx"> import uuid
</span><span class="cx"> 
</span><ins>+from calendarserver.tools import tables
+from calendarserver.tools.cmdline import utilityMain, WorkerService
+from pycalendar.datetime import DateTime
+from pycalendar.exceptions import ErrorBase
</ins><span class="cx"> from pycalendar.icalendar import definitions
</span><span class="cx"> from pycalendar.icalendar.calendar import Calendar
</span><del>-from pycalendar.datetime import DateTime
-from pycalendar.exceptions import ErrorBase
</del><span class="cx"> from pycalendar.period import Period
</span><span class="cx"> from pycalendar.timezone import Timezone
</span><del>-
</del><ins>+from twext.enterprise.dal.syntax import Select, Parameter, Count
+from twext.python.log import Logger
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.python import usage
</span><span class="cx"> from twisted.python.usage import Options
</span><del>-
-from twext.python.log import Logger
-from twext.enterprise.dal.syntax import Select, Parameter, Count
-
</del><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.dateops import pyCalendarTodatetime
</span><del>-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.ical import Component, ignoredComponents, \
</del><ins>+from twistedcaldav.ical import (
+    Component, ignoredComponents,
</ins><span class="cx">     InvalidICalendarDataError, Property, PERUSER_COMPONENT
</span><del>-from txdav.caldav.datastore.scheduling.itip import iTipGenerator
</del><ins>+)
</ins><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
</span><del>-from twistedcaldav.util import normalizationLookup
-
-from txdav.caldav.icalendarstore import ComponentUpdateState
</del><span class="cx"> from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
</span><span class="cx"> from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><ins>+from txdav.caldav.datastore.scheduling.itip import iTipGenerator
</ins><span class="cx"> from txdav.caldav.datastore.sql import CalendarStoreFeatures
</span><ins>+from txdav.caldav.datastore.util import normalizationLookup
+from txdav.caldav.icalendarstore import ComponentUpdateState
</ins><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
</span><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError
</span><ins>+from txdav.who.idirectory import (
+    RecordType as CalRecordType, AutoScheduleMode
+)
</ins><span class="cx"> 
</span><del>-from calendarserver.tools.cmdline import utilityMain, WorkerService
</del><span class="cx"> 
</span><del>-from calendarserver.tools import tables
-from calendarserver.tools.util import getDirectory
-
</del><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -396,7 +394,7 @@
</span><span class="cx">         self.output = output
</span><span class="cx">         self.reactor = reactor
</span><span class="cx">         self.config = config
</span><del>-        self._directory = None
</del><ins>+        self._directory = store.directoryService()
</ins><span class="cx"> 
</span><span class="cx">         self.cuaCache = {}
</span><span class="cx"> 
</span><span class="lines">@@ -427,11 +425,8 @@
</span><span class="cx"> 
</span><span class="cx">     def directoryService(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Get an appropriate directory service for this L{CalVerifyService}'s
-        configuration, creating one first if necessary.
</del><ins>+        Return the directory service
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        if self._directory is None:
-            self._directory = getDirectory(self.config) #directoryFromConfig(self.config)
</del><span class="cx">         return self._directory
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -970,14 +965,14 @@
</span><span class="cx">                 )).ljust(80))
</span><span class="cx">                 self.output.flush()
</span><span class="cx"> 
</span><del>-            record = self.directoryService().recordWithGUID(uid)
</del><ins>+            record = yield self.directoryService().recordWithUID(uid)
</ins><span class="cx">             if record is None:
</span><span class="cx">                 contents = yield self.countHomeContents(uid)
</span><span class="cx">                 missing.append((uid, contents,))
</span><span class="cx">             elif not record.thisServer():
</span><span class="cx">                 contents = yield self.countHomeContents(uid)
</span><span class="cx">                 wrong_server.append((uid, contents,))
</span><del>-            elif not record.enabledForCalendaring:
</del><ins>+            elif not record.hasCalendars:
</ins><span class="cx">                 contents = yield self.countHomeContents(uid)
</span><span class="cx">                 disabled.append((uid, contents,))
</span><span class="cx"> 
</span><span class="lines">@@ -1008,7 +1003,7 @@
</span><span class="cx">         table = tables.Table()
</span><span class="cx">         table.addHeader((&quot;Owner UID&quot;, &quot;Calendar Objects&quot;))
</span><span class="cx">         for uid, count in sorted(wrong_server, key=lambda x: x[0]):
</span><del>-            record = self.directoryService().recordWithGUID(uid)
</del><ins>+            record = yield self.directoryService().recordWithUID(uid)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (record.recordType if record else &quot;-&quot;, record.shortNames[0] if record else &quot;-&quot;, uid,),
</span><span class="cx">                 count,
</span><span class="lines">@@ -1022,7 +1017,7 @@
</span><span class="cx">         table = tables.Table()
</span><span class="cx">         table.addHeader((&quot;Owner UID&quot;, &quot;Calendar Objects&quot;))
</span><span class="cx">         for uid, count in sorted(disabled, key=lambda x: x[0]):
</span><del>-            record = self.directoryService().recordWithGUID(uid)
</del><ins>+            record = yield self.directoryService().recordWithUID(uid)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (record.recordType if record else &quot;-&quot;, record.shortNames[0] if record else &quot;-&quot;, uid,),
</span><span class="cx">                 count,
</span><span class="lines">@@ -1152,7 +1147,7 @@
</span><span class="cx">         table.addHeader((&quot;Owner&quot;, &quot;Event UID&quot;, &quot;RID&quot;, &quot;Problem&quot;,))
</span><span class="cx">         for item in sorted(results_bad, key=lambda x: (x[0], x[1])):
</span><span class="cx">             owner, uid, resid, message = item
</span><del>-            owner_record = self.directoryService().recordWithGUID(owner)
</del><ins>+            owner_record = yield self.directoryService().recordWithUID(owner)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (owner_record.recordType if owner_record else &quot;-&quot;, owner_record.shortNames[0] if owner_record else &quot;-&quot;, owner,),
</span><span class="cx">                 uid,
</span><span class="lines">@@ -1199,7 +1194,7 @@
</span><span class="cx">                 component.validOrganizerForScheduling(doFix=False)
</span><span class="cx">                 if component.hasDuplicateAlarms(doFix=False):
</span><span class="cx">                     raise InvalidICalendarDataError(&quot;Duplicate VALARMS&quot;)
</span><del>-            self.noPrincipalPathCUAddresses(component, doFix=False)
</del><ins>+            yield self.noPrincipalPathCUAddresses(component, doFix=False)
</ins><span class="cx">             if self.options[&quot;ical&quot;]:
</span><span class="cx">                 self.attendeesWithoutOrganizer(component, doFix=False)
</span><span class="cx"> 
</span><span class="lines">@@ -1220,15 +1215,17 @@
</span><span class="cx">         returnValue((result, message,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def noPrincipalPathCUAddresses(self, component, doFix):
</span><span class="cx"> 
</span><del>-        def lookupFunction(cuaddr, principalFunction, conf):
</del><ins>+        @inlineCallbacks
+        def lookupFunction(cuaddr, recordFunction, conf):
</ins><span class="cx"> 
</span><span class="cx">             # Return cached results, if any.
</span><span class="cx">             if cuaddr in self.cuaCache:
</span><del>-                return self.cuaCache[cuaddr]
</del><ins>+                returnValue(self.cuaCache[cuaddr])
</ins><span class="cx"> 
</span><del>-            result = normalizationLookup(cuaddr, principalFunction, conf)
</del><ins>+            result = yield normalizationLookup(cuaddr, recordFunction, conf)
</ins><span class="cx">             _ignore_name, guid, _ignore_cuaddrs = result
</span><span class="cx">             if guid is None:
</span><span class="cx">                 if cuaddr.find(&quot;__uids__&quot;) != -1:
</span><span class="lines">@@ -1237,7 +1234,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Cache the result
</span><span class="cx">             self.cuaCache[cuaddr] = result
</span><del>-            return result
</del><ins>+            returnValue(result)
</ins><span class="cx"> 
</span><span class="cx">         for subcomponent in component.subcomponents():
</span><span class="cx">             if subcomponent.name() in ignoredComponents:
</span><span class="lines">@@ -1249,13 +1246,13 @@
</span><span class="cx">                 # http(s) principals need to be converted to urn:uuid
</span><span class="cx">                 if cuaddr.startswith(&quot;http&quot;):
</span><span class="cx">                     if doFix:
</span><del>-                        component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                        yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">                     else:
</span><span class="cx">                         raise InvalidICalendarDataError(&quot;iCalendar ORGANIZER starts with 'http(s)'&quot;)
</span><span class="cx">                 elif cuaddr.startswith(&quot;mailto:&quot;):
</span><del>-                    if lookupFunction(cuaddr, self.directoryService().principalForCalendarUserAddress, self.config)[1] is not None:
</del><ins>+                    if (yield lookupFunction(cuaddr, self.directoryService().recordWithCalendarUserAddress, self.config))[1] is not None:
</ins><span class="cx">                         if doFix:
</span><del>-                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                            yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">                         else:
</span><span class="cx">                             raise InvalidICalendarDataError(&quot;iCalendar ORGANIZER starts with 'mailto:' and record exists&quot;)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1263,7 +1260,7 @@
</span><span class="cx">                         if doFix:
</span><span class="cx">                             # Add back in mailto: then re-normalize to urn:uuid if possible
</span><span class="cx">                             organizer.setValue(&quot;mailto:%s&quot; % (cuaddr,))
</span><del>-                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                            yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx"> 
</span><span class="cx">                             # Remove any SCHEDULE-AGENT=NONE
</span><span class="cx">                             if organizer.parameterValue(&quot;SCHEDULE-AGENT&quot;, &quot;SERVER&quot;) == &quot;NONE&quot;:
</span><span class="lines">@@ -1286,13 +1283,13 @@
</span><span class="cx">                 # http(s) principals need to be converted to urn:uuid
</span><span class="cx">                 if cuaddr.startswith(&quot;http&quot;):
</span><span class="cx">                     if doFix:
</span><del>-                        component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                        yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">                     else:
</span><span class="cx">                         raise InvalidICalendarDataError(&quot;iCalendar ATTENDEE starts with 'http(s)'&quot;)
</span><span class="cx">                 elif cuaddr.startswith(&quot;mailto:&quot;):
</span><del>-                    if lookupFunction(cuaddr, self.directoryService().principalForCalendarUserAddress, self.config)[1] is not None:
</del><ins>+                    if (yield lookupFunction(cuaddr, self.directoryService().recordWithCalendarUserAddress, self.config))[1] is not None:
</ins><span class="cx">                         if doFix:
</span><del>-                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                            yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">                         else:
</span><span class="cx">                             raise InvalidICalendarDataError(&quot;iCalendar ATTENDEE starts with 'mailto:' and record exists&quot;)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1300,7 +1297,7 @@
</span><span class="cx">                         if doFix:
</span><span class="cx">                             # Add back in mailto: then re-normalize to urn:uuid if possible
</span><span class="cx">                             attendee.setValue(&quot;mailto:%s&quot; % (cuaddr,))
</span><del>-                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
</del><ins>+                            yield component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">                         else:
</span><span class="cx">                             raise InvalidICalendarDataError(&quot;iCalendar ATTENDEE missing mailto:&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -1352,7 +1349,7 @@
</span><span class="cx">                 component.validCalendarForCalDAV(methodAllowed=isinbox)
</span><span class="cx">                 component.validOrganizerForScheduling(doFix=True)
</span><span class="cx">                 component.hasDuplicateAlarms(doFix=True)
</span><del>-            self.noPrincipalPathCUAddresses(component, doFix=True)
</del><ins>+            yield self.noPrincipalPathCUAddresses(component, doFix=True)
</ins><span class="cx">             if self.options[&quot;ical&quot;]:
</span><span class="cx">                 self.attendeesWithoutOrganizer(component, doFix=True)
</span><span class="cx">         except ValueError:
</span><span class="lines">@@ -1460,7 +1457,7 @@
</span><span class="cx">         self.attended = []
</span><span class="cx">         self.attended_byuid = collections.defaultdict(list)
</span><span class="cx">         self.matched_attendee_to_organizer = collections.defaultdict(set)
</span><del>-        skipped, inboxes = self.buildResourceInfo(rows)
</del><ins>+        skipped, inboxes = yield self.buildResourceInfo(rows)
</ins><span class="cx"> 
</span><span class="cx">         self.logResult(&quot;Number of organizer events to process&quot;, len(self.organized), self.total)
</span><span class="cx">         self.logResult(&quot;Number of attendee events to process&quot;, len(self.attended), self.total)
</span><span class="lines">@@ -1488,6 +1485,7 @@
</span><span class="cx">         self.printSummary()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def buildResourceInfo(self, rows, onlyOrganizer=False, onlyAttendee=False):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         For each resource, determine whether it is an organizer or attendee event, and also
</span><span class="lines">@@ -1506,7 +1504,7 @@
</span><span class="cx">         for owner, resid, uid, calname, md5, organizer, created, modified in rows:
</span><span class="cx"> 
</span><span class="cx">             # Skip owners not enabled for calendaring
</span><del>-            if not self.testForCalendaringUUID(owner):
</del><ins>+            if not (yield self.testForCalendaringUUID(owner)):
</ins><span class="cx">                 skipped += 1
</span><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="lines">@@ -1530,9 +1528,10 @@
</span><span class="cx">                     self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
</span><span class="cx">                     self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
</span><span class="cx"> 
</span><del>-        return skipped, inboxes
</del><ins>+        returnValue((skipped, inboxes))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def testForCalendaringUUID(self, uuid):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Determine if the specified directory UUID is valid for calendaring. Keep a cache of
</span><span class="lines">@@ -1545,9 +1544,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if uuid not in self.validForCalendaringUUIDs:
</span><del>-            record = self.directoryService().recordWithGUID(uuid)
-            self.validForCalendaringUUIDs[uuid] = record is not None and record.enabledForCalendaring and record.thisServer()
-        return self.validForCalendaringUUIDs[uuid]
</del><ins>+            record = yield self.directoryService().recordWithUID(uuid)
+            self.validForCalendaringUUIDs[uuid] = record is not None and record.hasCalendars and record.thisServer()
+        returnValue(self.validForCalendaringUUIDs[uuid])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1622,7 +1621,7 @@
</span><span class="cx">                 self.matched_attendee_to_organizer[uid].add(organizerAttendee)
</span><span class="cx"> 
</span><span class="cx">                 # Skip attendees not enabled for calendaring
</span><del>-                if not self.testForCalendaringUUID(organizerAttendee):
</del><ins>+                if not (yield self.testForCalendaringUUID(organizerAttendee)):
</ins><span class="cx">                     continue
</span><span class="cx"> 
</span><span class="cx">                 # Double check the missing attendee situation in case we missed it during the original query
</span><span class="lines">@@ -1699,8 +1698,8 @@
</span><span class="cx">         results_missing.sort()
</span><span class="cx">         for item in results_missing:
</span><span class="cx">             uid, resid, organizer, attendee, created, modified = item
</span><del>-            organizer_record = self.directoryService().recordWithGUID(organizer)
-            attendee_record = self.directoryService().recordWithGUID(attendee)
</del><ins>+            organizer_record = yield self.directoryService().recordWithUID(organizer)
+            attendee_record = yield self.directoryService().recordWithUID(attendee)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (organizer_record.recordType if organizer_record else &quot;-&quot;, organizer_record.shortNames[0] if organizer_record else &quot;-&quot;, organizer,),
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (attendee_record.recordType if attendee_record else &quot;-&quot;, attendee_record.shortNames[0] if attendee_record else &quot;-&quot;, attendee,),
</span><span class="lines">@@ -1721,8 +1720,8 @@
</span><span class="cx">         results_mismatch.sort()
</span><span class="cx">         for item in results_mismatch:
</span><span class="cx">             uid, org_resid, organizer, org_created, org_modified, attendee, att_created, att_modified = item
</span><del>-            organizer_record = self.directoryService().recordWithGUID(organizer)
-            attendee_record = self.directoryService().recordWithGUID(attendee)
</del><ins>+            organizer_record = yield self.directoryService().recordWithUID(organizer)
+            attendee_record = yield self.directoryService().recordWithUID(attendee)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (organizer_record.recordType if organizer_record else &quot;-&quot;, organizer_record.shortNames[0] if organizer_record else &quot;-&quot;, organizer,),
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (attendee_record.recordType if attendee_record else &quot;-&quot;, attendee_record.shortNames[0] if attendee_record else &quot;-&quot;, attendee,),
</span><span class="lines">@@ -1789,14 +1788,14 @@
</span><span class="cx">             organizer = organizer[9:]
</span><span class="cx"> 
</span><span class="cx">             # Skip organizers not enabled for calendaring
</span><del>-            if not self.testForCalendaringUUID(organizer):
</del><ins>+            if not (yield self.testForCalendaringUUID(organizer)):
</ins><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="cx">             # Double check the missing attendee situation in case we missed it during the original query
</span><span class="cx">             if uid not in self.organized_byuid:
</span><span class="cx">                 # Try to reload the organizer info data
</span><span class="cx">                 rows = yield self.getAllResourceInfoWithUID(uid)
</span><del>-                self.buildResourceInfo(rows, onlyOrganizer=True)
</del><ins>+                yield self.buildResourceInfo(rows, onlyOrganizer=True)
</ins><span class="cx"> 
</span><span class="cx">                 #if uid in self.organized_byuid:
</span><span class="cx">                 #    print(&quot;Reloaded missing organizer data: %s&quot; % (uid,))
</span><span class="lines">@@ -1849,9 +1848,9 @@
</span><span class="cx">             uid, attendee, organizer, resid, created, modified = item
</span><span class="cx">             unique_set.add(uid)
</span><span class="cx">             if organizer:
</span><del>-                organizerRecord = self.directoryService().recordWithGUID(organizer)
</del><ins>+                organizerRecord = yield self.directoryService().recordWithUID(organizer)
</ins><span class="cx">                 organizer = &quot;%s/%s (%s)&quot; % (organizerRecord.recordType if organizerRecord else &quot;-&quot;, organizerRecord.shortNames[0] if organizerRecord else &quot;-&quot;, organizer,)
</span><del>-            attendeeRecord = self.directoryService().recordWithGUID(attendee)
</del><ins>+            attendeeRecord = yield self.directoryService().recordWithUID(attendee)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 organizer,
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (attendeeRecord.recordType if attendeeRecord else &quot;-&quot;, attendeeRecord.shortNames[0] if attendeeRecord else &quot;-&quot;, attendee,),
</span><span class="lines">@@ -1874,9 +1873,9 @@
</span><span class="cx">         for item in mismatched:
</span><span class="cx">             uid, attendee, organizer, resid, att_created, att_modified = item
</span><span class="cx">             if organizer:
</span><del>-                organizerRecord = self.directoryService().recordWithGUID(organizer)
</del><ins>+                organizerRecord = yield self.directoryService().recordWithUID(organizer)
</ins><span class="cx">                 organizer = &quot;%s/%s (%s)&quot; % (organizerRecord.recordType if organizerRecord else &quot;-&quot;, organizerRecord.shortNames[0] if organizerRecord else &quot;-&quot;, organizer,)
</span><del>-            attendeeRecord = self.directoryService().recordWithGUID(attendee)
</del><ins>+            attendeeRecord = yield self.directoryService().recordWithUID(attendee)
</ins><span class="cx">             table.addRow((
</span><span class="cx">                 organizer,
</span><span class="cx">                 &quot;%s/%s (%s)&quot; % (attendeeRecord.recordType if attendeeRecord else &quot;-&quot;, attendeeRecord.shortNames[0] if attendeeRecord else &quot;-&quot;, attendee,),
</span><span class="lines">@@ -1980,8 +1979,9 @@
</span><span class="cx">             self.txn = self.store.newTransaction()
</span><span class="cx"> 
</span><span class="cx">             # Need to know whether the attendee is a location or resource with auto-accept set
</span><del>-            record = self.directoryService().recordWithGUID(attendee)
-            if record.autoSchedule:
</del><ins>+            record = yield self.directoryService().recordWithUID(attendee)
+            autoScheduleMode = getattr(record, &quot;autoScheduleMode&quot;, None)
+            if autoScheduleMode not in (None, AutoScheduleMode.none):
</ins><span class="cx">                 # Log details about the event so we can have a human manually process
</span><span class="cx">                 self.fixedAutoAccepts.append(details)
</span><span class="cx"> 
</span><span class="lines">@@ -2159,8 +2159,8 @@
</span><span class="cx">             self.txn = None
</span><span class="cx">             uuids = []
</span><span class="cx">             for uuid in sorted(homes):
</span><del>-                record = self.directoryService().recordWithGUID(uuid)
-                if record is not None and record.recordType in (DirectoryService.recordType_locations, DirectoryService.recordType_resources):
</del><ins>+                record = yield self.directoryService().recordWithUID(uuid)
+                if record is not None and record.recordType in (CalRecordType.location, CalRecordType.resource):
</ins><span class="cx">                     uuids.append(uuid)
</span><span class="cx">         else:
</span><span class="cx">             uuids = [self.options[&quot;uuid&quot;], ]
</span><span class="lines">@@ -2172,14 +2172,14 @@
</span><span class="cx">             self.total = 0
</span><span class="cx">             count += 1
</span><span class="cx"> 
</span><del>-            record = self.directoryService().recordWithGUID(uuid)
</del><ins>+            record = yield self.directoryService().recordWithUID(uuid)
</ins><span class="cx">             if record is None:
</span><span class="cx">                 continue
</span><del>-            if not record.thisServer() or not record.enabledForCalendaring:
</del><ins>+            if not record.thisServer() or not record.hasCalendars:
</ins><span class="cx">                 continue
</span><span class="cx"> 
</span><del>-            rname = record.fullName
-            auto = record.autoSchedule
</del><ins>+            rname = record.displayName
+            autoScheduleMode = getattr(record, &quot;autoSchedule&quot;, AutoScheduleMode.none)
</ins><span class="cx"> 
</span><span class="cx">             if len(uuids) &gt; 1 and not self.options[&quot;summary&quot;]:
</span><span class="cx">                 self.output.write(&quot;\n\n-----------------------------\n&quot;)
</span><span class="lines">@@ -2205,7 +2205,7 @@
</span><span class="cx">             if not self.options[&quot;summary&quot;]:
</span><span class="cx">                 self.logResult(&quot;UUID to process&quot;, uuid)
</span><span class="cx">                 self.logResult(&quot;Record name&quot;, rname)
</span><del>-                self.logResult(&quot;Auto-schedule&quot;, &quot;True&quot; if auto else &quot;False&quot;)
</del><ins>+                self.logResult(&quot;Auto-schedule-mode&quot;, autoScheduleMode.description)
</ins><span class="cx">                 self.addSummaryBreak()
</span><span class="cx">                 self.logResult(&quot;Number of events to process&quot;, self.total)
</span><span class="cx"> 
</span><span class="lines">@@ -2216,7 +2216,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 doubled = False
</span><span class="cx"> 
</span><del>-            self.uuid_details.append(UUIDDetails(uuid, rname, auto, doubled))
</del><ins>+            self.uuid_details.append(UUIDDetails(uuid, rname, autoScheduleMode, doubled))
</ins><span class="cx"> 
</span><span class="cx">             if not self.options[&quot;summary&quot;]:
</span><span class="cx">                 self.printSummary()
</span><span class="lines">@@ -2234,7 +2234,7 @@
</span><span class="cx">                 table.addRow((
</span><span class="cx">                     item.uuid,
</span><span class="cx">                     item.rname,
</span><del>-                    item.auto,
</del><ins>+                    item.autoScheduleMode,
</ins><span class="cx">                     item.doubled,
</span><span class="cx">                 ))
</span><span class="cx">                 doubled += 1
</span><span class="lines">@@ -2468,8 +2468,8 @@
</span><span class="cx">             if self.options[&quot;verbose&quot;]:
</span><span class="cx">                 self.output.write(&quot;%d uuids to check\n&quot; % (len(homes,)))
</span><span class="cx">             for uuid in sorted(homes):
</span><del>-                record = self.directoryService().recordWithGUID(uuid)
-                if record is not None and record.recordType in (DirectoryService.recordType_locations, DirectoryService.recordType_resources):
</del><ins>+                record = yield self.directoryService().recordWithUID(uuid)
+                if record is not None and record.recordType in (CalRecordType.location, CalRecordType.resource):
</ins><span class="cx">                     uuids.append(uuid)
</span><span class="cx">         else:
</span><span class="cx">             uuids = [self.options[&quot;uuid&quot;], ]
</span><span class="lines">@@ -2483,13 +2483,13 @@
</span><span class="cx">             self.total = 0
</span><span class="cx">             count += 1
</span><span class="cx"> 
</span><del>-            record = self.directoryService().recordWithGUID(uuid)
</del><ins>+            record = yield self.directoryService().recordWithUID(uuid)
</ins><span class="cx">             if record is None:
</span><span class="cx">                 continue
</span><del>-            if not record.thisServer() or not record.enabledForCalendaring:
</del><ins>+            if not record.thisServer() or not record.hasCalendars:
</ins><span class="cx">                 continue
</span><span class="cx"> 
</span><del>-            rname = record.fullName
</del><ins>+            rname = record.displayName
</ins><span class="cx"> 
</span><span class="cx">             if len(uuids) &gt; 1 and not self.options[&quot;summary&quot;]:
</span><span class="cx">                 self.output.write(&quot;\n\n-----------------------------\n&quot;)
</span><span class="lines">@@ -2602,9 +2602,10 @@
</span><span class="cx">                 if self.options[&quot;no-organizer&quot;]:
</span><span class="cx">                     fail = True
</span><span class="cx">             else:
</span><del>-                principal = self.directoryService().principalForCalendarUserAddress(organizer)
</del><ins>+                principal = yield self.directoryService().recordWithCalendarUserAddress(organizer)
+                # FIXME: Why the mix of records and principals here?
</ins><span class="cx">                 if principal is None and organizer.startswith(&quot;urn:uuid:&quot;):
</span><del>-                    principal = self.directoryService().principalCollection.principalForUID(organizer[9:])
</del><ins>+                    principal = yield self.directoryService().principalCollection.principalForUID(organizer[9:])
</ins><span class="cx">                 if principal is None:
</span><span class="cx">                     if self.options[&quot;invalid-organizer&quot;]:
</span><span class="cx">                         fail = True
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsexportpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/export.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/export.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/export.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -41,7 +41,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.python.text import wordWrap
</span><span class="cx"> from twisted.python.usage import Options, UsageError
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twistedcaldav.ical import Component
</span><span class="lines">@@ -76,6 +76,7 @@
</span><span class="cx">     )
</span><span class="cx"> )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class ExportOptions(Options):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Command-line options for 'calendarserver_export'
</span><span class="lines">@@ -188,7 +189,7 @@
</span><span class="cx">         Enumerate all calendars based on the directory record and/or calendars
</span><span class="cx">         for this calendar home.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        uid = self.getHomeUID(exportService)
</del><ins>+        uid = yield self.getHomeUID(exportService)
</ins><span class="cx">         home = yield txn.calendarHomeWithUID(uid, True)
</span><span class="cx">         result = []
</span><span class="cx">         if self.collections:
</span><span class="lines">@@ -218,7 +219,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getHomeUID(self, exportService):
</span><del>-        return self.uid
</del><ins>+        return succeed(self.uid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -244,13 +245,17 @@
</span><span class="cx">         self.shortName = shortName
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def getHomeUID(self, exportService):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Retrieve the home UID.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         directory = exportService.directoryService()
</span><del>-        record = directory.recordWithShortName(self.recordType, self.shortName)
-        return record.uid
</del><ins>+        record = yield directory.recordWithShortName(
+            directory.oldNameToRecordType(self.recordType),
+            self.shortName
+        )
+        returnValue(record.uid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsgatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/gateway.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/gateway.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/gateway.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,31 +19,55 @@
</span><span class="cx"> 
</span><span class="cx"> from getopt import getopt, GetoptError
</span><span class="cx"> import os
</span><ins>+from plistlib import readPlistFromString, writePlistToString
</ins><span class="cx"> import sys
</span><span class="cx"> import xml
</span><span class="cx"> 
</span><del>-from plistlib import readPlistFromString, writePlistToString
-
-from twisted.internet.defer import inlineCallbacks, succeed
-from twistedcaldav.directory.directory import DirectoryError
-from txdav.xml import element as davxml
-
-from calendarserver.tools.util import (
-    principalForPrincipalID, proxySubprincipal, addProxy, removeProxy,
-    ProxyError, ProxyWarning, autoDisableMemcached
-)
</del><ins>+from calendarserver.tools.cmdline import utilityMain
+from calendarserver.tools.config import WRITABLE_CONFIG_KEYS, setKeyPath, getKeyPath, flattenDictionary, WritableConfig
</ins><span class="cx"> from calendarserver.tools.principals import (
</span><del>-    getProxies, setProxies, updateRecord, attrMap
</del><ins>+    getProxies, setProxies
</ins><span class="cx"> )
</span><span class="cx"> from calendarserver.tools.purge import WorkerService, PurgeOldEventsService, DEFAULT_BATCH_SIZE, DEFAULT_RETAIN_DAYS
</span><del>-from calendarserver.tools.cmdline import utilityMain
-
</del><ins>+from calendarserver.tools.util import (
+    recordForPrincipalID, autoDisableMemcached
+)
</ins><span class="cx"> from pycalendar.datetime import DateTime
</span><del>-
</del><ins>+from twext.who.directory import DirectoryRecord
+from twisted.internet.defer import inlineCallbacks, succeed, returnValue
</ins><span class="cx"> from twistedcaldav.config import config, ConfigDict
</span><span class="cx"> 
</span><del>-from calendarserver.tools.config import WRITABLE_CONFIG_KEYS, setKeyPath, getKeyPath, flattenDictionary, WritableConfig
</del><ins>+from txdav.who.idirectory import RecordType as CalRecordType
+from twext.who.idirectory import FieldName
+from twisted.python.constants import Names, NamedConstant
+from txdav.who.delegates import (
+    addDelegate, removeDelegate, RecordType as DelegateRecordType
+)
</ins><span class="cx"> 
</span><ins>+
+attrMap = {
+    'GeneratedUID': {'attr': 'uid', },
+    'RealName': {'attr': 'fullNames', },
+    'RecordName': {'attr': 'shortNames', },
+    'AutoScheduleMode': {'attr': 'autoScheduleMode', },
+    'AutoAcceptGroup': {'attr': 'autoAcceptGroup', },
+
+    # 'Comment': {'extras': True, 'attr': 'comment', },
+    # 'Description': {'extras': True, 'attr': 'description', },
+    # 'Type': {'extras': True, 'attr': 'type', },
+
+    # For &quot;Locations&quot;, i.e. scheduled spaces
+    'Capacity': {'attr': 'capacity', },
+    'Floor': {'attr': 'floor', },
+    'AssociatedAddress': {'attr': 'associatedAddress', },
+
+    # For &quot;Addresses&quot;, i.e. nonscheduled areas containing Locations
+    'AbbreviatedName': {'attr': 'abbreviatedName', },
+    'StreetAddress': {'attr': 'streetAddress', },
+    'GeographicLocation': {'attr': 'geographicLocation', },
+}
+
+
</ins><span class="cx"> def usage(e=None):
</span><span class="cx"> 
</span><span class="cx">     name = os.path.basename(sys.argv[0])
</span><span class="lines">@@ -76,9 +100,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create/run a Runner to execute the commands
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        rootResource = self.rootResource()
-        directory = rootResource.getDirectory()
-        runner = Runner(rootResource, directory, self.store, self.commands)
</del><ins>+        runner = Runner(self.store, self.commands)
</ins><span class="cx">         if runner.validate():
</span><span class="cx">             yield runner.run()
</span><span class="cx"> 
</span><span class="lines">@@ -145,10 +167,9 @@
</span><span class="cx"> 
</span><span class="cx"> class Runner(object):
</span><span class="cx"> 
</span><del>-    def __init__(self, root, directory, store, commands, output=None):
-        self.root = root
-        self.dir = directory
</del><ins>+    def __init__(self, store, commands, output=None):
</ins><span class="cx">         self.store = store
</span><ins>+        self.dir = store.directoryService()
</ins><span class="cx">         self.commands = commands
</span><span class="cx">         if output is None:
</span><span class="cx">             output = sys.stdout
</span><span class="lines">@@ -180,12 +201,13 @@
</span><span class="cx">             pool.ClientEnabled = True
</span><span class="cx">         autoDisableMemcached(config)
</span><span class="cx"> 
</span><del>-        from twistedcaldav.directory import calendaruserproxy
-        if calendaruserproxy.ProxyDBService is not None:
-            # Reset the proxy db memcacher because memcached may have come or
-            # gone since the last time through here.
-            # TODO: figure out a better way to do this
-            calendaruserproxy.ProxyDBService._memcacher._memcacheProtocol = None
</del><ins>+        # FIXME:
+        # from twistedcaldav.directory import calendaruserproxy
+        # if calendaruserproxy.ProxyDBService is not None:
+        #     # Reset the proxy db memcacher because memcached may have come or
+        #     # gone since the last time through here.
+        #     # TODO: figure out a better way to do this
+        #     calendaruserproxy.ProxyDBService._memcacher._memcacheProtocol = None
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             for command in self.commands:
</span><span class="lines">@@ -203,224 +225,202 @@
</span><span class="cx"> 
</span><span class="cx">     # Locations
</span><span class="cx"> 
</span><ins>+    # deferred
</ins><span class="cx">     def command_getLocationList(self, command):
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;])
</del><ins>+        return self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;])
</ins><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def command_createLocation(self, command):
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
</del><ins>+    def _saveRecord(self, typeName, recordType, command, oldFields=None):
+        &quot;&quot;&quot;
+        Save a record using the values in the command plist, starting with
+        any fields in the optional oldFields.
</ins><span class="cx"> 
</span><del>-        try:
-            record = (yield updateRecord(True, self.dir, &quot;locations&quot;, **kwargs))
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
</del><ins>+        @param typeName: one of &quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;; used
+            to return the appropriate list of records afterwards.
+        @param recordType: the type of record to save
+        @param command: the command containing values
+        @type command: C{dict}
+        @param oldFields: the optional fields to start with, which will be
+            overridden by values from command
+        @type oldFiles: C{dict}
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-        readProxies = command.get(&quot;ReadProxies&quot;, None)
-        writeProxies = command.get(&quot;WriteProxies&quot;, None)
-        principal = principalForPrincipalID(record.guid, directory=self.dir)
-        (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
</del><ins>+        if oldFields is None:
+            fields = {
+                FieldName.recordType: recordType
+            }
+            create = True
+        else:
+            fields = oldFields.copy()
+            create = False
</ins><span class="cx"> 
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;])
</del><ins>+        for key, info in attrMap.iteritems():
+            if key in command:
+                attrName = info['attr']
+                field = self.dir.fieldName.lookupByName(attrName)
+                valueType = self.dir.fieldName.valueType(field)
+                value = command[key]
</ins><span class="cx"> 
</span><ins>+                # For backwards compatibility, convert to a list if needed
+                if (
+                    self.dir.fieldName.isMultiValue(field) and
+                    not isinstance(value, list)
+                ):
+                    value = [value]
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def command_getLocationAttributes(self, command):
-        guid = command['GeneratedUID']
-        record = self.dir.recordWithGUID(guid)
-        if record is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (guid,))
-            return
-        recordDict = recordToDict(record)
-        principal = principalForPrincipalID(guid, directory=self.dir)
-        if principal is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (guid,))
-            return
-        recordDict['AutoSchedule'] = principal.getAutoSchedule()
-        recordDict['AutoAcceptGroup'] = principal.getAutoAcceptGroup()
-        recordDict['ReadProxies'], recordDict['WriteProxies'] = (yield getProxies(principal,
-            directory=self.dir))
-        self.respond(command, recordDict)
</del><ins>+                if valueType == int:
+                    value = int(value)
+                elif issubclass(valueType, Names):
+                    if value is not None:
+                        value = valueType.lookupByName(value)
+                else:
+                    if isinstance(value, list):
+                        newList = []
+                        for item in value:
+                            if isinstance(item, str):
+                                newList.append(item.decode(&quot;utf-8&quot;))
+                            else:
+                                newList.append(item)
+                        value = newList
+                    elif isinstance(value, str):
+                        value = value.decode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><del>-    command_getResourceAttributes = command_getLocationAttributes
</del><ins>+                fields[field] = value
</ins><span class="cx"> 
</span><ins>+        if FieldName.shortNames not in fields:
+            # No short names were provided, so copy from uid
+            fields[FieldName.shortNames] = [fields[FieldName.uid]]
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def command_setLocationAttributes(self, command):
</del><ins>+        record = DirectoryRecord(self.dir, fields)
+        yield self.dir.updateRecords([record], create=create)
</ins><span class="cx"> 
</span><del>-        # Set autoSchedule prior to the updateRecord so that the right
-        # value ends up in memcached
-        principal = principalForPrincipalID(command['GeneratedUID'],
-            directory=self.dir)
-        (yield principal.setAutoSchedule(command.get('AutoSchedule', False)))
-        (yield principal.setAutoAcceptGroup(command.get('AutoAcceptGroup', &quot;&quot;)))
-
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            record = (yield updateRecord(False, self.dir, &quot;locations&quot;, **kwargs))
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
-
</del><span class="cx">         readProxies = command.get(&quot;ReadProxies&quot;, None)
</span><ins>+        if readProxies:
+            proxyRecords = []
+            for proxyUID in readProxies:
+                proxyRecord = yield self.dir.recordWithUID(proxyUID)
+                if proxyRecord is not None:
+                    proxyRecords.append(proxyRecord)
+            readProxies = proxyRecords
+
</ins><span class="cx">         writeProxies = command.get(&quot;WriteProxies&quot;, None)
</span><del>-        principal = principalForPrincipalID(record.guid, directory=self.dir)
-        (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
</del><ins>+        if writeProxies:
+            proxyRecords = []
+            for proxyUID in writeProxies:
+                proxyRecord = yield self.dir.recordWithUID(proxyUID)
+                if proxyRecord is not None:
+                    proxyRecords.append(proxyRecord)
+            writeProxies = proxyRecords
</ins><span class="cx"> 
</span><del>-        yield self.command_getLocationAttributes(command)
</del><ins>+        yield setProxies(record, readProxies, writeProxies)
</ins><span class="cx"> 
</span><ins>+        yield self.respondWithRecordsOfTypes(self.dir, command, [typeName])
</ins><span class="cx"> 
</span><del>-    def command_deleteLocation(self, command):
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            self.dir.destroyRecord(&quot;locations&quot;, **kwargs)
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;])
</del><span class="cx"> 
</span><ins>+    def command_createLocation(self, command):
+        return self._saveRecord(&quot;locations&quot;, CalRecordType.location, command)
</ins><span class="cx"> 
</span><del>-    # Resources
</del><span class="cx"> 
</span><del>-    def command_getResourceList(self, command):
-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;resources&quot;])
-
-
-    @inlineCallbacks
</del><span class="cx">     def command_createResource(self, command):
</span><del>-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
</del><ins>+        return self._saveRecord(&quot;resources&quot;, CalRecordType.resource, command)
</ins><span class="cx"> 
</span><del>-        try:
-            record = (yield updateRecord(True, self.dir, &quot;resources&quot;, **kwargs))
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
</del><span class="cx"> 
</span><del>-        readProxies = command.get(&quot;ReadProxies&quot;, None)
-        writeProxies = command.get(&quot;WriteProxies&quot;, None)
-        principal = principalForPrincipalID(record.guid, directory=self.dir)
-        (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
</del><ins>+    def command_createAddress(self, command):
+        return self._saveRecord(&quot;addresses&quot;, CalRecordType.address, command)
</ins><span class="cx"> 
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;resources&quot;])
</del><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def command_setLocationAttributes(self, command):
+        uid = command['GeneratedUID']
+        record = yield self.dir.recordWithUID(uid)
+        yield self._saveRecord(
+            &quot;locations&quot;,
+            CalRecordType.location,
+            command,
+            oldFields=record.fields
+        )
</ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def command_setResourceAttributes(self, command):
</span><ins>+        uid = command['GeneratedUID']
+        record = yield self.dir.recordWithUID(uid)
+        yield self._saveRecord(
+            &quot;resources&quot;,
+            CalRecordType.resource,
+            command,
+            oldFields=record.fields
+        )
</ins><span class="cx"> 
</span><del>-        # Set autoSchedule prior to the updateRecord so that the right
-        # value ends up in memcached
-        principal = principalForPrincipalID(command['GeneratedUID'],
-            directory=self.dir)
-        (yield principal.setAutoSchedule(command.get('AutoSchedule', False)))
-        (yield principal.setAutoAcceptGroup(command.get('AutoAcceptGroup', &quot;&quot;)))
</del><span class="cx"> 
</span><del>-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            record = (yield updateRecord(False, self.dir, &quot;resources&quot;, **kwargs))
-        except DirectoryError, e:
-            self.respondWithError(str(e))
</del><ins>+    @inlineCallbacks
+    def command_setAddressAttributes(self, command):
+        uid = command['GeneratedUID']
+        record = yield self.dir.recordWithUID(uid)
+        yield self._saveRecord(
+            &quot;addresses&quot;,
+            CalRecordType.address,
+            command,
+            oldFields=record.fields
+        )
+
+
+    @inlineCallbacks
+    def command_getLocationAttributes(self, command):
+        uid = command['GeneratedUID']
+        record = yield self.dir.recordWithUID(uid)
+        if record is None:
+            self.respondWithError(&quot;Principal not found: %s&quot; % (uid,))
</ins><span class="cx">             return
</span><ins>+        recordDict = recordToDict(record)
+        # recordDict['AutoSchedule'] = principal.getAutoSchedule()
+        try:
+            recordDict['AutoAcceptGroup'] = record.autoAcceptGroup
+        except AttributeError:
+            pass
</ins><span class="cx"> 
</span><del>-        readProxies = command.get(&quot;ReadProxies&quot;, None)
-        writeProxies = command.get(&quot;WriteProxies&quot;, None)
-        principal = principalForPrincipalID(record.guid, directory=self.dir)
-        (yield setProxies(self.store, principal, readProxies, writeProxies, directory=self.dir))
</del><ins>+        readProxies, writeProxies = yield getProxies(record)
+        recordDict['ReadProxies'] = [r.uid for r in readProxies]
+        recordDict['WriteProxies'] = [r.uid for r in writeProxies]
+        self.respond(command, recordDict)
</ins><span class="cx"> 
</span><del>-        yield self.command_getResourceAttributes(command)
</del><ins>+    command_getResourceAttributes = command_getLocationAttributes
+    command_getAddressAttributes = command_getLocationAttributes
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def command_deleteResource(self, command):
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            self.dir.destroyRecord(&quot;resources&quot;, **kwargs)
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
</del><ins>+    # Resources
+
+    def command_getResourceList(self, command):
</ins><span class="cx">         self.respondWithRecordsOfTypes(self.dir, command, [&quot;resources&quot;])
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # deferred
</ins><span class="cx">     def command_getLocationAndResourceList(self, command):
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;, &quot;resources&quot;])
</del><ins>+        return self.respondWithRecordsOfTypes(self.dir, command, [&quot;locations&quot;, &quot;resources&quot;])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     # Addresses
</span><span class="cx"> 
</span><span class="cx">     def command_getAddressList(self, command):
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;addresses&quot;])
</del><ins>+        return self.respondWithRecordsOfTypes(self.dir, command, [&quot;addresses&quot;])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def command_createAddress(self, command):
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
</del><ins>+    def _delete(self, typeName, command):
+        uid = command['GeneratedUID']
+        yield self.dir.removeRecords([uid])
+        self.respondWithRecordsOfTypes(self.dir, command, [typeName])
</ins><span class="cx"> 
</span><del>-        try:
-            yield updateRecord(True, self.dir, &quot;addresses&quot;, **kwargs)
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
</del><span class="cx"> 
</span><del>-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;addresses&quot;])
</del><ins>+    def command_deleteLocation(self, command):
+        return self._delete(&quot;locations&quot;, command)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def command_getAddressAttributes(self, command):
-        guid = command['GeneratedUID']
-        record = self.dir.recordWithGUID(guid)
-        if record is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (guid,))
-            return
-        recordDict = recordToDict(record)
-        self.respond(command, recordDict)
-        return succeed(None)
</del><ins>+    def command_deleteResource(self, command):
+        return self._delete(&quot;resources&quot;, command)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def command_setAddressAttributes(self, command):
-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            yield updateRecord(False, self.dir, &quot;addresses&quot;, **kwargs)
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
-
-        yield self.command_getAddressAttributes(command)
-
-
</del><span class="cx">     def command_deleteAddress(self, command):
</span><del>-        kwargs = {}
-        for key, info in attrMap.iteritems():
-            if key in command:
-                kwargs[info['attr']] = command[key]
-        try:
-            self.dir.destroyRecord(&quot;addresses&quot;, **kwargs)
-        except DirectoryError, e:
-            self.respondWithError(str(e))
-            return
-        self.respondWithRecordsOfTypes(self.dir, command, [&quot;addresses&quot;])
</del><ins>+        return self._delete(&quot;addresses&quot;, command)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     # Config
</span><span class="lines">@@ -471,106 +471,74 @@
</span><span class="cx"> 
</span><span class="cx">     # Proxies
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
</del><span class="cx">     def command_listWriteProxies(self, command):
</span><del>-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
-        if principal is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
-            return
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;write&quot;))
</del><ins>+        return self._listProxies(command, &quot;write&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def command_listReadProxies(self, command):
+        return self._listProxies(command, &quot;read&quot;)
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def command_addWriteProxy(self, command):
-        principal = principalForPrincipalID(command['Principal'],
-            directory=self.dir)
-        if principal is None:
</del><ins>+    def _listProxies(self, command, proxyType):
+        record = yield recordForPrincipalID(self.dir, command['Principal'])
+        if record is None:
</ins><span class="cx">             self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
</span><del>-            return
</del><ins>+            returnValue(None)
+        yield self.respondWithProxies(command, record, proxyType)
</ins><span class="cx"> 
</span><del>-        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
-        if proxy is None:
-            self.respondWithError(&quot;Proxy not found: %s&quot; % (command['Proxy'],))
-            return
-        try:
-            (yield addProxy(self.root, self.dir, self.store, principal, &quot;write&quot;, proxy))
-        except ProxyError, e:
-            self.respondWithError(str(e))
-            return
-        except ProxyWarning, e:
-            pass
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;write&quot;))
</del><span class="cx"> 
</span><ins>+    def command_addReadProxy(self, command):
+        return self._addProxy(command, &quot;read&quot;)
</ins><span class="cx"> 
</span><ins>+
+    def command_addWriteProxy(self, command):
+        return self._addProxy(command, &quot;write&quot;)
+
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def command_removeWriteProxy(self, command):
-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
-        if principal is None:
</del><ins>+    def _addProxy(self, command, proxyType):
+        record = yield recordForPrincipalID(self.dir, command['Principal'])
+        if record is None:
</ins><span class="cx">             self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
</span><del>-            return
-        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
-        if proxy is None:
</del><ins>+            returnValue(None)
+
+        proxyRecord = yield recordForPrincipalID(self.dir, command['Proxy'])
+        if proxyRecord is None:
</ins><span class="cx">             self.respondWithError(&quot;Proxy not found: %s&quot; % (command['Proxy'],))
</span><del>-            return
-        try:
-            (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=(&quot;write&quot;,)))
-        except ProxyError, e:
-            self.respondWithError(str(e))
-            return
-        except ProxyWarning, e:
-            pass
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;write&quot;))
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><ins>+        txn = self.store.newTransaction()
+        yield addDelegate(txn, record, proxyRecord, (proxyType == &quot;write&quot;))
+        yield txn.commit()
+        yield self.respondWithProxies(command, record, proxyType)
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def command_listReadProxies(self, command):
-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
-        if principal is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
-            return
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;read&quot;))
</del><span class="cx"> 
</span><ins>+    def command_removeReadProxy(self, command):
+        return self._removeProxy(command, &quot;read&quot;)
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def command_addReadProxy(self, command):
-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
-        if principal is None:
-            self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
-            return
-        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
-        if proxy is None:
-            self.respondWithError(&quot;Proxy not found: %s&quot; % (command['Proxy'],))
-            return
-        try:
-            (yield addProxy(self.root, self.dir, self.store, principal, &quot;read&quot;, proxy))
-        except ProxyError, e:
-            self.respondWithError(str(e))
-            return
-        except ProxyWarning, e:
-            pass
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;read&quot;))
</del><span class="cx"> 
</span><ins>+    def command_removeWriteProxy(self, command):
+        return self._removeProxy(command, &quot;write&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def command_removeReadProxy(self, command):
-        principal = principalForPrincipalID(command['Principal'], directory=self.dir)
-        if principal is None:
</del><ins>+    def _removeProxy(self, command, proxyType):
+        record = yield recordForPrincipalID(self.dir, command['Principal'])
+        if record is None:
</ins><span class="cx">             self.respondWithError(&quot;Principal not found: %s&quot; % (command['Principal'],))
</span><del>-            return
-        proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
-        if proxy is None:
</del><ins>+            returnValue(None)
+
+        proxyRecord = yield recordForPrincipalID(self.dir, command['Proxy'])
+        if proxyRecord is None:
</ins><span class="cx">             self.respondWithError(&quot;Proxy not found: %s&quot; % (command['Proxy'],))
</span><del>-            return
-        try:
-            (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=(&quot;read&quot;,)))
-        except ProxyError, e:
-            self.respondWithError(str(e))
-            return
-        except ProxyWarning, e:
-            pass
-        (yield self.respondWithProxies(self.dir, command, principal, &quot;read&quot;))
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><ins>+        txn = self.store.newTransaction()
+        yield removeDelegate(txn, record, proxyRecord, (proxyType == &quot;write&quot;))
+        yield txn.commit()
+        yield self.respondWithProxies(command, record, proxyType)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def command_purgeOldEvents(self, command):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -585,40 +553,42 @@
</span><span class="cx">         cutoff.setDateOnly(False)
</span><span class="cx">         cutoff.offsetDay(-retainDays)
</span><span class="cx">         eventCount = (yield PurgeOldEventsService.purgeOldEvents(self.store, cutoff, DEFAULT_BATCH_SIZE))
</span><del>-        self.respond(command, {'EventsRemoved' : eventCount, &quot;RetainDays&quot; : retainDays})
</del><ins>+        self.respond(command, {'EventsRemoved': eventCount, &quot;RetainDays&quot;: retainDays})
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def respondWithProxies(self, directory, command, principal, proxyType):
</del><ins>+    def respondWithProxies(self, command, record, proxyType):
</ins><span class="cx">         proxies = []
</span><del>-        subPrincipal = proxySubprincipal(principal, proxyType)
-        if subPrincipal is not None:
-            membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
-            if membersProperty.children:
-                for member in membersProperty.children:
-                    proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
-                    proxies.append(proxyPrincipal.record.guid)
</del><ins>+        recordType = {
+            &quot;read&quot;: DelegateRecordType.readDelegateGroup,
+            &quot;write&quot;: DelegateRecordType.writeDelegateGroup,
+        }[proxyType]
+        proxyGroup = yield self.dir.recordWithShortName(recordType, record.uid)
+        for member in (yield proxyGroup.members()):
+            proxies.append(member.uid)
</ins><span class="cx"> 
</span><span class="cx">         self.respond(command, {
</span><del>-            'Principal' : principal.record.guid, 'Proxies' : proxies
</del><ins>+            'Principal': record.uid, 'Proxies': proxies
</ins><span class="cx">         })
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def respondWithRecordsOfTypes(self, directory, command, recordTypes):
</span><span class="cx">         result = []
</span><span class="cx">         for recordType in recordTypes:
</span><del>-            for record in directory.listRecords(recordType):
</del><ins>+            recordType = directory.oldNameToRecordType(recordType)
+            for record in (yield directory.recordsWithRecordType(recordType)):
</ins><span class="cx">                 recordDict = recordToDict(record)
</span><span class="cx">                 result.append(recordDict)
</span><span class="cx">         self.respond(command, result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def respond(self, command, result):
</span><del>-        self.output.write(writePlistToString({'command' : command['command'], 'result' : result}))
</del><ins>+        self.output.write(writePlistToString({'command': command['command'], 'result': result}))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def respondWithError(self, msg, status=1):
</span><del>-        self.output.write(writePlistToString({'error' : msg, }))
</del><ins>+        self.output.write(writePlistToString({'error': msg, }))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -626,12 +596,17 @@
</span><span class="cx">     recordDict = {}
</span><span class="cx">     for key, info in attrMap.iteritems():
</span><span class="cx">         try:
</span><del>-            if info.get('extras', False):
-                value = record.extras[info['attr']]
-            else:
-                value = getattr(record, info['attr'])
</del><ins>+            value = record.fields[record.service.fieldName.lookupByName(info['attr'])]
+            if value is None:
+                continue
+            # For backwards compatibility, present fullName/RealName as single
+            # value even though twext.who now has it as multiValue
+            if key == &quot;RealName&quot;:
+                value = value[0]
</ins><span class="cx">             if isinstance(value, str):
</span><span class="cx">                 value = value.decode(&quot;utf-8&quot;)
</span><ins>+            elif isinstance(value, NamedConstant):
+                value = value.name
</ins><span class="cx">             recordDict[key] = value
</span><span class="cx">         except KeyError:
</span><span class="cx">             pass
</span><span class="lines">@@ -640,7 +615,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def respondWithError(msg, status=1):
</span><del>-    sys.stdout.write(writePlistToString({'error' : msg, }))
</del><ins>+    sys.stdout.write(writePlistToString({'error': msg, }))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsmigratepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/migrate.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/migrate.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/migrate.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -33,8 +33,9 @@
</span><span class="cx"> from twistedcaldav.config import ConfigurationError
</span><span class="cx"> from twistedcaldav.upgrade import upgradeData
</span><span class="cx"> 
</span><del>-from calendarserver.tools.util import loadConfig, getDirectory
</del><ins>+from calendarserver.tools.util import loadConfig
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def usage(e=None):
</span><span class="cx">     if e:
</span><span class="cx">         print(e)
</span><span class="lines">@@ -81,7 +82,6 @@
</span><span class="cx"> 
</span><span class="cx">     try:
</span><span class="cx">         config = loadConfig(configFileName)
</span><del>-        config.directory = getDirectory()
</del><span class="cx">     except ConfigurationError, e:
</span><span class="cx">         sys.stdout.write(&quot;%s\n&quot; % (e,))
</span><span class="cx">         sys.exit(1)
</span><span class="lines">@@ -90,7 +90,7 @@
</span><span class="cx"> 
</span><span class="cx">     if profiling:
</span><span class="cx">         import cProfile
</span><del>-        cProfile.runctx(&quot;upgradeData(c)&quot;, globals(), {&quot;c&quot; : config}, &quot;/tmp/upgrade.prof&quot;)
</del><ins>+        cProfile.runctx(&quot;upgradeData(c)&quot;, globals(), {&quot;c&quot;: config}, &quot;/tmp/upgrade.prof&quot;)
</ins><span class="cx">     else:
</span><span class="cx">         upgradeData(config)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/principals.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/principals.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/principals.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -17,39 +17,40 @@
</span><span class="cx"> ##
</span><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><del>-import sys
-import os
-import operator
</del><span class="cx"> from getopt import getopt, GetoptError
</span><ins>+import operator
+import os
+import sys
</ins><span class="cx"> from uuid import UUID
</span><span class="cx"> 
</span><ins>+from calendarserver.tools.cmdline import utilityMain, WorkerService
+from calendarserver.tools.util import (
+    recordForPrincipalID, prettyRecord
+)
+from twext.who.directory import DirectoryRecord
+from twext.who.idirectory import RecordType, InvalidDirectoryRecordError
</ins><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><del>-from txdav.xml import element as davxml
-
-from txdav.xml.base import decodeXMLName, encodeXMLName
-
</del><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryError
-from txdav.who.groups import schedulePolledGroupCachingUpdate
-
-from calendarserver.tools.util import (
-    booleanArgument, proxySubprincipal, action_addProxyPrincipal,
-    principalForPrincipalID, prettyPrincipal, ProxyError,
-    action_removeProxyPrincipal
</del><ins>+from txdav.who.delegates import (
+    addDelegate, removeDelegate, RecordType as DelegateRecordType
</ins><span class="cx"> )
</span><del>-from twistedcaldav.directory.augment import allowedAutoScheduleModes
</del><ins>+from txdav.who.idirectory import AutoScheduleMode
</ins><span class="cx"> 
</span><del>-from calendarserver.tools.cmdline import utilityMain, WorkerService
</del><span class="cx"> 
</span><ins>+allowedAutoScheduleModes = {
+    &quot;default&quot;: None,
+    &quot;none&quot;: AutoScheduleMode.none,
+    &quot;accept-always&quot;: AutoScheduleMode.accept,
+    &quot;decline-always&quot;: AutoScheduleMode.decline,
+    &quot;accept-if-free&quot;: AutoScheduleMode.acceptIfFree,
+    &quot;decline-if-busy&quot;: AutoScheduleMode.declineIfBusy,
+    &quot;automatic&quot;: AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+}
</ins><span class="cx"> 
</span><ins>+
</ins><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 class="lines">@@ -74,20 +75,18 @@
</span><span class="cx">     print(&quot;  --search &lt;search-string&gt;: search for matching principals&quot;)
</span><span class="cx">     print(&quot;  --list-principal-types: list all of the known principal types&quot;)
</span><span class="cx">     print(&quot;  --list-principals type: list all principals of the given type&quot;)
</span><del>-    print(&quot;  --read-property=property: read DAV property (eg.: {DAV:}group-member-set)&quot;)
</del><span class="cx">     print(&quot;  --list-read-proxies: list proxies with read-only access&quot;)
</span><span class="cx">     print(&quot;  --list-write-proxies: list proxies with read-write access&quot;)
</span><span class="cx">     print(&quot;  --list-proxies: list all proxies&quot;)
</span><ins>+    print(&quot;  --list-proxy-for: principals this principal is a proxy for&quot;)
</ins><span class="cx">     print(&quot;  --add-read-proxy=principal: add a read-only proxy&quot;)
</span><span class="cx">     print(&quot;  --add-write-proxy=principal: add a read-write proxy&quot;)
</span><span class="cx">     print(&quot;  --remove-proxy=principal: remove a proxy&quot;)
</span><del>-    print(&quot;  --set-auto-schedule={true|false}: set auto-accept state&quot;)
-    print(&quot;  --get-auto-schedule: read auto-schedule state&quot;)
</del><span class="cx">     print(&quot;  --set-auto-schedule-mode={default|none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic}: set auto-schedule mode&quot;)
</span><span class="cx">     print(&quot;  --get-auto-schedule-mode: read auto-schedule mode&quot;)
</span><span class="cx">     print(&quot;  --set-auto-accept-group=principal: set auto-accept-group&quot;)
</span><span class="cx">     print(&quot;  --get-auto-accept-group: read auto-accept-group&quot;)
</span><del>-    print(&quot;  --add {locations|resources|addresses} 'full name' [record name] [GUID]: add a principal&quot;)
</del><ins>+    print(&quot;  --add {locations|resources|addresses} full-name record-name UID: add a principal&quot;)
</ins><span class="cx">     print(&quot;  --remove: remove a principal&quot;)
</span><span class="cx">     print(&quot;  --set-geo=url: set the geo: url for an address (e.g. geo:37.331741,-122.030333)&quot;)
</span><span class="cx">     print(&quot;  --get-geo: get the geo: url for an address&quot;)
</span><span class="lines">@@ -102,7 +101,6 @@
</span><span class="cx">         sys.exit(0)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class PrincipalService(WorkerService):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Executes principals-related functions in a context which has access to the store
</span><span class="lines">@@ -118,33 +116,10 @@
</span><span class="cx">         resource, directory, store, and whatever has been assigned to &quot;params&quot;.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if self.function is not None:
</span><del>-            rootResource = self.rootResource()
-            directory = rootResource.getDirectory()
-            yield self.function(rootResource, directory, self.store, *self.params)
</del><ins>+            yield self.function(self.store, *self.params)
</ins><span class="cx"> 
</span><del>-attrMap = {
-    'GeneratedUID' : { 'attr' : 'guid', },
-    'RealName' : { 'attr' : 'fullName', },
-    'RecordName' : { 'attr' : 'shortNames', },
-    'AutoSchedule' : { 'attr' : 'autoSchedule', },
-    'AutoAcceptGroup' : { 'attr' : 'autoAcceptGroup', },
</del><span class="cx"> 
</span><del>-    'Comment' : { 'extras' : True, 'attr' : 'comment', },
-    'Description' : { 'extras' : True, 'attr' : 'description', },
-    'Type' : { 'extras' : True, 'attr' : 'type', },
</del><span class="cx"> 
</span><del>-    # For &quot;Locations&quot;, i.e. scheduled spaces
-    'Capacity' : { 'extras' : True, 'attr' : 'capacity', },
-    'Floor' : { 'extras' : True, 'attr' : 'floor', },
-    'AssociatedAddress' : { 'extras' : True, 'attr' : 'associatedAddress', },
-
-    # For &quot;Addresses&quot;, i.e. nonscheduled areas containing Locations
-    'AbbreviatedName' : { 'extras' : True, 'attr' : 'abbreviatedName', },
-    'StreetAddress' : { 'extras' : True, 'attr' : 'streetAddress', },
-    'Geo' : { 'extras' : True, 'attr' : 'geo', },
-}
-
-
</del><span class="cx"> def main():
</span><span class="cx">     try:
</span><span class="cx">         (optargs, args) = getopt(
</span><span class="lines">@@ -156,15 +131,13 @@
</span><span class="cx">                 &quot;search=&quot;,
</span><span class="cx">                 &quot;list-principal-types&quot;,
</span><span class="cx">                 &quot;list-principals=&quot;,
</span><del>-                &quot;read-property=&quot;,
</del><span class="cx">                 &quot;list-read-proxies&quot;,
</span><span class="cx">                 &quot;list-write-proxies&quot;,
</span><span class="cx">                 &quot;list-proxies&quot;,
</span><ins>+                &quot;list-proxy-for&quot;,
</ins><span class="cx">                 &quot;add-read-proxy=&quot;,
</span><span class="cx">                 &quot;add-write-proxy=&quot;,
</span><span class="cx">                 &quot;remove-proxy=&quot;,
</span><del>-                &quot;set-auto-schedule=&quot;,
-                &quot;get-auto-schedule&quot;,
</del><span class="cx">                 &quot;set-auto-schedule-mode=&quot;,
</span><span class="cx">                 &quot;get-auto-schedule-mode&quot;,
</span><span class="cx">                 &quot;set-auto-accept-group=&quot;,
</span><span class="lines">@@ -193,6 +166,10 @@
</span><span class="cx">     verbose = False
</span><span class="cx"> 
</span><span class="cx">     for opt, arg in optargs:
</span><ins>+
+        # Args come in as encoded bytes
+        arg = arg.decode(&quot;utf-8&quot;)
+
</ins><span class="cx">         if opt in (&quot;-h&quot;, &quot;--help&quot;):
</span><span class="cx">             usage()
</span><span class="cx"> 
</span><span class="lines">@@ -217,13 +194,6 @@
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--search&quot;):
</span><span class="cx">             searchPrincipals = arg
</span><span class="cx"> 
</span><del>-        elif opt in (&quot;&quot;, &quot;--read-property&quot;):
-            try:
-                qname = decodeXMLName(arg)
-            except ValueError, e:
-                abort(e)
-            principalActions.append((action_readProperty, qname))
-
</del><span class="cx">         elif opt in (&quot;&quot;, &quot;--list-read-proxies&quot;):
</span><span class="cx">             principalActions.append((action_listProxies, &quot;read&quot;))
</span><span class="cx"> 
</span><span class="lines">@@ -233,6 +203,9 @@
</span><span class="cx">         elif opt in (&quot;-L&quot;, &quot;--list-proxies&quot;):
</span><span class="cx">             principalActions.append((action_listProxies, &quot;read&quot;, &quot;write&quot;))
</span><span class="cx"> 
</span><ins>+        elif opt in (&quot;--list-proxy-for&quot;):
+            principalActions.append((action_listProxyFor, &quot;read&quot;, &quot;write&quot;))
+
</ins><span class="cx">         elif opt in (&quot;--add-read-proxy&quot;, &quot;--add-write-proxy&quot;):
</span><span class="cx">             if &quot;read&quot; in opt:
</span><span class="cx">                 proxyType = &quot;read&quot;
</span><span class="lines">@@ -240,38 +213,17 @@
</span><span class="cx">                 proxyType = &quot;write&quot;
</span><span class="cx">             else:
</span><span class="cx">                 raise AssertionError(&quot;Unknown proxy type&quot;)
</span><del>-
-            try:
-                principalForPrincipalID(arg, checkOnly=True)
-            except ValueError, e:
-                abort(e)
-
</del><span class="cx">             principalActions.append((action_addProxy, proxyType, arg))
</span><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--remove-proxy&quot;):
</span><del>-            try:
-                principalForPrincipalID(arg, checkOnly=True)
-            except ValueError, e:
-                abort(e)
-
</del><span class="cx">             principalActions.append((action_removeProxy, arg))
</span><span class="cx"> 
</span><del>-        elif opt in (&quot;&quot;, &quot;--set-auto-schedule&quot;):
-            try:
-                autoSchedule = booleanArgument(arg)
-            except ValueError, e:
-                abort(e)
-
-            principalActions.append((action_setAutoSchedule, autoSchedule))
-
-        elif opt in (&quot;&quot;, &quot;--get-auto-schedule&quot;):
-            principalActions.append((action_getAutoSchedule,))
-
</del><span class="cx">         elif opt in (&quot;&quot;, &quot;--set-auto-schedule-mode&quot;):
</span><span class="cx">             try:
</span><span class="cx">                 if arg not in allowedAutoScheduleModes:
</span><del>-                    raise ValueError(&quot;Unknown auto-schedule mode: %s&quot; % (arg,))
-                autoScheduleMode = arg
</del><ins>+                    raise ValueError(&quot;Unknown auto-schedule mode: {mode}&quot;.format(
+                        mode=arg))
+                autoScheduleMode = allowedAutoScheduleModes[arg]
</ins><span class="cx">             except ValueError, e:
</span><span class="cx">                 abort(e)
</span><span class="cx"> 
</span><span class="lines">@@ -281,33 +233,28 @@
</span><span class="cx">             principalActions.append((action_getAutoScheduleMode,))
</span><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--set-auto-accept-group&quot;):
</span><del>-            try:
-                principalForPrincipalID(arg, checkOnly=True)
-            except ValueError, e:
-                abort(e)
-
</del><span class="cx">             principalActions.append((action_setAutoAcceptGroup, arg))
</span><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--get-auto-accept-group&quot;):
</span><span class="cx">             principalActions.append((action_getAutoAcceptGroup,))
</span><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--set-geo&quot;):
</span><del>-            principalActions.append((action_setValue, &quot;Geo&quot;, arg))
</del><ins>+            principalActions.append((action_setValue, u&quot;geographicLocation&quot;, arg))
</ins><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--get-geo&quot;):
</span><del>-            principalActions.append((action_getValue, &quot;Geo&quot;))
</del><ins>+            principalActions.append((action_getValue, u&quot;geographicLocation&quot;))
</ins><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--set-street-address&quot;):
</span><del>-            principalActions.append((action_setValue, &quot;StreetAddress&quot;, arg))
</del><ins>+            principalActions.append((action_setValue, u&quot;streetAddress&quot;, arg))
</ins><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--get-street-address&quot;):
</span><del>-            principalActions.append((action_getValue, &quot;StreetAddress&quot;))
</del><ins>+            principalActions.append((action_getValue, u&quot;streetAddress&quot;))
</ins><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--set-address&quot;):
</span><del>-            principalActions.append((action_setValue, &quot;AssociatedAddress&quot;, arg))
</del><ins>+            principalActions.append((action_setValue, u&quot;associatedAddress&quot;, arg))
</ins><span class="cx"> 
</span><span class="cx">         elif opt in (&quot;&quot;, &quot;--get-address&quot;):
</span><del>-            principalActions.append((action_getValue, &quot;AssociatedAddress&quot;))
</del><ins>+            principalActions.append((action_getValue, u&quot;associatedAddress&quot;))
</ins><span class="cx"> 
</span><span class="cx">         else:
</span><span class="cx">             raise NotImplementedError(opt)
</span><span class="lines">@@ -325,29 +272,41 @@
</span><span class="cx">     elif addType:
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            addType = matchStrings(addType, [&quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;])
</del><ins>+            addType = matchStrings(
+                addType,
+                [
+                    &quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;, &quot;users&quot;, &quot;groups&quot;
+                ]
+            )
</ins><span class="cx">         except ValueError, e:
</span><span class="cx">             print(e)
</span><span class="cx">             return
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            fullName, shortName, guid = parseCreationArgs(args)
</del><ins>+            fullName, shortName, uid = parseCreationArgs(args)
</ins><span class="cx">         except ValueError, e:
</span><span class="cx">             print(e)
</span><span class="cx">             return
</span><span class="cx"> 
</span><ins>+        if fullName is not None:
+            fullNames = [fullName]
+        else:
+            fullNames = ()
+
</ins><span class="cx">         if shortName is not None:
</span><span class="cx">             shortNames = [shortName]
</span><span class="cx">         else:
</span><span class="cx">             shortNames = ()
</span><span class="cx"> 
</span><span class="cx">         function = runAddPrincipal
</span><del>-        params = (addType, guid, shortNames, fullName)
</del><ins>+        params = (addType, uid, shortNames, fullNames)
</ins><span class="cx"> 
</span><span class="cx">     elif listPrincipals:
</span><span class="cx">         try:
</span><del>-            listPrincipals = matchStrings(listPrincipals, [&quot;users&quot;, &quot;groups&quot;,
-                &quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;])
</del><ins>+            listPrincipals = matchStrings(
+                listPrincipals,
+                [&quot;users&quot;, &quot;groups&quot;, &quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;]
+            )
</ins><span class="cx">         except ValueError, e:
</span><span class="cx">             print(e)
</span><span class="cx">             return
</span><span class="lines">@@ -363,21 +322,12 @@
</span><span class="cx">         params = (searchPrincipals,)
</span><span class="cx"> 
</span><span class="cx">     else:
</span><del>-        #
-        # Do a quick sanity check that arguments look like principal
-        # identifiers.
-        #
</del><span class="cx">         if not args:
</span><span class="cx">             usage(&quot;No principals specified.&quot;)
</span><span class="cx"> 
</span><del>-        for arg in args:
-            try:
-                principalForPrincipalID(arg, checkOnly=True)
-            except ValueError, e:
-                abort(e)
-
</del><ins>+        unicodeArgs = [a.decode(&quot;utf-8&quot;) for a in args]
</ins><span class="cx">         function = runPrincipalActions
</span><del>-        params = (args, principalActions)
</del><ins>+        params = (unicodeArgs, principalActions)
</ins><span class="cx"> 
</span><span class="cx">     PrincipalService.function = function
</span><span class="cx">     PrincipalService.params = params
</span><span class="lines">@@ -385,74 +335,86 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def runListPrincipalTypes(service, rootResource, directory, store):
</del><ins>+def runListPrincipalTypes(service, store):
+    directory = store.directoryService()
</ins><span class="cx">     for recordType in directory.recordTypes():
</span><del>-        print(recordType)
</del><ins>+        print(directory.recordTypeToOldName(recordType))
</ins><span class="cx">     return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def runListPrincipals(service, rootResource, directory, store, listPrincipals):
</del><ins>+@inlineCallbacks
+def runListPrincipals(service, store, listPrincipals):
+    directory = store.directoryService()
+    recordType = directory.oldNameToRecordType(listPrincipals)
</ins><span class="cx">     try:
</span><del>-        records = list(directory.listRecords(listPrincipals))
</del><ins>+        records = list((yield directory.recordsWithRecordType(recordType)))
</ins><span class="cx">         if records:
</span><span class="cx">             printRecordList(records)
</span><span class="cx">         else:
</span><span class="cx">             print(&quot;No records of type %s&quot; % (listPrincipals,))
</span><del>-    except UnknownRecordTypeError, e:
</del><ins>+    except InvalidDirectoryRecordError, e:
</ins><span class="cx">         usage(e)
</span><del>-    return succeed(None)
</del><ins>+    returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def runPrincipalActions(service, rootResource, directory, store, principalIDs,
-    actions):
</del><ins>+def runPrincipalActions(service, store, principalIDs, actions):
+    directory = store.directoryService()
</ins><span class="cx">     for principalID in principalIDs:
</span><del>-        # Resolve the given principal IDs to principals
</del><ins>+        # Resolve the given principal IDs to records
</ins><span class="cx">         try:
</span><del>-            principal = principalForPrincipalID(principalID, directory=directory)
</del><ins>+            record = yield recordForPrincipalID(directory, principalID)
</ins><span class="cx">         except ValueError:
</span><del>-            principal = None
</del><ins>+            record = None
</ins><span class="cx"> 
</span><del>-        if principal is None:
</del><ins>+        if record is None:
</ins><span class="cx">             sys.stderr.write(&quot;Invalid principal ID: %s\n&quot; % (principalID,))
</span><span class="cx">             continue
</span><span class="cx"> 
</span><span class="cx">         # Performs requested actions
</span><span class="cx">         for action in actions:
</span><del>-            (yield action[0](rootResource, directory, store, principal,
-                *action[1:]))
</del><ins>+            (yield action[0](store, record, *action[1:]))
</ins><span class="cx">             print(&quot;&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def runSearch(service, rootResource, directory, store, searchTerm):
-
</del><ins>+def runSearch(service, store, searchTerm):
+    directory = store.directoryService()
</ins><span class="cx">     fields = []
</span><del>-    for fieldName in (&quot;fullName&quot;, &quot;firstName&quot;, &quot;lastName&quot;, &quot;emailAddresses&quot;):
</del><ins>+    for fieldName in (&quot;fullNames&quot;, &quot;emailAddresses&quot;):
</ins><span class="cx">         fields.append((fieldName, searchTerm, True, &quot;contains&quot;))
</span><span class="cx"> 
</span><span class="cx">     records = list((yield directory.recordsMatchingTokens(searchTerm.strip().split())))
</span><span class="cx">     if records:
</span><del>-        records.sort(key=operator.attrgetter('fullName'))
-        print(&quot;%d matches found:&quot; % (len(records),))
</del><ins>+        records.sort(key=operator.attrgetter('fullNames'))
+        print(&quot;{n} matches found:&quot;.format(n=len(records)))
</ins><span class="cx">         for record in records:
</span><del>-            print(&quot;\n%s (%s)&quot; % (record.fullName,
-                {&quot;users&quot; : &quot;User&quot;,
-                 &quot;groups&quot; : &quot;Group&quot;,
-                 &quot;locations&quot; : &quot;Place&quot;,
-                 &quot;resources&quot; : &quot;Resource&quot;,
-                 &quot;addresses&quot; : &quot;Address&quot;,
-                }.get(record.recordType),
-            ))
-            print(&quot;   GUID: %s&quot; % (record.guid,))
-            print(&quot;   Record name(s): %s&quot; % (&quot;, &quot;.join(record.shortNames),))
-            if record.authIDs:
-                print(&quot;   Auth ID(s): %s&quot; % (&quot;, &quot;.join(record.authIDs),))
-            if record.emailAddresses:
-                print(&quot;   Email(s): %s&quot; % (&quot;, &quot;.join(record.emailAddresses),))
</del><ins>+            print(
+                &quot;\n{d} ({rt})&quot;.format(
+                    d=record.displayName,
+                    rt=record.recordType.name
+                )
+            )
+            print(&quot;   UID: {u}&quot;.format(u=record.uid,))
+            print(
+                &quot;   Record name{plural}: {names}&quot;.format(
+                    plural=(&quot;s&quot; if len(record.shortNames) &gt; 1 else &quot;&quot;),
+                    names=(&quot;, &quot;.join(record.shortNames))
+                )
+            )
+            try:
+                if record.emailAddresses:
+                    print(
+                        &quot;   Email{plural}: {emails}&quot;.format(
+                            plural=(&quot;s&quot; if len(record.emailAddresses) &gt; 1 else &quot;&quot;),
+                            emails=(&quot;, &quot;.join(record.emailAddresses))
+                        )
+                    )
+            except AttributeError:
+                pass
</ins><span class="cx">     else:
</span><span class="cx">         print(&quot;No matches found&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -461,291 +423,346 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def runAddPrincipal(service, rootResource, directory, store, addType, guid,
-    shortNames, fullName):
-    try:
-        yield updateRecord(True, directory, addType, guid=guid,
-            shortNames=shortNames, fullName=fullName)
-        print(&quot;Added '%s'&quot; % (fullName,))
-    except DirectoryError, e:
-        print(e)
</del><ins>+def runAddPrincipal(service, store, addType, uid, shortNames, fullNames):
+    directory = store.directoryService()
+    recordType = directory.oldNameToRecordType(addType)
</ins><span class="cx"> 
</span><ins>+    # See if that UID is in use
+    record = yield directory.recordWithUID(uid)
+    if record is not None:
+        print(&quot;UID already in use: {uid}&quot;.format(uid=uid))
+        returnValue(None)
</ins><span class="cx"> 
</span><ins>+    # See if the shortnames are in use
+    for shortName in shortNames:
+        record = yield directory.recordWithShortName(recordType, shortName)
+        if record is not None:
+            print(&quot;Record name already in use: {name}&quot;.format(name=shortName))
+            returnValue(None)
</ins><span class="cx"> 
</span><del>-def action_removePrincipal(rootResource, directory, store, principal):
-    record = principal.record
-    fullName = record.fullName
-    shortName = record.shortNames[0]
-    guid = record.guid
</del><ins>+    fields = {
+        directory.fieldName.recordType: recordType,
+        directory.fieldName.uid: uid,
+        directory.fieldName.shortNames: shortNames,
+        directory.fieldName.fullNames: fullNames,
+    }
+    record = DirectoryRecord(directory, fields)
+    yield record.service.updateRecords([record], create=True)
+    print(&quot;Added '{name}'&quot;.format(name=fullNames[0]))
</ins><span class="cx"> 
</span><del>-    directory.destroyRecord(record.recordType, guid=guid)
-    print(&quot;Removed '%s' %s %s&quot; % (fullName, shortName, guid))
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> @inlineCallbacks
</span><del>-def action_readProperty(rootResource, directory, store, resource, qname):
-    property = (yield resource.readProperty(qname, None))
-    print(&quot;%r on %s:&quot; % (encodeXMLName(*qname), resource))
-    print(&quot;&quot;)
-    print(property.toxml())
</del><ins>+def action_removePrincipal(store, record):
+    directory = store.directoryService()
+    fullName = record.displayName
+    shortNames = &quot;,&quot;.join(record.shortNames)
</ins><span class="cx"> 
</span><ins>+    yield directory.removeRecords([record.uid])
+    print(
+        &quot;Removed '{full}' {shorts} {uid}&quot;.format(
+            full=fullName, shorts=shortNames, uid=record.uid
+        )
+    )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def action_listProxies(rootResource, directory, store, principal, *proxyTypes):
</del><ins>+def action_listProxies(store, record, *proxyTypes):
+    directory = store.directoryService()
</ins><span class="cx">     for proxyType in proxyTypes:
</span><del>-        subPrincipal = proxySubprincipal(principal, proxyType)
-        if subPrincipal is None:
-            print(&quot;No %s proxies for %s&quot; % (proxyType,
-                prettyPrincipal(principal)))
-            continue
</del><span class="cx"> 
</span><del>-        membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
</del><ins>+        groupRecordType = {
+            &quot;read&quot;: directory.recordType.readDelegateGroup,
+            &quot;write&quot;: directory.recordType.writeDelegateGroup,
+        }.get(proxyType)
</ins><span class="cx"> 
</span><del>-        if membersProperty.children:
</del><ins>+        pseudoGroup = yield directory.recordWithShortName(
+            groupRecordType,
+            record.uid
+        )
+        proxies = yield pseudoGroup.members()
+        if proxies:
</ins><span class="cx">             print(&quot;%s proxies for %s:&quot; % (
</span><span class="cx">                 {&quot;read&quot;: &quot;Read-only&quot;, &quot;write&quot;: &quot;Read/write&quot;}[proxyType],
</span><del>-                prettyPrincipal(principal)
</del><ins>+                prettyRecord(record)
</ins><span class="cx">             ))
</span><del>-            records = []
-            for member in membersProperty.children:
-                proxyPrincipal = principalForPrincipalID(str(member),
-                    directory=directory)
-                records.append(proxyPrincipal.record)
-
-            printRecordList(records)
-            print
</del><ins>+            printRecordList(proxies)
+            print(&quot;&quot;)
</ins><span class="cx">         else:
</span><del>-            print(&quot;No %s proxies for %s&quot; % (proxyType,
-                prettyPrincipal(principal)))
</del><ins>+            print(&quot;No %s proxies for %s&quot; % (proxyType, prettyRecord(record)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
+def action_listProxyFor(store, record, *proxyTypes):
+    directory = store.directoryService()
+    for proxyType in proxyTypes:
</ins><span class="cx"> 
</span><ins>+        groupRecordType = {
+            &quot;read&quot;: directory.recordType.readDelegatorGroup,
+            &quot;write&quot;: directory.recordType.writeDelegatorGroup,
+        }.get(proxyType)
+
+        pseudoGroup = yield directory.recordWithShortName(
+            groupRecordType,
+            record.uid
+        )
+        proxies = yield pseudoGroup.members()
+        if proxies:
+            print(&quot;%s is a %s proxy for:&quot; % (
+                prettyRecord(record),
+                {&quot;read&quot;: &quot;Read-only&quot;, &quot;write&quot;: &quot;Read/write&quot;}[proxyType]
+            ))
+            printRecordList(proxies)
+            print(&quot;&quot;)
+        else:
+            print(
+                &quot;{r} is not a {t} proxy for anyone&quot;.format(
+                    r=prettyRecord(record),
+                    t={&quot;read&quot;: &quot;Read-only&quot;, &quot;write&quot;: &quot;Read/write&quot;}[proxyType]
+                )
+            )
+
+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def action_addProxy(rootResource, directory, store, principal, proxyType, *proxyIDs):
</del><ins>+def _addRemoveProxy(msg, fn, store, record, proxyType, *proxyIDs):
+    directory = store.directoryService()
+    readWrite = (proxyType == &quot;write&quot;)
</ins><span class="cx">     for proxyID in proxyIDs:
</span><del>-        proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
-        if proxyPrincipal is None:
</del><ins>+        proxyRecord = yield recordForPrincipalID(directory, proxyID)
+        if proxyRecord is None:
</ins><span class="cx">             print(&quot;Invalid principal ID: %s&quot; % (proxyID,))
</span><span class="cx">         else:
</span><del>-            (yield action_addProxyPrincipal(rootResource, directory, store,
-                principal, proxyType, proxyPrincipal))
</del><ins>+            txn = store.newTransaction()
+            yield fn(txn, record, proxyRecord, readWrite)
+            yield txn.commit()
+            print(
+                &quot;{msg} {proxy} as a {proxyType} proxy for {record}&quot;.format(
+                    msg=msg, proxy=prettyRecord(proxyRecord),
+                    proxyType=proxyType, record=prettyRecord(record)
+                )
+            )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
+def action_addProxy(store, record, proxyType, *proxyIDs):
+    yield _addRemoveProxy(&quot;Added&quot;, addDelegate, store, record, proxyType, *proxyIDs)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def setProxies(store, principal, readProxyPrincipals, writeProxyPrincipals, directory=None):
</del><ins>+def action_removeProxy(store, record, *proxyIDs):
+    # Write
+    yield _addRemoveProxy(&quot;Removed&quot;, removeDelegate, store, record, &quot;write&quot;, *proxyIDs)
+    # Read
+    yield _addRemoveProxy(&quot;Removed&quot;, removeDelegate, store, record, &quot;read&quot;, *proxyIDs)
+
+
+
+@inlineCallbacks
+def setProxies(record, readProxyRecords, writeProxyRecords):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Set read/write proxies en masse for a principal
-    @param principal: DirectoryPrincipalResource
-    @param readProxyPrincipals: a list of principal IDs (see principalForPrincipalID)
-    @param writeProxyPrincipals: a list of principal IDs (see principalForPrincipalID)
</del><ins>+    Set read/write proxies en masse for a record
+    @param record: L{IDirectoryRecord}
+    @param readProxyRecords: a list of records
+    @param writeProxyRecords: a list of records
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     proxyTypes = [
</span><del>-        (&quot;read&quot;, readProxyPrincipals),
-        (&quot;write&quot;, writeProxyPrincipals),
</del><ins>+        (DelegateRecordType.readDelegateGroup, readProxyRecords),
+        (DelegateRecordType.writeDelegateGroup, writeProxyRecords),
</ins><span class="cx">     ]
</span><del>-    for proxyType, proxyIDs in proxyTypes:
-        if proxyIDs is None:
</del><ins>+    for recordType, proxyRecords in proxyTypes:
+        if proxyRecords is None:
</ins><span class="cx">             continue
</span><del>-        subPrincipal = proxySubprincipal(principal, proxyType)
-        if subPrincipal is None:
-            raise ProxyError(&quot;Unable to edit %s proxies for %s\n&quot; % (proxyType,
-                prettyPrincipal(principal)))
-        memberURLs = []
-        for proxyID in proxyIDs:
-            proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
-            proxyURL = proxyPrincipal.url()
-            memberURLs.append(davxml.HRef(proxyURL))
-        membersProperty = davxml.GroupMemberSet(*memberURLs)
-        yield subPrincipal.writeProperty(membersProperty, None)
-        if store is not None:
-            # Schedule work the PeerConnectionPool will pick up as overdue
-            yield schedulePolledGroupCachingUpdate(store)
</del><ins>+        proxyGroup = yield record.service.recordWithShortName(
+            recordType, record.uid
+        )
+        yield proxyGroup.setMembers(proxyRecords)
</ins><span class="cx"> 
</span><ins>+    # if store is not None:
+    #     # Schedule work the PeerConnectionPool will pick up as overdue
+    #     yield schedulePolledGroupCachingUpdate(store)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def getProxies(principal, directory=None):
</del><ins>+def getProxies(record):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Returns a tuple containing the GUIDs for read proxies and write proxies
-    of the given principal
</del><ins>+    Returns a tuple containing the records for read proxies and write proxies
+    of the given record
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    proxies = {
-        &quot;read&quot; : [],
-        &quot;write&quot; : [],
</del><ins>+    allProxies = {
+        DelegateRecordType.readDelegateGroup: [],
+        DelegateRecordType.writeDelegateGroup: [],
</ins><span class="cx">     }
</span><del>-    for proxyType in proxies.iterkeys():
-        subPrincipal = proxySubprincipal(principal, proxyType)
-        if subPrincipal is not None:
-            membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
-            if membersProperty.children:
-                for member in membersProperty.children:
-                    proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
-                    proxies[proxyType].append(proxyPrincipal.record.guid)
</del><ins>+    for recordType in allProxies.iterkeys():
+        proxyGroup = yield record.service.recordWithShortName(
+            recordType, record.uid
+        )
+        allProxies[recordType] = yield proxyGroup.members()
</ins><span class="cx"> 
</span><del>-    returnValue((proxies['read'], proxies['write']))
</del><ins>+    returnValue(
+        (
+            allProxies[DelegateRecordType.readDelegateGroup],
+            allProxies[DelegateRecordType.writeDelegateGroup]
+        )
+    )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@inlineCallbacks
-def action_removeProxy(rootResource, directory, store, principal, *proxyIDs, **kwargs):
-    for proxyID in proxyIDs:
-        proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
-        if proxyPrincipal is None:
-            print(&quot;Invalid principal ID: %s&quot; % (proxyID,))
-        else:
-            (yield action_removeProxyPrincipal(rootResource, directory, store,
-                principal, proxyPrincipal, **kwargs))
</del><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def action_getAutoScheduleMode(store, record):
+    print(
+        &quot;Auto-schedule mode for {record} is {mode}&quot;.format(
+            record=prettyRecord(record),
+            mode=(
+                record.autoScheduleMode.description if record.autoScheduleMode
+                else &quot;Default&quot;
+            )
+        )
+    )
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def action_setAutoSchedule(rootResource, directory, store, principal, autoSchedule):
-    if principal.record.recordType == &quot;groups&quot;:
-        print(&quot;Enabling auto-schedule for %s is not allowed.&quot; % (principal,))
</del><ins>+def action_setAutoScheduleMode(store, record, autoScheduleMode):
+    if record.recordType == RecordType.group:
+        print(
+            &quot;Setting auto-schedule-mode for {record} is not allowed.&quot;.format(
+                record=prettyRecord(record)
+            )
+        )
</ins><span class="cx"> 
</span><del>-    elif principal.record.recordType == &quot;users&quot; and not config.Scheduling.Options.AutoSchedule.AllowUsers:
-        print(&quot;Enabling auto-schedule for %s is not allowed.&quot; % (principal,))
</del><ins>+    elif (
+        record.recordType == RecordType.user and
+        not config.Scheduling.Options.AutoSchedule.AllowUsers
+    ):
+        print(
+            &quot;Setting auto-schedule-mode for {record} is not allowed.&quot;.format(
+                record=prettyRecord(record)
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><del>-        print(&quot;Setting auto-schedule to %s for %s&quot; % (
-            {True: &quot;true&quot;, False: &quot;false&quot;}[autoSchedule],
-            prettyPrincipal(principal),
-        ))
</del><ins>+        print(
+            &quot;Setting auto-schedule-mode to {mode} for {record}&quot;.format(
+                mode=autoScheduleMode.description,
+                record=prettyRecord(record),
+            )
+        )
</ins><span class="cx"> 
</span><del>-        (yield updateRecord(False, directory,
-            principal.record.recordType,
-            guid=principal.record.guid,
-            shortNames=principal.record.shortNames,
-            fullName=principal.record.fullName,
-            autoSchedule=autoSchedule,
-            **principal.record.extras
-        ))
</del><ins>+        # Get original fields
+        newFields = record.fields.copy()
</ins><span class="cx"> 
</span><ins>+        # Set new values
+        newFields[record.service.fieldName.autoScheduleMode] = autoScheduleMode
</ins><span class="cx"> 
</span><ins>+        updatedRecord = DirectoryRecord(record.service, newFields)
+        yield record.service.updateRecords([updatedRecord], create=False)
</ins><span class="cx"> 
</span><del>-def action_getAutoSchedule(rootResource, directory, store, principal):
-    autoSchedule = principal.getAutoSchedule()
-    print(&quot;Auto-schedule for %s is %s&quot; % (
-        prettyPrincipal(principal),
-        {True: &quot;true&quot;, False: &quot;false&quot;}[autoSchedule],
-    ))
</del><span class="cx"> 
</span><del>-
-
</del><span class="cx"> @inlineCallbacks
</span><del>-def action_setAutoScheduleMode(rootResource, directory, store, principal, autoScheduleMode):
-    if principal.record.recordType == &quot;groups&quot;:
-        print(&quot;Setting auto-schedule mode for %s is not allowed.&quot; % (principal,))
</del><ins>+def action_setAutoAcceptGroup(store, record, autoAcceptGroup):
+    if record.recordType == RecordType.group:
+        print(
+            &quot;Setting auto-accept-group for {record} is not allowed.&quot;.format(
+                record=prettyRecord(record)
+            )
+        )
</ins><span class="cx"> 
</span><del>-    elif principal.record.recordType == &quot;users&quot; and not config.Scheduling.Options.AutoSchedule.AllowUsers:
-        print(&quot;Setting auto-schedule mode for %s is not allowed.&quot; % (principal,))
</del><ins>+    elif (
+        record.recordType == RecordType.user and
+        not config.Scheduling.Options.AutoSchedule.AllowUsers
+    ):
+        print(
+            &quot;Setting auto-accept-group for {record} is not allowed.&quot;.format(
+                record=prettyRecord(record)
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><del>-        print(&quot;Setting auto-schedule mode to %s for %s&quot; % (
-            autoScheduleMode,
-            prettyPrincipal(principal),
-        ))
</del><ins>+        groupRecord = yield recordForPrincipalID(record.service, autoAcceptGroup)
+        if groupRecord is None or groupRecord.recordType != RecordType.group:
+            print(&quot;Invalid principal ID: {id}&quot;.format(id=autoAcceptGroup))
+        else:
+            print(&quot;Setting auto-accept-group to {group} for {record}&quot;.format(
+                group=prettyRecord(groupRecord),
+                record=prettyRecord(record),
+            ))
</ins><span class="cx"> 
</span><del>-        (yield updateRecord(False, directory,
-            principal.record.recordType,
-            guid=principal.record.guid,
-            shortNames=principal.record.shortNames,
-            fullName=principal.record.fullName,
-            autoScheduleMode=autoScheduleMode,
-            **principal.record.extras
-        ))
</del><ins>+            # Get original fields
+            newFields = record.fields.copy()
</ins><span class="cx"> 
</span><ins>+            # Set new values
+            newFields[record.service.fieldName.autoAcceptGroup] = groupRecord.uid
</ins><span class="cx"> 
</span><ins>+            updatedRecord = DirectoryRecord(record.service, newFields)
+            yield record.service.updateRecords([updatedRecord], create=False)
</ins><span class="cx"> 
</span><del>-def action_getAutoScheduleMode(rootResource, directory, store, principal):
-    autoScheduleMode = principal.getAutoScheduleMode()
-    if not autoScheduleMode:
-        autoScheduleMode = &quot;automatic&quot;
-    print(&quot;Auto-schedule mode for %s is %s&quot; % (
-        prettyPrincipal(principal),
-        autoScheduleMode,
-    ))
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> @inlineCallbacks
</span><del>-def action_setAutoAcceptGroup(rootResource, directory, store, principal, autoAcceptGroup):
-    if principal.record.recordType == &quot;groups&quot;:
-        print(&quot;Setting auto-accept-group for %s is not allowed.&quot; % (principal,))
-
-    elif principal.record.recordType == &quot;users&quot; and not config.Scheduling.Options.AutoSchedule.AllowUsers:
-        print(&quot;Setting auto-accept-group for %s is not allowed.&quot; % (principal,))
-
-    else:
-        groupPrincipal = principalForPrincipalID(autoAcceptGroup, directory=directory)
-        if groupPrincipal is None or groupPrincipal.record.recordType != &quot;groups&quot;:
-            print(&quot;Invalid principal ID: %s&quot; % (autoAcceptGroup,))
</del><ins>+def action_getAutoAcceptGroup(store, record):
+    if record.autoAcceptGroup:
+        groupRecord = yield record.service.recordWithUID(
+            record.autoAcceptGroup
+        )
+        if groupRecord is not None:
+            print(
+                &quot;Auto-accept-group for {record} is {group}&quot;.format(
+                    record=prettyRecord(record),
+                    group=prettyRecord(groupRecord),
+                )
+            )
</ins><span class="cx">         else:
</span><del>-            print(&quot;Setting auto-accept-group to %s for %s&quot; % (
-                prettyPrincipal(groupPrincipal),
-                prettyPrincipal(principal),
-            ))
-
-            (yield updateRecord(False, directory,
-                principal.record.recordType,
-                guid=principal.record.guid,
-                shortNames=principal.record.shortNames,
-                fullName=principal.record.fullName,
-                autoAcceptGroup=groupPrincipal.record.guid,
-                **principal.record.extras
-            ))
-
-
-
-def action_getAutoAcceptGroup(rootResource, directory, store, principal):
-    autoAcceptGroup = principal.getAutoAcceptGroup()
-    if autoAcceptGroup:
-        record = directory.recordWithGUID(autoAcceptGroup)
-        if record is not None:
-            groupPrincipal = directory.principalCollection.principalForUID(record.uid)
-            if groupPrincipal is not None:
-                print(&quot;Auto-accept-group for %s is %s&quot; % (
-                    prettyPrincipal(principal),
-                    prettyPrincipal(groupPrincipal),
-                ))
-                return
-        print(&quot;Invalid auto-accept-group assigned: %s&quot; % (autoAcceptGroup,))
</del><ins>+            print(
+                &quot;Invalid auto-accept-group assigned: {uid}&quot;.format(
+                    uid=record.autoAcceptGroup
+                )
+            )
</ins><span class="cx">     else:
</span><del>-        print(&quot;No auto-accept-group assigned to %s&quot; % (prettyPrincipal(principal),))
</del><ins>+        print(
+            &quot;No auto-accept-group assigned to {record}&quot;.format(
+                record=prettyRecord(record)
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> @inlineCallbacks
</span><del>-def action_setValue(rootResource, directory, store, principal, name, value):
-    print(&quot;Setting %s to %s for %s&quot; % (
-        name, value, prettyPrincipal(principal),
-    ))
</del><ins>+def action_setValue(store, record, name, value):
+    print(
+        &quot;Setting {name} to {value} for {record}&quot;.format(
+            name=name, value=value, record=prettyRecord(record),
+        )
+    )
+    # Get original fields
+    newFields = record.fields.copy()
</ins><span class="cx"> 
</span><del>-    principal.record.extras[attrMap[name][&quot;attr&quot;]] = value
-    (yield updateRecord(False, directory,
-        principal.record.recordType,
-        guid=principal.record.guid,
-        shortNames=principal.record.shortNames,
-        fullName=principal.record.fullName,
-        **principal.record.extras
-    ))
</del><ins>+    # Set new value
+    newFields[record.service.fieldName.lookupByName(name)] = value
</ins><span class="cx"> 
</span><ins>+    updatedRecord = DirectoryRecord(record.service, newFields)
+    yield record.service.updateRecords([updatedRecord], create=False)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-def action_getValue(rootResource, directory, store, principal, name):
-    print(&quot;%s for %s is %s&quot; % (
-        name,
-        prettyPrincipal(principal),
-        principal.record.extras[attrMap[name][&quot;attr&quot;]]
-    ))
</del><ins>+def action_getValue(store, record, name):
+    try:
+        value = record.fields[record.service.fieldName.lookupByName(name)]
+        print(
+            &quot;{name} for {record} is {value}&quot;.format(
+                name=name, record=prettyRecord(record), value=value
+            )
+        )
+    except KeyError:
+        print(
+            &quot;{name} is not set for {record}&quot;.format(
+                name=name, record=prettyRecord(record),
+            )
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> def abort(msg, status=1):
</span><span class="cx">     sys.stdout.write(&quot;%s\n&quot; % (msg,))
</span><span class="cx">     try:
</span><span class="lines">@@ -758,29 +775,23 @@
</span><span class="cx"> 
</span><span class="cx"> def parseCreationArgs(args):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    Look at the command line arguments for --add, and figure out which
-    one is the shortName and which one is the guid by attempting to make a
-    UUID object out of them.
</del><ins>+    Look at the command line arguments for --add, and simply assume the first
+    is full name, the second is short name, and the third is uid.  We can make
+    this fancier later.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    fullName = args[0]
-    shortName = None
-    guid = None
-    for arg in args[1:]:
-        if isUUID(arg):
-            if guid is not None:
-                # Both the 2nd and 3rd args are UUIDs.  The first one
-                # should be used for shortName.
-                shortName = guid
-            guid = arg
-        else:
-            shortName = arg
</del><ins>+    if len(args) != 3:
+        print(
+            &quot;When adding a principal, you must provide full-name, record-name, &quot;
+            &quot;and UID&quot;
+        )
+        sys.exit(64)
</ins><span class="cx"> 
</span><del>-    if len(args) == 3 and guid is None:
-        # both shortName and guid were specified but neither was a UUID
-        raise ValueError(&quot;Invalid value for guid&quot;)
</del><ins>+    fullName = args[0].decode(&quot;utf-8&quot;)
+    shortName = args[1].decode(&quot;utf-8&quot;)
+    uid = args[2].decode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><del>-    return fullName, shortName, guid
</del><ins>+    return fullName, shortName, uid
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -803,95 +814,20 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def printRecordList(records):
</span><del>-    results = [(record.fullName, record.shortNames[0], record.guid)
-        for record in records]
</del><ins>+    results = [
+        (record.displayName, record.recordType.name, record.uid, record.shortNames)
+        for record in records
+    ]
</ins><span class="cx">     results.sort()
</span><del>-    format = &quot;%-22s %-17s %s&quot;
-    print(format % (&quot;Full name&quot;, &quot;Record name&quot;, &quot;UUID&quot;))
-    print(format % (&quot;---------&quot;, &quot;-----------&quot;, &quot;----&quot;))
-    for fullName, shortName, guid in results:
-        print(format % (fullName, shortName, guid))
</del><ins>+    format = &quot;%-22s %-10s %-20s %s&quot;
+    print(format % (&quot;Full name&quot;, &quot;Type&quot;, &quot;UID&quot;, &quot;Short names&quot;))
+    print(format % (&quot;---------&quot;, &quot;----&quot;, &quot;---&quot;, &quot;-----------&quot;))
+    for fullName, recordType, uid, shortNames in results:
+        print(format % (fullName, recordType, uid, u&quot;, &quot;.join(shortNames)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@inlineCallbacks
-def updateRecord(create, directory, recordType, **kwargs):
-    &quot;&quot;&quot;
-    Create/update a record, including the extra work required to set the
-    autoSchedule bit in the augment record.
</del><span class="cx"> 
</span><del>-    If C{create} is true, the record is created, otherwise update the record
-    matching the guid in kwargs.
-    &quot;&quot;&quot;
</del><span class="cx"> 
</span><del>-    assignAutoSchedule = False
-    if &quot;autoSchedule&quot; in kwargs:
-        assignAutoSchedule = True
-        autoSchedule = kwargs[&quot;autoSchedule&quot;]
-        del kwargs[&quot;autoSchedule&quot;]
-    elif create:
-        assignAutoSchedule = True
-        autoSchedule = recordType in (&quot;locations&quot;, &quot;resources&quot;)
-
-    assignAutoScheduleMode = False
-    if &quot;autoScheduleMode&quot; in kwargs:
-        assignAutoScheduleMode = True
-        autoScheduleMode = kwargs[&quot;autoScheduleMode&quot;]
-        del kwargs[&quot;autoScheduleMode&quot;]
-    elif create:
-        assignAutoScheduleMode = True
-        autoScheduleMode = None
-
-    assignAutoAcceptGroup = False
-    if &quot;autoAcceptGroup&quot; in kwargs:
-        assignAutoAcceptGroup = True
-        autoAcceptGroup = kwargs[&quot;autoAcceptGroup&quot;]
-        del kwargs[&quot;autoAcceptGroup&quot;]
-    elif create:
-        assignAutoAcceptGroup = True
-        autoAcceptGroup = None
-
-    for key, value in kwargs.items():
-        if isinstance(value, unicode):
-            kwargs[key] = value.encode(&quot;utf-8&quot;)
-        elif isinstance(value, list):
-            newValue = [v.encode(&quot;utf-8&quot;) for v in value]
-            kwargs[key] = newValue
-
-    if create:
-        record = directory.createRecord(recordType, **kwargs)
-        kwargs['guid'] = record.guid
-    else:
-        try:
-            record = directory.updateRecord(recordType, **kwargs)
-        except NotImplementedError:
-            # Updating of directory information is not supported by underlying
-            # directory implementation, but allow augment information to be
-            # updated
-            record = directory.recordWithGUID(kwargs[&quot;guid&quot;])
-            pass
-
-    augmentService = directory.serviceForRecordType(recordType).augmentService
-    augmentRecord = (yield augmentService.getAugmentRecord(kwargs['guid'], recordType))
-
-    if assignAutoSchedule:
-        augmentRecord.autoSchedule = autoSchedule
-    if assignAutoScheduleMode:
-        augmentRecord.autoScheduleMode = autoScheduleMode
-    if assignAutoAcceptGroup:
-        augmentRecord.autoAcceptGroup = autoAcceptGroup
-    (yield augmentService.addAugmentRecords([augmentRecord]))
-    try:
-        directory.updateRecord(recordType, **kwargs)
-    except NotImplementedError:
-        # Updating of directory information is not supported by underlying
-        # directory implementation, but allow augment information to be
-        # updated
-        pass
-
-    returnValue(record)
-
-
-
</del><span class="cx"> if __name__ == &quot;__main__&quot;:
</span><span class="cx">     main()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolspurgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/purge.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/purge.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/purge.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,7 +19,6 @@
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tools import tables
</span><span class="cx"> from calendarserver.tools.cmdline import utilityMain, WorkerService
</span><del>-from calendarserver.tools.util import removeProxy
</del><span class="cx"> 
</span><span class="cx"> from getopt import getopt, GetoptError
</span><span class="cx"> 
</span><span class="lines">@@ -30,10 +29,9 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml
</span><del>-from twistedcaldav.directory.directory import DirectoryRecord
</del><ins>+# from twistedcaldav.directory.directory import DirectoryRecord
</ins><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.query.filter import Filter
</span><del>-from txdav.xml import element as davxml
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> import collections
</span><span class="lines">@@ -170,7 +168,7 @@
</span><span class="cx">         service.batchSize = batchSize
</span><span class="cx">         service.dryrun = dryrun
</span><span class="cx">         service.verbose = verbose
</span><del>-        result = (yield service.doWork())
</del><ins>+        result = yield service.doWork()
</ins><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -181,7 +179,7 @@
</span><span class="cx">             if self.verbose:
</span><span class="cx">                 print(&quot;(Dry run) Searching for old events...&quot;)
</span><span class="cx">             txn = self.store.newTransaction(label=&quot;Find old events&quot;)
</span><del>-            oldEvents = (yield txn.eventsOlderThan(self.cutoff))
</del><ins>+            oldEvents = yield txn.eventsOlderThan(self.cutoff)
</ins><span class="cx">             eventCount = len(oldEvents)
</span><span class="cx">             if self.verbose:
</span><span class="cx">                 if eventCount == 0:
</span><span class="lines">@@ -199,8 +197,8 @@
</span><span class="cx">         totalRemoved = 0
</span><span class="cx">         while numEventsRemoved:
</span><span class="cx">             txn = self.store.newTransaction(label=&quot;Remove old events&quot;)
</span><del>-            numEventsRemoved = (yield txn.removeOldEvents(self.cutoff, batchSize=self.batchSize))
-            (yield txn.commit())
</del><ins>+            numEventsRemoved = yield txn.removeOldEvents(self.cutoff, batchSize=self.batchSize)
+            yield txn.commit()
</ins><span class="cx">             if numEventsRemoved:
</span><span class="cx">                 totalRemoved += numEventsRemoved
</span><span class="cx">                 if self.verbose:
</span><span class="lines">@@ -360,7 +358,7 @@
</span><span class="cx">         service.batchSize = limit
</span><span class="cx">         service.dryrun = dryrun
</span><span class="cx">         service.verbose = verbose
</span><del>-        result = (yield service.doWork())
</del><ins>+        result = yield service.doWork()
</ins><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -368,20 +366,20 @@
</span><span class="cx">     def doWork(self):
</span><span class="cx"> 
</span><span class="cx">         if self.dryrun:
</span><del>-            orphans = (yield self._orphansDryRun())
</del><ins>+            orphans = yield self._orphansDryRun()
</ins><span class="cx">             if self.cutoff is not None:
</span><del>-                dropbox = (yield self._dropboxDryRun())
-                managed = (yield self._managedDryRun())
</del><ins>+                dropbox = yield self._dropboxDryRun()
+                managed = yield self._managedDryRun()
</ins><span class="cx">             else:
</span><span class="cx">                 dropbox = ()
</span><span class="cx">                 managed = ()
</span><span class="cx"> 
</span><span class="cx">             returnValue(self._dryRunSummary(orphans, dropbox, managed))
</span><span class="cx">         else:
</span><del>-            total = (yield self._orphansPurge())
</del><ins>+            total = yield self._orphansPurge()
</ins><span class="cx">             if self.cutoff is not None:
</span><del>-                total += (yield self._dropboxPurge())
-                total += (yield self._managedPurge())
</del><ins>+                total += yield self._dropboxPurge()
+                total += yield self._managedPurge()
</ins><span class="cx">             returnValue(total)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -391,7 +389,7 @@
</span><span class="cx">         if self.verbose:
</span><span class="cx">             print(&quot;(Dry run) Searching for orphaned attachments...&quot;)
</span><span class="cx">         txn = self.store.newTransaction(label=&quot;Find orphaned attachments&quot;)
</span><del>-        orphans = (yield txn.orphanedAttachments(self.uuid))
</del><ins>+        orphans = yield txn.orphanedAttachments(self.uuid)
</ins><span class="cx">         returnValue(orphans)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -401,7 +399,7 @@
</span><span class="cx">         if self.verbose:
</span><span class="cx">             print(&quot;(Dry run) Searching for old dropbox attachments...&quot;)
</span><span class="cx">         txn = self.store.newTransaction(label=&quot;Find old dropbox attachments&quot;)
</span><del>-        cutoffs = (yield txn.oldDropboxAttachments(self.cutoff, self.uuid))
</del><ins>+        cutoffs = yield txn.oldDropboxAttachments(self.cutoff, self.uuid)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         returnValue(cutoffs)
</span><span class="lines">@@ -413,7 +411,7 @@
</span><span class="cx">         if self.verbose:
</span><span class="cx">             print(&quot;(Dry run) Searching for old managed attachments...&quot;)
</span><span class="cx">         txn = self.store.newTransaction(label=&quot;Find old managed attachments&quot;)
</span><del>-        cutoffs = (yield txn.oldManagedAttachments(self.cutoff, self.uuid))
</del><ins>+        cutoffs = yield txn.oldManagedAttachments(self.cutoff, self.uuid)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         returnValue(cutoffs)
</span><span class="lines">@@ -495,7 +493,7 @@
</span><span class="cx">         totalRemoved = 0
</span><span class="cx">         while numOrphansRemoved:
</span><span class="cx">             txn = self.store.newTransaction(label=&quot;Remove orphaned attachments&quot;)
</span><del>-            numOrphansRemoved = (yield txn.removeOrphanedAttachments(self.uuid, batchSize=self.batchSize))
</del><ins>+            numOrphansRemoved = yield txn.removeOrphanedAttachments(self.uuid, batchSize=self.batchSize)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             if numOrphansRemoved:
</span><span class="cx">                 totalRemoved += numOrphansRemoved
</span><span class="lines">@@ -526,7 +524,7 @@
</span><span class="cx">         totalRemoved = 0
</span><span class="cx">         while numOldRemoved:
</span><span class="cx">             txn = self.store.newTransaction(label=&quot;Remove old dropbox attachments&quot;)
</span><del>-            numOldRemoved = (yield txn.removeOldDropboxAttachments(self.cutoff, self.uuid, batchSize=self.batchSize))
</del><ins>+            numOldRemoved = yield txn.removeOldDropboxAttachments(self.cutoff, self.uuid, batchSize=self.batchSize)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             if numOldRemoved:
</span><span class="cx">                 totalRemoved += numOldRemoved
</span><span class="lines">@@ -557,7 +555,7 @@
</span><span class="cx">         totalRemoved = 0
</span><span class="cx">         while numOldRemoved:
</span><span class="cx">             txn = self.store.newTransaction(label=&quot;Remove old managed attachments&quot;)
</span><del>-            numOldRemoved = (yield txn.removeOldManagedAttachments(self.cutoff, self.uuid, batchSize=self.batchSize))
</del><ins>+            numOldRemoved = yield txn.removeOldManagedAttachments(self.cutoff, self.uuid, batchSize=self.batchSize)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             if numOldRemoved:
</span><span class="cx">                 totalRemoved += numOldRemoved
</span><span class="lines">@@ -697,7 +695,7 @@
</span><span class="cx">         service.doimplicit = doimplicit
</span><span class="cx">         service.proxies = proxies
</span><span class="cx">         service.when = when
</span><del>-        result = (yield service.doWork())
</del><ins>+        result = yield service.doWork()
</ins><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -711,10 +709,8 @@
</span><span class="cx"> 
</span><span class="cx">         total = 0
</span><span class="cx"> 
</span><del>-        allAssignments = {}
-
</del><span class="cx">         for uid in self.uids:
</span><del>-            count, allAssignments[uid] = (yield self._purgeUID(uid))
</del><ins>+            count = yield self._purgeUID(uid)
</ins><span class="cx">             total += count
</span><span class="cx"> 
</span><span class="cx">         if self.verbose:
</span><span class="lines">@@ -724,7 +720,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 print(&quot;Modified or deleted %s&quot; % (amount,))
</span><span class="cx"> 
</span><del>-        returnValue((total, allAssignments,))
</del><ins>+        returnValue(total)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -734,30 +730,26 @@
</span><span class="cx">             self.when = DateTime.getNowUTC()
</span><span class="cx"> 
</span><span class="cx">         # Does the record exist?
</span><del>-        record = self.directory.recordWithUID(uid)
-        if record is None:
</del><ins>+        record = yield self.directory.recordWithUID(uid)
+        # if record is None:
</ins><span class="cx">             # The user has already been removed from the directory service.  We
</span><span class="cx">             # need to fashion a temporary, fake record
</span><span class="cx"> 
</span><span class="cx">             # FIXME: probably want a more elegant way to accomplish this,
</span><span class="cx">             # since it requires the aggregate directory to examine these first:
</span><del>-            record = DirectoryRecord(self.directory, &quot;users&quot;, uid, shortNames=(uid,), enabledForCalendaring=True)
-            self.directory._tmpRecords[&quot;shortNames&quot;][uid] = record
-            self.directory._tmpRecords[&quot;uids&quot;][uid] = record
</del><ins>+            # record = DirectoryRecord(self.directory, &quot;users&quot;, uid, shortNames=(uid,), enabledForCalendaring=True)
+            # self.directory._tmpRecords[&quot;shortNames&quot;][uid] = record
+            # self.directory._tmpRecords[&quot;uids&quot;][uid] = record
</ins><span class="cx"> 
</span><span class="cx">         # Override augments settings for this record
</span><del>-        record.enabled = True
-        record.enabledForCalendaring = True
-        record.enabledForAddressBooks = True
</del><ins>+        record.hasCalendars = True
+        record.hasContacts = True
</ins><span class="cx"> 
</span><del>-        cua = &quot;urn:uuid:%s&quot; % (uid,)
</del><ins>+        cua = record.canonicalCalendarUserAddress()
</ins><span class="cx"> 
</span><del>-        principalCollection = self.directory.principalCollection
-        principal = principalCollection.principalForRecord(record)
-
</del><span class="cx">         # See if calendar home is provisioned
</span><span class="cx">         txn = self.store.newTransaction()
</span><del>-        storeCalHome = (yield txn.calendarHomeWithUID(uid))
</del><ins>+        storeCalHome = yield txn.calendarHomeWithUID(uid)
</ins><span class="cx">         calHomeProvisioned = storeCalHome is not None
</span><span class="cx"> 
</span><span class="cx">         # If in &quot;completely&quot; mode, unshare collections, remove notifications
</span><span class="lines">@@ -767,24 +759,23 @@
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         count = 0
</span><del>-        assignments = []
</del><span class="cx"> 
</span><span class="cx">         if calHomeProvisioned:
</span><del>-            count = (yield self._cancelEvents(txn, uid, cua))
</del><ins>+            count = yield self._cancelEvents(txn, uid, cua)
</ins><span class="cx"> 
</span><span class="cx">         # Remove empty calendar collections (and calendar home if no more
</span><span class="cx">         # calendars)
</span><span class="cx">         yield self._removeCalendarHome(uid)
</span><span class="cx"> 
</span><span class="cx">         # Remove VCards
</span><del>-        count += (yield self._removeAddressbookHome(uid))
</del><ins>+        count += yield self._removeAddressbookHome(uid)
</ins><span class="cx"> 
</span><span class="cx">         if self.proxies and not self.dryrun:
</span><span class="cx">             if self.verbose:
</span><span class="cx">                 print(&quot;Deleting any proxy assignments&quot;)
</span><del>-            assignments = (yield self._purgeProxyAssignments(principal))
</del><ins>+            yield self._purgeProxyAssignments(self.store, record)
</ins><span class="cx"> 
</span><del>-        returnValue((count, assignments))
</del><ins>+        returnValue(count)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -799,13 +790,13 @@
</span><span class="cx">                 else:
</span><span class="cx">                     print(&quot;Unsharing: %s&quot; % (child.name(),))
</span><span class="cx">             if not self.dryrun:
</span><del>-                (yield child.unshare())
</del><ins>+                yield child.unshare()
</ins><span class="cx"> 
</span><span class="cx">         if not self.dryrun:
</span><del>-            (yield storeCalHome.removeUnacceptedShares())
-            notificationHome = (yield txn.notificationsWithUID(storeCalHome.uid()))
</del><ins>+            yield storeCalHome.removeUnacceptedShares()
+            notificationHome = yield txn.notificationsWithUID(storeCalHome.uid())
</ins><span class="cx">             if notificationHome is not None:
</span><del>-                (yield notificationHome.remove())
</del><ins>+                yield notificationHome.remove()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -826,15 +817,15 @@
</span><span class="cx"> 
</span><span class="cx">         count = 0
</span><span class="cx">         txn = self.store.newTransaction()
</span><del>-        storeCalHome = (yield txn.calendarHomeWithUID(uid))
-        calendarNames = (yield storeCalHome.listCalendars())
</del><ins>+        storeCalHome = yield txn.calendarHomeWithUID(uid)
+        calendarNames = yield storeCalHome.listCalendars()
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         for calendarName in calendarNames:
</span><span class="cx"> 
</span><span class="cx">             txn = self.store.newTransaction(authz_uid=uid)
</span><del>-            storeCalHome = (yield txn.calendarHomeWithUID(uid))
-            calendar = (yield storeCalHome.calendarWithName(calendarName))
</del><ins>+            storeCalHome = yield txn.calendarHomeWithUID(uid)
+            calendar = yield storeCalHome.calendarWithName(calendarName)
</ins><span class="cx">             childNames = []
</span><span class="cx"> 
</span><span class="cx">             if self.completely:
</span><span class="lines">@@ -850,17 +841,17 @@
</span><span class="cx">             for childName in childNames:
</span><span class="cx"> 
</span><span class="cx">                 txn = self.store.newTransaction(authz_uid=uid)
</span><del>-                storeCalHome = (yield txn.calendarHomeWithUID(uid))
-                calendar = (yield storeCalHome.calendarWithName(calendarName))
</del><ins>+                storeCalHome = yield txn.calendarHomeWithUID(uid)
+                calendar = yield storeCalHome.calendarWithName(calendarName)
</ins><span class="cx"> 
</span><span class="cx">                 try:
</span><del>-                    childResource = (yield calendar.calendarObjectWithName(childName))
</del><ins>+                    childResource = yield calendar.calendarObjectWithName(childName)
</ins><span class="cx"> 
</span><span class="cx">                     # Always delete inbox items
</span><span class="cx">                     if self.completely or calendar.isInbox():
</span><span class="cx">                         action = self.CANCELEVENT_SHOULD_DELETE
</span><span class="cx">                     else:
</span><del>-                        event = (yield childResource.componentForUser())
</del><ins>+                        event = yield childResource.componentForUser()
</ins><span class="cx">                         action = self._cancelEvent(event, self.when, cua)
</span><span class="cx"> 
</span><span class="cx">                     uri = &quot;/calendars/__uids__/%s/%s/%s&quot; % (storeCalHome.uid(), calendar.name(), childName)
</span><span class="lines">@@ -921,7 +912,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Remove empty calendar collections (and calendar home if no more
</span><span class="cx">             # calendars)
</span><del>-            storeCalHome = (yield txn.calendarHomeWithUID(uid))
</del><ins>+            storeCalHome = yield txn.calendarHomeWithUID(uid)
</ins><span class="cx">             if storeCalHome is not None:
</span><span class="cx">                 calendars = list((yield storeCalHome.calendars()))
</span><span class="cx">                 remainingCalendars = len(calendars)
</span><span class="lines">@@ -947,7 +938,7 @@
</span><span class="cx">                         else:
</span><span class="cx">                             print(&quot;Deleting calendar home&quot;)
</span><span class="cx">                     if not self.dryrun:
</span><del>-                        (yield storeCalHome.remove())
</del><ins>+                        yield storeCalHome.remove()
</ins><span class="cx"> 
</span><span class="cx">             # Commit
</span><span class="cx">             yield txn.commit()
</span><span class="lines">@@ -966,7 +957,7 @@
</span><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             # Remove VCards
</span><del>-            storeAbHome = (yield txn.addressbookHomeWithUID(uid))
</del><ins>+            storeAbHome = yield txn.addressbookHomeWithUID(uid)
</ins><span class="cx">             if storeAbHome is not None:
</span><span class="cx">                 for abColl in list((yield storeAbHome.addressbooks())):
</span><span class="cx">                     for card in list((yield abColl.addressbookObjects())):
</span><span class="lines">@@ -978,7 +969,7 @@
</span><span class="cx">                             else:
</span><span class="cx">                                 print(&quot;Deleting: %s&quot; % (uri,))
</span><span class="cx">                         if not self.dryrun:
</span><del>-                            (yield card.remove())
</del><ins>+                            yield card.remove()
</ins><span class="cx">                         count += 1
</span><span class="cx">                     abName = abColl.name()
</span><span class="cx">                     if self.verbose:
</span><span class="lines">@@ -988,10 +979,14 @@
</span><span class="cx">                             print(&quot;Deleting addressbook: %s&quot; % (abName,))
</span><span class="cx">                     if not self.dryrun:
</span><span class="cx">                         # Also remove the addressbook collection itself
</span><ins>+&lt;&lt;&lt;&lt;&lt;&lt;&lt; .working
</ins><span class="cx">                         if abColl.owned():
</span><span class="cx">                             yield storeAbHome.removeChildWithName(abName)
</span><span class="cx">                         else:
</span><span class="cx">                             yield abColl.unshare()
</span><ins>+=======
+                        yield storeAbHome.removeChildWithName(abColl.name())
+&gt;&gt;&gt;&gt;&gt;&gt;&gt; .merge-right.r13157
</ins><span class="cx"> 
</span><span class="cx">                 if self.verbose:
</span><span class="cx">                     if self.dryrun:
</span><span class="lines">@@ -999,7 +994,7 @@
</span><span class="cx">                     else:
</span><span class="cx">                         print(&quot;Deleting addressbook home&quot;)
</span><span class="cx">                 if not self.dryrun:
</span><del>-                    (yield storeAbHome.remove())
</del><ins>+                    yield storeAbHome.remove()
</ins><span class="cx"> 
</span><span class="cx">             # Commit
</span><span class="cx">             yield txn.commit()
</span><span class="lines">@@ -1113,22 +1108,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _purgeProxyAssignments(self, principal):
</del><ins>+    def _purgeProxyAssignments(self, store, record):
</ins><span class="cx"> 
</span><del>-        assignments = []
-
-        for proxyType in (&quot;read&quot;, &quot;write&quot;):
-
-            proxyFor = (yield principal.proxyFor(proxyType == &quot;write&quot;))
-            for other in proxyFor:
-                assignments.append((principal.record.uid, proxyType, other.record.uid))
-                (yield removeProxy(self.root, self.directory, self.store, other, principal))
-
-            subPrincipal = principal.getChild(&quot;calendar-proxy-&quot; + proxyType)
-            proxies = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
-            for other in proxies.children:
-                assignments.append((str(other).split(&quot;/&quot;)[3], proxyType, principal.record.uid))
-
-            (yield subPrincipal.writeProperty(davxml.GroupMemberSet(), None))
-
-        returnValue(assignments)
</del><ins>+        txn = store.newTransaction()
+        for readWrite in (True, False):
+            yield txn.removeDelegates(record.uid, readWrite)
+            yield txn.removeDelegateGroupss(record.uid, readWrite)
+        yield txn.commit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolspushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/push.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/push.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/push.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,6 +20,7 @@
</span><span class="cx"> from argparse import ArgumentParser
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> import time
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -59,7 +60,7 @@
</span><span class="cx"> def displayAPNSubscriptions(store, directory, root, users):
</span><span class="cx">     for user in users:
</span><span class="cx">         print
</span><del>-        record = directory.recordWithShortName(&quot;users&quot;, user)
</del><ins>+        record = yield directory.recordWithShortName(RecordType.user, user)
</ins><span class="cx">         if record is not None:
</span><span class="cx">             print(&quot;User %s (%s)...&quot; % (user, record.uid))
</span><span class="cx">             txn = store.newTransaction(label=&quot;Display APN Subscriptions&quot;)
</span><span class="lines">@@ -81,7 +82,7 @@
</span><span class="cx">                     else:
</span><span class="cx">                         uid = path
</span><span class="cx">                         collection = None
</span><del>-                    record = directory.recordWithUID(uid)
</del><ins>+                    record = yield directory.recordWithUID(uid)
</ins><span class="cx">                     user = record.shortNames[0]
</span><span class="cx">                     if collection:
</span><span class="cx">                         print(&quot;...is subscribed to a share from %s's %s home&quot; % (user, resource),)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsresourcespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/resources.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/resources.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/resources.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,38 +15,36 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><ins>+
</ins><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><ins>+__all__ = [
+    &quot;migrateResources&quot;,
+]
+
+from getopt import getopt, GetoptError
+from grp import getgrnam
</ins><span class="cx"> import os
</span><ins>+from pwd import getpwnam
</ins><span class="cx"> import sys
</span><del>-from grp import getgrnam
-from pwd import getpwnam
-from getopt import getopt, GetoptError
</del><span class="cx"> 
</span><ins>+from calendarserver.tools.util import (
+    loadConfig, setupMemcached, checkDirectory
+)
+from twext.python.log import Logger, StandardIOObserver
</ins><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> from twisted.python.util import switchUID
</span><del>-
-from twext.python.log import Logger, StandardIOObserver
-
</del><span class="cx"> from twistedcaldav.config import config, ConfigurationError
</span><span class="cx"> from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryService, DirectoryError
</span><span class="cx"> from twistedcaldav.directory.xmlfile import XMLDirectoryService
</span><ins>+from txdav.who.util import directoryFromConfig
</ins><span class="cx"> 
</span><del>-from calendarserver.platform.darwin.od import dsattributes
-from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, checkDirectory
-
</del><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-__all__ = [
-    &quot;migrateResources&quot;,
-]
-
-
-
</del><span class="cx"> def usage():
</span><span class="cx"> 
</span><span class="cx">     name = os.path.basename(sys.argv[0])
</span><span class="lines">@@ -141,28 +139,37 @@
</span><span class="cx">         os.umask(config.umask)
</span><span class="cx"> 
</span><span class="cx">         # Configure memcached client settings prior to setting up resource
</span><del>-        # hierarchy (in getDirectory)
</del><ins>+        # hierarchy
</ins><span class="cx">         setupMemcached(config)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            config.directory = getDirectory()
</del><ins>+            config.directory = directoryFromConfig(config)
</ins><span class="cx">         except DirectoryError, e:
</span><span class="cx">             abort(e)
</span><span class="cx"> 
</span><span class="cx">     except ConfigurationError, e:
</span><span class="cx">         abort(e)
</span><span class="cx"> 
</span><ins>+    # FIXME: this all has to change:
</ins><span class="cx">     # Find the opendirectory service
</span><span class="cx">     userService = config.directory.serviceForRecordType(&quot;users&quot;)
</span><span class="cx">     resourceService = config.directory.serviceForRecordType(&quot;resources&quot;)
</span><del>-    if (not isinstance(userService, OpenDirectoryService) or
-        not isinstance(resourceService, XMLDirectoryService)):
-        abort(&quot;This script only migrates resources and locations from OpenDirectory to XML; this calendar server does not have such a configuration.&quot;)
</del><ins>+    if (
+        not isinstance(userService, OpenDirectoryService) or
+        not isinstance(resourceService, XMLDirectoryService)
+    ):
+        abort(
+            &quot;This script only migrates resources and locations from &quot;
+            &quot;OpenDirectory to XML; this calendar server does not have such a &quot;
+            &quot;configuration.&quot;
+        )
</ins><span class="cx"> 
</span><span class="cx">     #
</span><span class="cx">     # Start the reactor
</span><span class="cx">     #
</span><del>-    reactor.callLater(0, migrate, userService, resourceService, verbose=verbose)
</del><ins>+    reactor.callLater(
+        0, migrate, userService, resourceService, verbose=verbose
+    )
</ins><span class="cx">     reactor.run()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -186,8 +193,8 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     attrs = [
</span><del>-        dsattributes.kDS1AttrGeneratedUID,
-        dsattributes.kDS1AttrDistinguishedName,
</del><ins>+        &quot;dsAttrTypeStandard:GeneratedUID&quot;,
+        &quot;dsAttrTypeStandard:RealName&quot;,
</ins><span class="cx">     ]
</span><span class="cx"> 
</span><span class="cx">     if verbose:
</span><span class="lines">@@ -207,24 +214,26 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def migrateResources(sourceService, destService, autoSchedules=None,
-    queryMethod=queryForType, verbose=False):
</del><ins>+def migrateResources(
+    sourceService, destService, autoSchedules=None,
+    queryMethod=queryForType, verbose=False
+):
</ins><span class="cx"> 
</span><span class="cx">     directoryRecords = []
</span><span class="cx">     augmentRecords = []
</span><span class="cx"> 
</span><span class="cx">     for recordTypeOD, recordType in (
</span><del>-        (dsattributes.kDSStdRecordTypeResources, DirectoryService.recordType_resources),
-        (dsattributes.kDSStdRecordTypePlaces, DirectoryService.recordType_locations),
</del><ins>+        (&quot;dsRecTypeStandard:Resources&quot;, DirectoryService.recordType_resources),
+        (&quot;dsRecTypeStandard:Places&quot;, DirectoryService.recordType_locations),
</ins><span class="cx">     ):
</span><span class="cx">         data = queryMethod(sourceService, recordTypeOD, verbose=verbose)
</span><span class="cx">         for recordName, val in data:
</span><del>-            guid = val.get(dsattributes.kDS1AttrGeneratedUID, None)
-            fullName = val.get(dsattributes.kDS1AttrDistinguishedName, None)
</del><ins>+            guid = val.get(&quot;dsAttrTypeStandard:GeneratedUID&quot;, None)
+            fullName = val.get(&quot;dsAttrTypeStandard:RealName&quot;, None)
</ins><span class="cx">             if guid and fullName:
</span><span class="cx">                 if not recordName:
</span><span class="cx">                     recordName = guid
</span><del>-                record = destService.recordWithGUID(guid)
</del><ins>+                record = yield destService.recordWithGUID(guid)
</ins><span class="cx">                 if record is None:
</span><span class="cx">                     if verbose:
</span><span class="cx">                         print(&quot;Migrating %s (%s)&quot; % (fullName, recordType))
</span><span class="lines">@@ -233,23 +242,29 @@
</span><span class="cx">                         autoSchedule = autoSchedules.get(guid, 1)
</span><span class="cx">                     else:
</span><span class="cx">                         autoSchedule = True
</span><del>-                    augmentRecord = (yield destService.augmentService.getAugmentRecord(guid, recordType))
-                    augmentRecord.autoSchedule = autoSchedule
-                    augmentRecords.append(augmentRecord)
-
-                    directoryRecords.append(
-                        (recordType,
-                            {
-                                &quot;guid&quot; : guid,
-                                &quot;shortNames&quot; : [recordName],
-                                &quot;fullName&quot; : fullName,
-                            }
</del><ins>+                    augmentRecord = (
+                        yield destService.augmentService.getAugmentRecord(
+                            guid, recordType
</ins><span class="cx">                         )
</span><span class="cx">                     )
</span><ins>+                    if autoSchedule:
+                        augmentRecord.autoScheduleMode = &quot;automatic&quot;
+                    else:
+                        augmentRecord.autoScheduleMode = &quot;none&quot;
+                    augmentRecords.append(augmentRecord)
</ins><span class="cx"> 
</span><ins>+                    directoryRecords.append((
+                        recordType,
+                        {
+                            &quot;guid&quot;: guid,
+                            &quot;shortNames&quot;: [recordName],
+                            &quot;fullName&quot;: fullName,
+                        }
+                    ))
+
</ins><span class="cx">     destService.createRecords(directoryRecords)
</span><span class="cx"> 
</span><del>-    (yield destService.augmentService.addAugmentRecords(augmentRecords))
</del><ins>+    yield destService.augmentService.addAugmentRecords(augmentRecords)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsshelldirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/directory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/directory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/directory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -83,16 +83,22 @@
</span><span class="cx">     add(&quot;First Name&quot;, record.firstName)
</span><span class="cx">     add(&quot;Last Name&quot; , record.lastName )
</span><span class="cx"> 
</span><del>-    for email in record.emailAddresses:
-        add(&quot;Email Address&quot;, email)
</del><ins>+    try:
+        for email in record.emailAddresses:
+            add(&quot;Email Address&quot;, email)
+    except AttributeError:
+        pass
</ins><span class="cx"> 
</span><del>-    for cua in record.calendarUserAddresses:
-        add(&quot;Calendar User Address&quot;, cua)
</del><ins>+    try:
+        for cua in record.calendarUserAddresses:
+            add(&quot;Calendar User Address&quot;, cua)
+    except AttributeError:
+        pass
</ins><span class="cx"> 
</span><span class="cx">     add(&quot;Server ID&quot;           , record.serverID)
</span><span class="cx">     add(&quot;Enabled&quot;             , record.enabled)
</span><del>-    add(&quot;Enabled for Calendar&quot;, record.enabledForCalendaring)
-    add(&quot;Enabled for Contacts&quot;, record.enabledForAddressBooks)
</del><ins>+    add(&quot;Enabled for Calendar&quot;, record.hasCalendars)
+    add(&quot;Enabled for Contacts&quot;, record.hasContacts)
</ins><span class="cx"> 
</span><span class="cx">     return succeed(table.toString())
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsshellterminalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/terminal.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/terminal.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/terminal.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -51,7 +51,6 @@
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tools.cmdline import utilityMain, WorkerService
</span><del>-from calendarserver.tools.util import getDirectory
</del><span class="cx"> from calendarserver.tools.shell.cmd import Commands, UsageError as CommandUsageError
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -116,9 +115,9 @@
</span><span class="cx">     @type config: L{twistedcaldav.config.Config}
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def __init__(self, store, directory, options, reactor, config):
</del><ins>+    def __init__(self, store, options, reactor, config):
</ins><span class="cx">         super(ShellService, self).__init__(store)
</span><del>-        self.directory = directory
</del><ins>+        self.directory = store.directoryService()
</ins><span class="cx">         self.options = options
</span><span class="cx">         self.reactor = reactor
</span><span class="cx">         self.config = config
</span><span class="lines">@@ -434,8 +433,7 @@
</span><span class="cx"> 
</span><span class="cx">     def makeService(store):
</span><span class="cx">         from twistedcaldav.config import config
</span><del>-        directory = getDirectory()
-        return ShellService(store, directory, options, reactor, config)
</del><ins>+        return ShellService(store, options, reactor, config)
</ins><span class="cx"> 
</span><span class="cx">     print(&quot;Initializing shell...&quot;)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsshelltesttest_vfspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/test/test_vfs.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/test/test_vfs.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/test/test_vfs.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,32 +18,53 @@
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks
</span><span class="cx"> 
</span><ins>+# from twext.who.test.test_xml import xmlService
+
+# from txdav.common.datastore.test.util import buildStore
+
</ins><span class="cx"> from calendarserver.tools.shell.vfs import ListEntry
</span><span class="cx"> from calendarserver.tools.shell.vfs import File, Folder
</span><del>-from calendarserver.tools.shell.vfs import UIDsFolder
-from calendarserver.tools.shell.terminal import ShellService
-from twistedcaldav.directory.test.test_xmlfile import XMLFileBase
-from txdav.common.datastore.test.util import buildStore
</del><ins>+# from calendarserver.tools.shell.vfs import UIDsFolder
+# from calendarserver.tools.shell.terminal import ShellService
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class TestListEntry(TestCase):
</span><span class="cx">     def test_toString(self):
</span><del>-        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;).toString(), &quot;thingo&quot;)
-        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(), &quot;thingo&quot;)
-        self.assertEquals(ListEntry(None, Folder, &quot;thingo&quot;).toString(), &quot;thingo/&quot;)
-        self.assertEquals(ListEntry(None, Folder, &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(), &quot;thingo/&quot;)
</del><ins>+        self.assertEquals(
+            ListEntry(None, File, &quot;thingo&quot;).toString(),
+            &quot;thingo&quot;
+        )
+        self.assertEquals(
+            ListEntry(None, File, &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(),
+            &quot;thingo&quot;
+        )
+        self.assertEquals(
+            ListEntry(None, Folder, &quot;thingo&quot;).toString(),
+            &quot;thingo/&quot;
+        )
+        self.assertEquals(
+            ListEntry(None, Folder, &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(),
+            &quot;thingo/&quot;
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_fieldNamesImplicit(self):
</span><span class="cx">         # This test assumes File doesn't set list.fieldNames.
</span><span class="cx">         assert not hasattr(File.list, &quot;fieldNames&quot;)
</span><span class="cx"> 
</span><del>-        self.assertEquals(set(ListEntry(File(None, ()), File, &quot;thingo&quot;).fieldNames), set((&quot;Name&quot;,)))
</del><ins>+        self.assertEquals(
+            set(ListEntry(File(None, ()), File, &quot;thingo&quot;).fieldNames),
+            set((&quot;Name&quot;,))
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_fieldNamesExplicit(self):
</span><span class="cx">         def fieldNames(fileClass):
</span><del>-            return ListEntry(fileClass(None, ()), fileClass, &quot;thingo&quot;, Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;)
</del><ins>+            return ListEntry(
+                fileClass(None, ()), fileClass, &quot;thingo&quot;,
+                Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;
+            )
</ins><span class="cx"> 
</span><span class="cx">         # Full list
</span><span class="cx">         class MyFile1(File):
</span><span class="lines">@@ -83,14 +104,24 @@
</span><span class="cx"> 
</span><span class="cx">         # Name first, rest sorted by field name
</span><span class="cx">         self.assertEquals(
</span><del>-            tuple(ListEntry(File(None, ()), File, &quot;thingo&quot;, Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;).toFields()),
</del><ins>+            tuple(
+                ListEntry(
+                    File(None, ()), File, &quot;thingo&quot;,
+                    Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;
+                ).toFields()
+            ),
</ins><span class="cx">             (&quot;thingo&quot;, &quot;Coconut&quot;, &quot;Hard&quot;)
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_toFieldsExplicit(self):
</span><span class="cx">         def fields(fileClass):
</span><del>-            return tuple(ListEntry(fileClass(None, ()), fileClass, &quot;thingo&quot;, Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;).toFields())
</del><ins>+            return tuple(
+                ListEntry(
+                    fileClass(None, ()), fileClass, &quot;thingo&quot;,
+                    Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;
+                ).toFields()
+            )
</ins><span class="cx"> 
</span><span class="cx">         # Full list
</span><span class="cx">         class MyFile1(File):
</span><span class="lines">@@ -125,34 +156,23 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DirectoryStubber(XMLFileBase):
-    &quot;&quot;&quot;
-    Object which creates a stub L{IDirectoryService}.
-    &quot;&quot;&quot;
-    def __init__(self, testCase):
-        self.testCase = testCase
-
-
-    def mktemp(self):
-        return self.testCase.mktemp()
-
-
-
</del><span class="cx"> class UIDsFolderTests(TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     L{UIDsFolder} contains all principals and is keyed by UID.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def setUp(self):
-        &quot;&quot;&quot;
-        Create a L{UIDsFolder}.
-        &quot;&quot;&quot;
-        directory = DirectoryStubber(self).service()
-        self.svc = ShellService(store=(yield buildStore(self, None, directoryService=directory)),
-                                directory=directory,
-                                options=None, reactor=None, config=None)
-        self.folder = UIDsFolder(self.svc, ())
</del><ins>+    # @inlineCallbacks
+    # def setUp(self):
+    #     &quot;&quot;&quot;
+    #     Create a L{UIDsFolder}.
+    #     &quot;&quot;&quot;
+    #     directory = xmlService(self.mktemp())
+    #     self.svc = ShellService(
+    #         store=(yield buildStore(self, None, directoryService=directory)),
+    #         directory=directory,
+    #         options=None, reactor=None, config=None
+    #     )
+    #     self.folder = UIDsFolder(self.svc, ())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -171,8 +191,20 @@
</span><span class="cx">         listing = list((yield self.folder.list()))
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             [x.fields for x in listing],
</span><del>-            [{&quot;Record Type&quot;: &quot;users&quot;, &quot;Short Name&quot;: &quot;wsanchez&quot;,
-              &quot;Full Name&quot;: &quot;Wilfredo Sanchez&quot;, &quot;Name&quot;: wsanchez},
-              {&quot;Record Type&quot;: &quot;users&quot;, &quot;Short Name&quot;: &quot;dreid&quot;,
-              &quot;Full Name&quot;: &quot;David Reid&quot;, &quot;Name&quot;: dreid}]
</del><ins>+            [
+                {
+                    &quot;Record Type&quot;: &quot;users&quot;,
+                    &quot;Short Name&quot;: &quot;wsanchez&quot;,
+                    &quot;Full Name&quot;: &quot;Wilfredo Sanchez&quot;,
+                    &quot;Name&quot;: wsanchez
+                },
+                {
+                    &quot;Record Type&quot;: &quot;users&quot;,
+                    &quot;Short Name&quot;: &quot;dreid&quot;,
+                    &quot;Full Name&quot;: &quot;David Reid&quot;,
+                    &quot;Name&quot;: dreid
+                },
+            ]
</ins><span class="cx">         )
</span><ins>+
+    test_list.todo = &quot;setup() needs to be reimplemented&quot;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsshellvfspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/vfs.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/vfs.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/shell/vfs.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -334,7 +334,9 @@
</span><span class="cx">     def list(self):
</span><span class="cx">         names = set()
</span><span class="cx"> 
</span><del>-        for record in self.service.directory.listRecords(self.recordType):
</del><ins>+        for record in self.service.directory.recordsWithRecordType(
+            self.recordType
+        ):
</ins><span class="cx">             for shortName in record.shortNames:
</span><span class="cx">                 if shortName in names:
</span><span class="cx">                     continue
</span><span class="lines">@@ -411,7 +413,7 @@
</span><span class="cx">                 if (
</span><span class="cx">                     self.record is not None and
</span><span class="cx">                     self.service.config.EnableCalDAV and
</span><del>-                    self.record.enabledForCalendaring
</del><ins>+                    self.record.hasCalendars
</ins><span class="cx">                 ):
</span><span class="cx">                     create = True
</span><span class="cx">                 else:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayaugmentsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/augments.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/augments.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/augments.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,12 +20,6 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;augments&gt;
</span><span class="cx">   &lt;record&gt;
</span><del>-    &lt;uid&gt;Default&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
</del><span class="cx">     &lt;uid&gt;user01&lt;/uid&gt;
</span><span class="cx">     &lt;enable&gt;true&lt;/enable&gt;
</span><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewaycaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/caldavd.plist (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/caldavd.plist        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/caldavd.plist        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -85,29 +85,29 @@
</span><span class="cx">     &lt;key&gt;ServerRoot&lt;/key&gt;
</span><span class="cx">     &lt;string&gt;%(ServerRoot)s&lt;/string&gt;
</span><span class="cx"> 
</span><ins>+    &lt;!-- Data root --&gt;
+    &lt;key&gt;DataRoot&lt;/key&gt;
+    &lt;string&gt;%(DataRoot)s&lt;/string&gt;
+
</ins><span class="cx">     &lt;!-- Database root --&gt;
</span><span class="cx">     &lt;key&gt;DatabaseRoot&lt;/key&gt;
</span><span class="cx">     &lt;string&gt;%(DatabaseRoot)s&lt;/string&gt;
</span><span class="cx"> 
</span><del>-    &lt;!-- Data root --&gt;
-    &lt;key&gt;DataRoot&lt;/key&gt;
-    &lt;string&gt;Data&lt;/string&gt;
-
</del><span class="cx">     &lt;!-- Document root --&gt;
</span><span class="cx">     &lt;key&gt;DocumentRoot&lt;/key&gt;
</span><del>-    &lt;string&gt;Documents&lt;/string&gt;
</del><ins>+    &lt;string&gt;%(DocumentRoot)s&lt;/string&gt;
</ins><span class="cx"> 
</span><span class="cx">     &lt;!-- Configuration root --&gt;
</span><span class="cx">     &lt;key&gt;ConfigRoot&lt;/key&gt;
</span><del>-    &lt;string&gt;config&lt;/string&gt;
</del><ins>+    &lt;string&gt;%(ConfigRoot)s&lt;/string&gt;
</ins><span class="cx"> 
</span><span class="cx">     &lt;!-- Log root --&gt;
</span><span class="cx">     &lt;key&gt;LogRoot&lt;/key&gt;
</span><del>-    &lt;string&gt;Logs&lt;/string&gt;
</del><ins>+    &lt;string&gt;%(LogRoot)s&lt;/string&gt;
</ins><span class="cx"> 
</span><span class="cx">     &lt;!-- Run root --&gt;
</span><span class="cx">     &lt;key&gt;RunRoot&lt;/key&gt;
</span><del>-    &lt;string&gt;Logs/state&lt;/string&gt;
</del><ins>+    &lt;string&gt;%(RunRoot)s&lt;/string&gt;
</ins><span class="cx"> 
</span><span class="cx">     &lt;!-- Child aliases --&gt;
</span><span class="cx">     &lt;key&gt;Aliases&lt;/key&gt;
</span><span class="lines">@@ -147,7 +147,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFile&lt;/key&gt;
</span><span class="lines">@@ -167,7 +167,7 @@
</span><span class="cx">       &lt;true/&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFile&lt;/key&gt;
</span><span class="lines">@@ -180,14 +180,14 @@
</span><span class="cx">         &lt;/array&gt;
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- Open Directory Service (Mac OS X) --&gt;
</span><span class="cx">     &lt;!--
</span><span class="cx">     &lt;key&gt;DirectoryService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.appleopendirectory.OpenDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;node&lt;/key&gt;
</span><span class="lines">@@ -211,7 +211,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentXMLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFiles&lt;/key&gt;
</span><span class="lines">@@ -220,14 +220,14 @@
</span><span class="cx">         &lt;/array&gt;
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- Sqlite Augment Service --&gt;
</span><span class="cx">     &lt;!--
</span><span class="cx">     &lt;key&gt;AugmentService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentSqliteDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;dbpath&lt;/key&gt;
</span><span class="lines">@@ -242,7 +242,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentPostgreSQLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;host&lt;/key&gt;
</span><span class="lines">@@ -258,7 +258,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.calendaruserproxy.ProxySqliteDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;dbpath&lt;/key&gt;
</span><span class="lines">@@ -272,7 +272,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;host&lt;/key&gt;
</span><span class="lines">@@ -515,6 +515,65 @@
</span><span class="cx">           &lt;/dict&gt;
</span><span class="cx">         &lt;/dict&gt;
</span><span class="cx"> 
</span><ins>+        &lt;key&gt;SimpleLineNotifier&lt;/key&gt;
+        &lt;dict&gt;
+          &lt;!-- Simple line notification service (for testing) --&gt;
+          &lt;key&gt;Service&lt;/key&gt;
+          &lt;string&gt;twistedcaldav.notify.SimpleLineNotifierService&lt;/string&gt;
+          &lt;key&gt;Enabled&lt;/key&gt;
+          &lt;false/&gt;
+          &lt;key&gt;Port&lt;/key&gt;
+          &lt;integer&gt;62308&lt;/integer&gt;
+        &lt;/dict&gt;
+
+        &lt;key&gt;XMPPNotifier&lt;/key&gt;
+        &lt;dict&gt;
+          &lt;!-- XMPP notification service --&gt;
+          &lt;key&gt;Service&lt;/key&gt;
+          &lt;string&gt;twistedcaldav.notify.XMPPNotifierService&lt;/string&gt;
+          &lt;key&gt;Enabled&lt;/key&gt;
+          &lt;false/&gt;
+
+          &lt;!-- XMPP host and port to contact --&gt;
+          &lt;key&gt;Host&lt;/key&gt;
+          &lt;string&gt;xmpp.host.name&lt;/string&gt;
+          &lt;key&gt;Port&lt;/key&gt;
+          &lt;integer&gt;5222&lt;/integer&gt;
+
+          &lt;!-- Jabber ID and password for the server --&gt;
+          &lt;key&gt;JID&lt;/key&gt;
+          &lt;string&gt;jid@xmpp.host.name/resource&lt;/string&gt;
+          &lt;key&gt;Password&lt;/key&gt;
+          &lt;string&gt;password_goes_here&lt;/string&gt;
+
+          &lt;!-- PubSub service address --&gt;
+          &lt;key&gt;ServiceAddress&lt;/key&gt;
+          &lt;string&gt;pubsub.xmpp.host.name&lt;/string&gt;
+
+          &lt;key&gt;NodeConfiguration&lt;/key&gt;
+          &lt;dict&gt;
+            &lt;key&gt;pubsub#deliver_payloads&lt;/key&gt;
+            &lt;string&gt;1&lt;/string&gt;
+            &lt;key&gt;pubsub#persist_items&lt;/key&gt;
+            &lt;string&gt;1&lt;/string&gt;
+          &lt;/dict&gt;
+
+          &lt;!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) --&gt;
+          &lt;key&gt;KeepAliveSeconds&lt;/key&gt;
+          &lt;integer&gt;120&lt;/integer&gt;
+
+          &lt;!-- Sends a pubsub publish to a particular heartbeat node at this interval --&gt;
+          &lt;key&gt;HeartbeatMinutes&lt;/key&gt;
+          &lt;integer&gt;30&lt;/integer&gt;
+
+          &lt;!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) --&gt;
+          &lt;key&gt;AllowedJIDs&lt;/key&gt;
+          &lt;array&gt;
+            &lt;!--
+            &lt;string&gt;*.example.com&lt;/string&gt;
+             --&gt;
+          &lt;/array&gt;
+        &lt;/dict&gt;
</ins><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -651,6 +710,7 @@
</span><span class="cx">         &lt;key&gt;UsePackageTimezones&lt;/key&gt;
</span><span class="cx">         &lt;true/&gt;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     &lt;!--
</span><span class="cx">         Miscellaneous items
</span><span class="cx">       --&gt;
</span><span class="lines">@@ -666,7 +726,7 @@
</span><span class="cx">     &lt;!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 --&gt;
</span><span class="cx">     &lt;key&gt;ResponseCompression&lt;/key&gt;
</span><span class="cx">     &lt;false/&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- The retry-after value (in seconds) to return with a 503 error. --&gt;
</span><span class="cx">     &lt;key&gt;HTTPRetryAfter&lt;/key&gt;
</span><span class="cx">     &lt;integer&gt;180&lt;/integer&gt;
</span><span class="lines">@@ -705,7 +765,6 @@
</span><span class="cx">     &lt;key&gt;ResponseCacheTimeout&lt;/key&gt;
</span><span class="cx">     &lt;integer&gt;30&lt;/integer&gt; &lt;!-- in minutes --&gt;
</span><span class="cx"> 
</span><del>-
</del><span class="cx">     &lt;!-- For unit tests, enable SharedConnectionPool so we don't use up shared memory --&gt;
</span><span class="cx">     &lt;key&gt;SharedConnectionPool&lt;/key&gt;
</span><span class="cx">     &lt;true/&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayresourceslocationsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/resources-locations.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/resources-locations.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/resources-locations.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,7 +18,110 @@
</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;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location01&lt;/short-name&gt;
+    &lt;uid&gt;location01&lt;/uid&gt;
+    &lt;full-name&gt;Room 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location02&lt;/short-name&gt;
+    &lt;uid&gt;location02&lt;/uid&gt;
+    &lt;full-name&gt;Room 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location03&lt;/short-name&gt;
+    &lt;uid&gt;location03&lt;/uid&gt;
+    &lt;full-name&gt;Room 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location04&lt;/short-name&gt;
+    &lt;uid&gt;location04&lt;/uid&gt;
+    &lt;full-name&gt;Room 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location05&lt;/short-name&gt;
+    &lt;uid&gt;location05&lt;/uid&gt;
+    &lt;full-name&gt;Room 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location06&lt;/short-name&gt;
+    &lt;uid&gt;location06&lt;/uid&gt;
+    &lt;full-name&gt;Room 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location07&lt;/short-name&gt;
+    &lt;uid&gt;location07&lt;/uid&gt;
+    &lt;full-name&gt;Room 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location08&lt;/short-name&gt;
+    &lt;uid&gt;location08&lt;/uid&gt;
+    &lt;full-name&gt;Room 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location09&lt;/short-name&gt;
+    &lt;uid&gt;location09&lt;/uid&gt;
+    &lt;full-name&gt;Room 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location10&lt;/short-name&gt;
+    &lt;uid&gt;location10&lt;/uid&gt;
+    &lt;full-name&gt;Room 10&lt;/full-name&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource01&lt;/short-name&gt;
+    &lt;uid&gt;resource01&lt;/uid&gt;
+    &lt;full-name&gt;Resource 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource02&lt;/short-name&gt;
+    &lt;uid&gt;resource02&lt;/uid&gt;
+    &lt;full-name&gt;Resource 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource03&lt;/short-name&gt;
+    &lt;uid&gt;resource03&lt;/uid&gt;
+    &lt;full-name&gt;Resource 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource04&lt;/short-name&gt;
+    &lt;uid&gt;resource04&lt;/uid&gt;
+    &lt;full-name&gt;Resource 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource05&lt;/short-name&gt;
+    &lt;uid&gt;resource05&lt;/uid&gt;
+    &lt;full-name&gt;Resource 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource06&lt;/short-name&gt;
+    &lt;uid&gt;resource06&lt;/uid&gt;
+    &lt;full-name&gt;Resource 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource07&lt;/short-name&gt;
+    &lt;uid&gt;resource07&lt;/uid&gt;
+    &lt;full-name&gt;Resource 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource08&lt;/short-name&gt;
+    &lt;uid&gt;resource08&lt;/uid&gt;
+    &lt;full-name&gt;Resource 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource09&lt;/short-name&gt;
+    &lt;uid&gt;resource09&lt;/uid&gt;
+    &lt;full-name&gt;Resource 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource10&lt;/short-name&gt;
+    &lt;uid&gt;resource10&lt;/uid&gt;
+    &lt;full-name&gt;Resource 10&lt;/full-name&gt;
+  &lt;/record&gt;
+
+  &lt;!--
</ins><span class="cx">   &lt;location repeat=&quot;10&quot;&gt;
</span><span class="cx">     &lt;uid&gt;location%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;location%02d&lt;/guid&gt;
</span><span class="lines">@@ -31,4 +134,5 @@
</span><span class="cx">     &lt;password&gt;resource%02d&lt;/password&gt;
</span><span class="cx">     &lt;name&gt;Resource %02d&lt;/name&gt;
</span><span class="cx">   &lt;/resource&gt;
</span><del>-&lt;/accounts&gt;
</del><ins>+--&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestgatewayusersgroupsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/users-groups.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/users-groups.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/gateway/users-groups.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,7 +18,95 @@
</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;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&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;group&quot;&gt;
+    &lt;uid&gt;e5a6142c-4189-4e9e-90b0-9cd0268b314b&lt;/uid&gt;
+    &lt;short-name&gt;testgroup1&lt;/short-name&gt;
+    &lt;full-name&gt;Group 01&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user01&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user02&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;!--
</ins><span class="cx">   &lt;user repeat=&quot;10&quot;&gt;
</span><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;user%02d&lt;/guid&gt;
</span><span class="lines">@@ -37,13 +125,5 @@
</span><span class="cx">       &lt;member type=&quot;users&quot;&gt;user02&lt;/member&gt;
</span><span class="cx">     &lt;/members&gt;
</span><span class="cx">   &lt;/group&gt;
</span><del>-  &lt;group&gt;
-    &lt;uid&gt;testgroup2&lt;/uid&gt;
-    &lt;guid&gt;f5a6142c-4189-4e9e-90b0-9cd0268b314b&lt;/guid&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &lt;name&gt;Group 02&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>+  --&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalscaldavdplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/caldavd.plist (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/caldavd.plist        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/caldavd.plist        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -139,7 +139,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFile&lt;/key&gt;
</span><span class="lines">@@ -159,7 +159,7 @@
</span><span class="cx">       &lt;true/&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFile&lt;/key&gt;
</span><span class="lines">@@ -168,17 +168,18 @@
</span><span class="cx">         &lt;array&gt;
</span><span class="cx">             &lt;string&gt;resources&lt;/string&gt;
</span><span class="cx">             &lt;string&gt;locations&lt;/string&gt;
</span><ins>+            &lt;string&gt;addresses&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- Open Directory Service (Mac OS X) --&gt;
</span><span class="cx">     &lt;!--
</span><span class="cx">     &lt;key&gt;DirectoryService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.appleopendirectory.OpenDirectoryService&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;node&lt;/key&gt;
</span><span class="lines">@@ -202,7 +203,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentXMLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;xmlFiles&lt;/key&gt;
</span><span class="lines">@@ -211,14 +212,14 @@
</span><span class="cx">         &lt;/array&gt;
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- Sqlite Augment Service --&gt;
</span><span class="cx">     &lt;!--
</span><span class="cx">     &lt;key&gt;AugmentService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentSqliteDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;dbpath&lt;/key&gt;
</span><span class="lines">@@ -233,7 +234,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.augment.AugmentPostgreSQLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;host&lt;/key&gt;
</span><span class="lines">@@ -249,7 +250,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.calendaruserproxy.ProxySqliteDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;dbpath&lt;/key&gt;
</span><span class="lines">@@ -263,7 +264,7 @@
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><span class="cx">       &lt;string&gt;twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB&lt;/string&gt;
</span><del>-      
</del><ins>+
</ins><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;host&lt;/key&gt;
</span><span class="lines">@@ -692,7 +693,7 @@
</span><span class="cx">     &lt;!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 --&gt;
</span><span class="cx">     &lt;key&gt;ResponseCompression&lt;/key&gt;
</span><span class="cx">     &lt;false/&gt;
</span><del>-    
</del><ins>+
</ins><span class="cx">     &lt;!-- The retry-after value (in seconds) to return with a 503 error. --&gt;
</span><span class="cx">     &lt;key&gt;HTTPRetryAfter&lt;/key&gt;
</span><span class="cx">     &lt;integer&gt;180&lt;/integer&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalsresourceslocationsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/resources-locations.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/resources-locations.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/resources-locations.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,7 +18,110 @@
</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;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location01&lt;/short-name&gt;
+    &lt;uid&gt;location01&lt;/uid&gt;
+    &lt;full-name&gt;Room 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location02&lt;/short-name&gt;
+    &lt;uid&gt;location02&lt;/uid&gt;
+    &lt;full-name&gt;Room 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location03&lt;/short-name&gt;
+    &lt;uid&gt;location03&lt;/uid&gt;
+    &lt;full-name&gt;Room 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location04&lt;/short-name&gt;
+    &lt;uid&gt;location04&lt;/uid&gt;
+    &lt;full-name&gt;Room 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location05&lt;/short-name&gt;
+    &lt;uid&gt;location05&lt;/uid&gt;
+    &lt;full-name&gt;Room 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location06&lt;/short-name&gt;
+    &lt;uid&gt;location06&lt;/uid&gt;
+    &lt;full-name&gt;Room 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location07&lt;/short-name&gt;
+    &lt;uid&gt;location07&lt;/uid&gt;
+    &lt;full-name&gt;Room 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location08&lt;/short-name&gt;
+    &lt;uid&gt;location08&lt;/uid&gt;
+    &lt;full-name&gt;Room 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location09&lt;/short-name&gt;
+    &lt;uid&gt;location09&lt;/uid&gt;
+    &lt;full-name&gt;Room 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location10&lt;/short-name&gt;
+    &lt;uid&gt;location10&lt;/uid&gt;
+    &lt;full-name&gt;Room 10&lt;/full-name&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource01&lt;/short-name&gt;
+    &lt;uid&gt;resource01&lt;/uid&gt;
+    &lt;full-name&gt;Resource 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource02&lt;/short-name&gt;
+    &lt;uid&gt;resource02&lt;/uid&gt;
+    &lt;full-name&gt;Resource 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource03&lt;/short-name&gt;
+    &lt;uid&gt;resource03&lt;/uid&gt;
+    &lt;full-name&gt;Resource 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource04&lt;/short-name&gt;
+    &lt;uid&gt;resource04&lt;/uid&gt;
+    &lt;full-name&gt;Resource 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource05&lt;/short-name&gt;
+    &lt;uid&gt;resource05&lt;/uid&gt;
+    &lt;full-name&gt;Resource 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource06&lt;/short-name&gt;
+    &lt;uid&gt;resource06&lt;/uid&gt;
+    &lt;full-name&gt;Resource 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource07&lt;/short-name&gt;
+    &lt;uid&gt;resource07&lt;/uid&gt;
+    &lt;full-name&gt;Resource 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource08&lt;/short-name&gt;
+    &lt;uid&gt;resource08&lt;/uid&gt;
+    &lt;full-name&gt;Resource 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource09&lt;/short-name&gt;
+    &lt;uid&gt;resource09&lt;/uid&gt;
+    &lt;full-name&gt;Resource 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource10&lt;/short-name&gt;
+    &lt;uid&gt;resource10&lt;/uid&gt;
+    &lt;full-name&gt;Resource 10&lt;/full-name&gt;
+  &lt;/record&gt;
+
+  &lt;!--
</ins><span class="cx">   &lt;location repeat=&quot;10&quot;&gt;
</span><span class="cx">     &lt;uid&gt;location%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;location%02d&lt;/guid&gt;
</span><span class="lines">@@ -31,4 +134,5 @@
</span><span class="cx">     &lt;password&gt;resource%02d&lt;/password&gt;
</span><span class="cx">     &lt;name&gt;Resource %02d&lt;/name&gt;
</span><span class="cx">   &lt;/resource&gt;
</span><del>-&lt;/accounts&gt;
</del><ins>+--&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstestprincipalsusersgroupsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/users-groups.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/users-groups.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/principals/users-groups.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,7 +18,95 @@
</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;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&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;group&quot;&gt;
+    &lt;uid&gt;e5a6142c-4189-4e9e-90b0-9cd0268b314b&lt;/uid&gt;
+    &lt;short-name&gt;testgroup1&lt;/short-name&gt;
+    &lt;full-name&gt;Group 01&lt;/full-name&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user01&lt;/member-uid&gt;
+      &lt;member-uid type=&quot;users&quot;&gt;user02&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;!--
</ins><span class="cx">   &lt;user repeat=&quot;10&quot;&gt;
</span><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;user%02d&lt;/guid&gt;
</span><span class="lines">@@ -37,4 +125,5 @@
</span><span class="cx">       &lt;member type=&quot;users&quot;&gt;user02&lt;/member&gt;
</span><span class="cx">     &lt;/members&gt;
</span><span class="cx">   &lt;/group&gt;
</span><del>-&lt;/accounts&gt;
</del><ins>+  --&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_agentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_agent.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_agent.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_agent.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -16,93 +16,31 @@
</span><span class="cx"> 
</span><span class="cx"> try:
</span><span class="cx">     from calendarserver.tools.agent import AgentRealm
</span><del>-    from calendarserver.tools.agent import CustomDigestCredentialFactory
-    from calendarserver.tools.agent import DirectoryServiceChecker
</del><span class="cx">     from calendarserver.tools.agent import InactivityDetector
</span><span class="cx">     from twistedcaldav.test.util import TestCase
</span><del>-    from twisted.internet.defer import inlineCallbacks
</del><span class="cx">     from twisted.internet.task import Clock
</span><del>-    from twisted.cred.error import UnauthorizedLogin
</del><span class="cx">     from twisted.web.resource import IResource
</span><span class="cx">     from twisted.web.resource import ForbiddenResource
</span><del>-    RUN_TESTS = True
</del><ins>+
</ins><span class="cx"> except ImportError:
</span><del>-    RUN_TESTS = False
</del><ins>+    pass
</ins><span class="cx"> 
</span><del>-
-
-if RUN_TESTS:
</del><ins>+else:
</ins><span class="cx">     class AgentTestCase(TestCase):
</span><span class="cx"> 
</span><del>-        def test_CustomDigestCredentialFactory(self):
-            f = CustomDigestCredentialFactory(&quot;md5&quot;, &quot;/Local/Default&quot;)
-            challenge = f.getChallenge(FakeRequest())
-            self.assertTrue(&quot;qop&quot; not in challenge)
-            self.assertEquals(challenge[&quot;algorithm&quot;], &quot;md5&quot;)
-            self.assertEquals(challenge[&quot;realm&quot;], &quot;/Local/Default&quot;)
-
-        @inlineCallbacks
-        def test_DirectoryServiceChecker(self):
-            c = DirectoryServiceChecker(&quot;/Local/Default&quot;)
-            fakeOpenDirectory = FakeOpenDirectory()
-            c.directoryModule = fakeOpenDirectory
-
-            fields = {
-                &quot;username&quot; : &quot;foo&quot;,
-                &quot;realm&quot; : &quot;/Local/Default&quot;,
-                &quot;nonce&quot; : 1,
-                &quot;uri&quot; : &quot;/gateway&quot;,
-                &quot;response&quot; : &quot;abc&quot;,
-                &quot;algorithm&quot; : &quot;md5&quot;,
-            }
-            creds = FakeCredentials(&quot;foo&quot;, fields)
-
-            # Record does not exist:
-            fakeOpenDirectory.returnThisRecord(None)
-            try:
-                yield c.requestAvatarId(creds)
-            except UnauthorizedLogin:
-                pass
-            else:
-                self.fail(&quot;Didn't raise UnauthorizedLogin&quot;)
-
-            # Record exists, but invalid credentials
-            fakeOpenDirectory.returnThisRecord(&quot;fooRecord&quot;)
-            fakeOpenDirectory.returnThisAuthResponse(False)
-            try:
-                yield c.requestAvatarId(creds)
-            except UnauthorizedLogin:
-                pass
-            else:
-                self.fail(&quot;Didn't raise UnauthorizedLogin&quot;)
-
-            # Record exists, valid credentials
-            fakeOpenDirectory.returnThisRecord(&quot;fooRecord&quot;)
-            fakeOpenDirectory.returnThisAuthResponse(True)
-            avatar = (yield c.requestAvatarId(creds))
-            self.assertEquals(avatar, &quot;foo&quot;)
-
-            # Record exists, but missing fields in credentials
-            del creds.fields[&quot;nonce&quot;]
-            fakeOpenDirectory.returnThisRecord(&quot;fooRecord&quot;)
-            fakeOpenDirectory.returnThisAuthResponse(False)
-            try:
-                yield c.requestAvatarId(creds)
-            except UnauthorizedLogin:
-                pass
-            else:
-                self.fail(&quot;Didn't raise UnauthorizedLogin&quot;)
-
-
</del><span class="cx">         def test_AgentRealm(self):
</span><span class="cx">             realm = AgentRealm(&quot;root&quot;, [&quot;abc&quot;])
</span><span class="cx"> 
</span><span class="cx">             # Valid avatar
</span><del>-            _ignore_interface, resource, ignored = realm.requestAvatar(&quot;abc&quot;, None, IResource)
</del><ins>+            _ignore_interface, resource, ignored = realm.requestAvatar(
+                &quot;abc&quot;, None, IResource
+            )
</ins><span class="cx">             self.assertEquals(resource, &quot;root&quot;)
</span><span class="cx"> 
</span><span class="cx">             # Not allowed avatar
</span><del>-            _ignore_interface, resource, ignored = realm.requestAvatar(&quot;def&quot;, None, IResource)
</del><ins>+            _ignore_interface, resource, ignored = realm.requestAvatar(
+                &quot;def&quot;, None, IResource
+            )
</ins><span class="cx">             self.assertTrue(isinstance(resource, ForbiddenResource))
</span><span class="cx"> 
</span><span class="cx">             # Interface unhandled
</span><span class="lines">@@ -120,6 +58,7 @@
</span><span class="cx">             clock = Clock()
</span><span class="cx"> 
</span><span class="cx">             self.inactivityReached = False
</span><ins>+
</ins><span class="cx">             def becameInactive():
</span><span class="cx">                 self.inactivityReached = True
</span><span class="cx"> 
</span><span class="lines">@@ -162,8 +101,9 @@
</span><span class="cx">         def returnThisAuthResponse(self, response):
</span><span class="cx">             self.authResponse = response
</span><span class="cx"> 
</span><del>-        def authenticateUserDigest(self, ignored, node, username, challenge, response,
-            method):
</del><ins>+        def authenticateUserDigest(
+            self, ignored, node, username, challenge, response, method
+        ):
</ins><span class="cx">             return self.authResponse
</span><span class="cx"> 
</span><span class="cx">         ODNSerror = &quot;Error&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_calverifypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_calverify.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_calverify.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_calverify.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -35,7 +35,6 @@
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> 
</span><span class="cx"> from StringIO import StringIO
</span><del>-import os
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> OK_ICS = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="lines">@@ -471,20 +470,7 @@
</span><span class="cx"> 
</span><span class="cx">     number_to_process = len(requirements[&quot;home1&quot;][&quot;calendar_1&quot;])
</span><span class="cx"> 
</span><del>-    def configure(self):
-        super(CalVerifyDataTests, self).configure()
-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;calverify&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;calverify&quot;, &quot;resources.xml&quot;
-            )
-        )
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def populate(self):
</span><span class="cx"> 
</span><span class="lines">@@ -944,25 +930,7 @@
</span><span class="cx">     uuid3 = &quot;AC478592-7783-44D1-B2AE-52359B4E8415&quot;
</span><span class="cx">     uuidl1 = &quot;75EA36BE-F71B-40F9-81F9-CF59BF40CA8F&quot;
</span><span class="cx"> 
</span><del>-    def configure(self):
-        super(CalVerifyMismatchTestsBase, self).configure()
-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;calverify&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;calverify&quot;, &quot;resources.xml&quot;
-            )
-        )
-        self.patch(config.AugmentService.params, &quot;xmlFiles&quot;,
-            [os.path.join(
-                os.path.dirname(__file__), &quot;calverify&quot;, &quot;augments.xml&quot;
-            ), ]
-        )
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def populate(self):
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_gatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_gateway.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_gateway.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_gateway.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -25,16 +25,46 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.test.util import TestCase, CapturingProcessProtocol
-from calendarserver.tools.util import getDirectory
</del><ins>+from twistedcaldav.test.util import StoreTestCase, CapturingProcessProtocol
</ins><span class="cx"> import plistlib
</span><ins>+from twistedcaldav.memcacheclient import ClientFactory
+from twistedcaldav import memcacher
+from txdav.who.idirectory import AutoScheduleMode
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-class RunCommandTestCase(TestCase):
</del><ins>+class RunCommandTestCase(StoreTestCase):
</ins><span class="cx"> 
</span><del>-    def setUp(self):
-        super(RunCommandTestCase, self).setUp()
</del><ins>+    def configure(self):
+        &quot;&quot;&quot;
+        Override the standard StoreTestCase configuration
+        &quot;&quot;&quot;
+        self.serverRoot = self.mktemp()
+        os.mkdir(self.serverRoot)
+        absoluteServerRoot = os.path.abspath(self.serverRoot)
</ins><span class="cx"> 
</span><ins>+        configRoot = os.path.join(absoluteServerRoot, &quot;Config&quot;)
+        if not os.path.exists(configRoot):
+            os.makedirs(configRoot)
+
+        dataRoot = os.path.join(absoluteServerRoot, &quot;Data&quot;)
+        if not os.path.exists(dataRoot):
+            os.makedirs(dataRoot)
+
+        documentRoot = os.path.join(absoluteServerRoot, &quot;Documents&quot;)
+        if not os.path.exists(documentRoot):
+            os.makedirs(documentRoot)
+
+        logRoot = os.path.join(absoluteServerRoot, &quot;Logs&quot;)
+        if not os.path.exists(logRoot):
+            os.makedirs(logRoot)
+
+        runRoot = os.path.join(absoluteServerRoot, &quot;Run&quot;)
+        if not os.path.exists(runRoot):
+            os.makedirs(runRoot)
+
+        config.reset()
+        self.configInit()
+
</ins><span class="cx">         testRoot = os.path.join(os.path.dirname(__file__), &quot;gateway&quot;)
</span><span class="cx">         templateName = os.path.join(testRoot, &quot;caldavd.plist&quot;)
</span><span class="cx">         templateFile = open(templateName)
</span><span class="lines">@@ -43,41 +73,79 @@
</span><span class="cx"> 
</span><span class="cx">         databaseRoot = os.path.abspath(&quot;_spawned_scripts_db&quot; + str(os.getpid()))
</span><span class="cx">         newConfig = template % {
</span><del>-            &quot;ServerRoot&quot; : os.path.abspath(config.ServerRoot),
-            &quot;DatabaseRoot&quot; : databaseRoot,
-            &quot;WritablePlist&quot; : os.path.join(os.path.abspath(config.ConfigRoot), &quot;caldavd-writable.plist&quot;),
</del><ins>+            &quot;ServerRoot&quot;: absoluteServerRoot,
+            &quot;DataRoot&quot;: dataRoot,
+            &quot;DatabaseRoot&quot;: databaseRoot,
+            &quot;DocumentRoot&quot;: documentRoot,
+            &quot;ConfigRoot&quot;: configRoot,
+            &quot;LogRoot&quot;: logRoot,
+            &quot;RunRoot&quot;: runRoot,
+            &quot;WritablePlist&quot;: os.path.join(
+                os.path.abspath(configRoot), &quot;caldavd-writable.plist&quot;
+            ),
</ins><span class="cx">         }
</span><del>-        configFilePath = FilePath(os.path.join(config.ConfigRoot, &quot;caldavd.plist&quot;))
</del><ins>+        configFilePath = FilePath(
+            os.path.join(configRoot, &quot;caldavd.plist&quot;)
+        )
+
</ins><span class="cx">         configFilePath.setContent(newConfig)
</span><span class="cx"> 
</span><span class="cx">         self.configFileName = configFilePath.path
</span><span class="cx">         config.load(self.configFileName)
</span><span class="cx"> 
</span><del>-        origUsersFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;gateway&quot;, &quot;users-groups.xml&quot;))
-        copyUsersFile = FilePath(os.path.join(config.DataRoot, &quot;accounts.xml&quot;))
</del><ins>+        config.Memcached.Pools.Default.ClientEnabled = False
+        config.Memcached.Pools.Default.ServerEnabled = False
+        ClientFactory.allowTestCache = True
+        memcacher.Memcacher.allowTestCache = True
+        memcacher.Memcacher.memoryCacheInstance = None
+        config.DirectoryAddressBook.Enabled = False
+        config.UsePackageTimezones = True
+
+        origUsersFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;gateway&quot;,
+                &quot;users-groups.xml&quot;
+            )
+        )
+        copyUsersFile = FilePath(
+            os.path.join(config.DataRoot, &quot;accounts.xml&quot;)
+        )
</ins><span class="cx">         origUsersFile.copyTo(copyUsersFile)
</span><span class="cx"> 
</span><del>-        origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;gateway&quot;, &quot;resources-locations.xml&quot;))
-        copyResourcesFile = FilePath(os.path.join(config.DataRoot, &quot;resources.xml&quot;))
</del><ins>+        origResourcesFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;gateway&quot;,
+                &quot;resources-locations.xml&quot;
+            )
+        )
+        copyResourcesFile = FilePath(
+            os.path.join(config.DataRoot, &quot;resources.xml&quot;)
+        )
</ins><span class="cx">         origResourcesFile.copyTo(copyResourcesFile)
</span><span class="cx"> 
</span><del>-        origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;gateway&quot;, &quot;augments.xml&quot;))
</del><ins>+        origAugmentFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;gateway&quot;,
+                &quot;augments.xml&quot;
+            )
+        )
</ins><span class="cx">         copyAugmentFile = FilePath(os.path.join(config.DataRoot, &quot;augments.xml&quot;))
</span><span class="cx">         origAugmentFile.copyTo(copyAugmentFile)
</span><span class="cx"> 
</span><del>-        # Make sure trial puts the reactor in the right state, by letting it
-        # run one reactor iteration.  (Ignore me, please.)
-        d = Deferred()
-        reactor.callLater(0, d.callback, True)
-        return d
</del><ins>+        # # Make sure trial puts the reactor in the right state, by letting it
+        # # run one reactor iteration.  (Ignore me, please.)
+        # d = Deferred()
+        # reactor.callLater(0, d.callback, True)
+        # return d
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def runCommand(self, command, error=False,
-        script=&quot;calendarserver_command_gateway&quot;):
</del><ins>+    def runCommand(
+        self, command, error=False, script=&quot;calendarserver_command_gateway&quot;
+    ):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Run the given command by feeding it as standard input to
</span><span class="cx">         calendarserver_command_gateway in a subprocess.
</span><span class="lines">@@ -86,7 +154,9 @@
</span><span class="cx">         if isinstance(command, unicode):
</span><span class="cx">             command = command.encode(&quot;utf-8&quot;)
</span><span class="cx"> 
</span><del>-        sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
</del><ins>+        sourceRoot = os.path.dirname(
+            os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+        )
</ins><span class="cx">         cmd = os.path.join(sourceRoot, &quot;bin&quot;, script)
</span><span class="cx"> 
</span><span class="cx">         args = [cmd, &quot;-f&quot;, self.configFileName]
</span><span class="lines">@@ -110,6 +180,12 @@
</span><span class="cx"> 
</span><span class="cx"> class GatewayTestCase(RunCommandTestCase):
</span><span class="cx"> 
</span><ins>+    def _flush(self):
+        # Flush both XML directories
+        self.directory._directory.services[0].flush()
+        self.directory._directory.services[1].flush()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_getLocationAndResourceList(self):
</span><span class="cx">         results = yield self.runCommand(command_getLocationAndResourceList)
</span><span class="lines">@@ -125,14 +201,18 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_getLocationAttributes(self):
</span><span class="cx">         yield self.runCommand(command_createLocation)
</span><ins>+
+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
+
</ins><span class="cx">         results = yield self.runCommand(command_getLocationAttributes)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Capacity&quot;], &quot;40&quot;)
-        self.assertEquals(results[&quot;result&quot;][&quot;Description&quot;], &quot;Test Description&quot;)
</del><ins>+        # self.assertEquals(results[&quot;result&quot;][&quot;Capacity&quot;], &quot;40&quot;)
+        # self.assertEquals(results[&quot;result&quot;][&quot;Description&quot;], &quot;Test Description&quot;)
</ins><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;RecordName&quot;], [&quot;createdlocation01&quot;])
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;RealName&quot;],
</span><span class="cx">             &quot;Created Location 01 %s %s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;))
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Comment&quot;], &quot;Test Comment&quot;)
-        self.assertEquals(results[&quot;result&quot;][&quot;AutoSchedule&quot;], True)
</del><ins>+        # self.assertEquals(results[&quot;result&quot;][&quot;Comment&quot;], &quot;Test Comment&quot;)
+        self.assertEquals(results[&quot;result&quot;][&quot;AutoScheduleMode&quot;], u&quot;acceptIfFree&quot;)
</ins><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;AutoAcceptGroup&quot;], &quot;E5A6142C-4189-4E9E-90B0-9CD0268B314B&quot;)
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;ReadProxies&quot;]), set(['user03', 'user04']))
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;WriteProxies&quot;]), set(['user05', 'user06']))
</span><span class="lines">@@ -147,32 +227,37 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_getResourceAttributes(self):
</span><span class="cx">         yield self.runCommand(command_createResource)
</span><ins>+
+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
+
</ins><span class="cx">         results = yield self.runCommand(command_getResourceAttributes)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Comment&quot;], &quot;Test Comment&quot;)
-        self.assertEquals(results[&quot;result&quot;][&quot;Type&quot;], &quot;Computer&quot;)
</del><ins>+        # self.assertEquals(results[&quot;result&quot;][&quot;Comment&quot;], &quot;Test Comment&quot;)
+        # self.assertEquals(results[&quot;result&quot;][&quot;Type&quot;], &quot;Computer&quot;)
</ins><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;ReadProxies&quot;]), set(['user03', 'user04']))
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;WriteProxies&quot;]), set(['user05', 'user06']))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_createAddress(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
</ins><span class="cx">         self.assertEquals(record, None)
</span><span class="cx">         yield self.runCommand(command_createAddress)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
</ins><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
-        self.assertEquals(record.fullName.decode(&quot;utf-8&quot;),
-            &quot;Created Address 01 %s %s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;))
</del><ins>+        record = yield self.directory.recordWithUID(&quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
+        self.assertEquals(
+            record.displayName,
+            &quot;Created Address 01 %s %s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;)
+        )
</ins><span class="cx"> 
</span><del>-        self.assertNotEquals(record, None)
</del><span class="cx"> 
</span><del>-        self.assertEquals(record.extras[&quot;abbreviatedName&quot;], &quot;Addr1&quot;)
-        self.assertEquals(record.extras[&quot;streetAddress&quot;], &quot;1 Infinite Loop\nCupertino, 95014\nCA&quot;)
-        self.assertEquals(record.extras[&quot;geo&quot;], &quot;geo:37.331,-122.030&quot;)
</del><ins>+        self.assertEquals(record.abbreviatedName, &quot;Addr1&quot;)
+        self.assertEquals(record.streetAddress, &quot;1 Infinite Loop\nCupertino, 95014\nCA&quot;)
+        self.assertEquals(record.geographicLocation, &quot;geo:37.331,-122.030&quot;)
</ins><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(command_getAddressList)
</span><span class="cx">         self.assertEquals(len(results[&quot;result&quot;]), 1)
</span><span class="lines">@@ -185,7 +270,7 @@
</span><span class="cx">         results = yield self.runCommand(command_getAddressAttributes)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;RealName&quot;], u'Updated Address')
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;StreetAddress&quot;], u'Updated Street Address')
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Geo&quot;], u'Updated Geo')
</del><ins>+        self.assertEquals(results[&quot;result&quot;][&quot;GeographicLocation&quot;], u'Updated Geo')
</ins><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(command_deleteAddress)
</span><span class="cx"> 
</span><span class="lines">@@ -195,29 +280,23 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_createLocation(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
</ins><span class="cx">         self.assertEquals(record, None)
</span><span class="cx">         yield self.runCommand(command_createLocation)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
</ins><span class="cx"> 
</span><del>-        # This appears to be necessary in order for record.autoSchedule to
-        # reflect the change prior to the directory record expiration
-        augmentService = directory.serviceForRecordType(directory.recordType_locations).augmentService
-        augmentService.refresh()
</del><ins>+        record = yield self.directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
+        self.assertEquals(record.fullNames[0],
+            u&quot;Created Location 01 %s %s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;))
</ins><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
-        self.assertEquals(record.fullName.decode(&quot;utf-8&quot;),
-            &quot;Created Location 01 %s %s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;))
-
</del><span class="cx">         self.assertNotEquals(record, None)
</span><del>-        self.assertEquals(record.autoSchedule, True)
</del><ins>+        # self.assertEquals(record.autoScheduleMode, &quot;&quot;)
</ins><span class="cx"> 
</span><del>-        self.assertEquals(record.extras[&quot;comment&quot;], &quot;Test Comment&quot;)
-        self.assertEquals(record.extras[&quot;floor&quot;], &quot;First&quot;)
-        self.assertEquals(record.extras[&quot;capacity&quot;], &quot;40&quot;)
</del><ins>+        self.assertEquals(record.floor, u&quot;First&quot;)
+        # self.assertEquals(record.extras[&quot;capacity&quot;], &quot;40&quot;)
</ins><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(command_getLocationAttributes)
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;ReadProxies&quot;]), set(['user03', 'user04']))
</span><span class="lines">@@ -226,88 +305,104 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_setLocationAttributes(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_createLocation)
</span><span class="cx">         yield self.runCommand(command_setLocationAttributes)
</span><del>-        directory.flushCaches()
</del><span class="cx"> 
</span><del>-        # This appears to be necessary in order for record.autoSchedule to
-        # reflect the change
-        augmentService = directory.serviceForRecordType(directory.recordType_locations).augmentService
-        augmentService.refresh()
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
</ins><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
</ins><span class="cx"> 
</span><del>-        self.assertEquals(record.extras[&quot;comment&quot;], &quot;Updated Test Comment&quot;)
-        self.assertEquals(record.extras[&quot;floor&quot;], &quot;Second&quot;)
-        self.assertEquals(record.extras[&quot;capacity&quot;], &quot;41&quot;)
-        self.assertEquals(record.extras[&quot;streetAddress&quot;], &quot;2 Infinite Loop\nCupertino, 95014\nCA&quot;)
-        self.assertEquals(record.autoSchedule, True)
</del><ins>+        # self.assertEquals(record.extras[&quot;comment&quot;], &quot;Updated Test Comment&quot;)
+        self.assertEquals(record.floor, &quot;Second&quot;)
+        # self.assertEquals(record.extras[&quot;capacity&quot;], &quot;41&quot;)
+        self.assertEquals(record.autoScheduleMode, AutoScheduleMode.acceptIfFree)
</ins><span class="cx">         self.assertEquals(record.autoAcceptGroup, &quot;F5A6142C-4189-4E9E-90B0-9CD0268B314B&quot;)
</span><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(command_getLocationAttributes)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;AutoSchedule&quot;], True)
</del><ins>+        self.assertEquals(results[&quot;result&quot;][&quot;AutoScheduleMode&quot;], &quot;acceptIfFree&quot;)
</ins><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;AutoAcceptGroup&quot;], &quot;F5A6142C-4189-4E9E-90B0-9CD0268B314B&quot;)
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;ReadProxies&quot;]), set(['user03']))
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;WriteProxies&quot;]), set(['user05', 'user06', 'user07']))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_setAddressOnLocation(self):
+        yield self.runCommand(command_createLocation)
+        yield self.runCommand(command_createAddress)
+        yield self.runCommand(command_setAddressOnLocation)
+        results = yield self.runCommand(command_getLocationAttributes)
+        self.assertEquals(results[&quot;result&quot;][&quot;AssociatedAddress&quot;], &quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
+        self._flush()
+        record = yield self.directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
+        self.assertEquals(record.associatedAddress, &quot;C701069D-9CA1-4925-A1A9-5CD94767B74B&quot;)
+        yield self.runCommand(command_removeAddressFromLocation)
+        results = yield self.runCommand(command_getLocationAttributes)
+        self.assertEquals(results[&quot;result&quot;][&quot;AssociatedAddress&quot;], &quot;&quot;)
+        self._flush()
+        record = yield self.directory.recordWithUID(&quot;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&quot;)
+        self.assertEquals(record.associatedAddress, u&quot;&quot;)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_destroyLocation(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;location01&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;location01&quot;)
</ins><span class="cx">         self.assertNotEquals(record, None)
</span><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_deleteLocation)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
-        record = directory.recordWithUID(&quot;location01&quot;)
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
+
+        record = yield self.directory.recordWithUID(&quot;location01&quot;)
</ins><span class="cx">         self.assertEquals(record, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_createResource(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
</ins><span class="cx">         self.assertEquals(record, None)
</span><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_createResource)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
-        record = directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
+
+        record = yield self.directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
</ins><span class="cx">         self.assertNotEquals(record, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_setResourceAttributes(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_createResource)
</span><del>-        directory.flushCaches()
-        record = directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
-        self.assertEquals(record.fullName, &quot;Laptop 1&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
+        self.assertEquals(record.displayName, &quot;Laptop 1&quot;)
</ins><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_setResourceAttributes)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
-        record = directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
-        self.assertEquals(record.fullName, &quot;Updated Laptop 1&quot;)
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
</ins><span class="cx"> 
</span><ins>+        record = yield self.directory.recordWithUID(&quot;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&quot;)
+        self.assertEquals(record.displayName, &quot;Updated Laptop 1&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_destroyResource(self):
</span><del>-        directory = getDirectory()
</del><span class="cx"> 
</span><del>-        record = directory.recordWithUID(&quot;resource01&quot;)
</del><ins>+        record = yield self.directory.recordWithUID(&quot;resource01&quot;)
</ins><span class="cx">         self.assertNotEquals(record, None)
</span><span class="cx"> 
</span><span class="cx">         yield self.runCommand(command_deleteResource)
</span><span class="cx"> 
</span><del>-        directory.flushCaches()
-        record = directory.recordWithUID(&quot;resource01&quot;)
</del><ins>+        # Tell the resources services to flush its cache and re-read XML
+        self._flush()
+
+        record = yield self.directory.recordWithUID(&quot;resource01&quot;)
</ins><span class="cx">         self.assertEquals(record, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -338,9 +433,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Verify readConfig returns with only the writable keys
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        results = yield self.runCommand(command_readConfig,
-            script=&quot;calendarserver_config&quot;)
-
</del><ins>+        results = yield self.runCommand(
+            command_readConfig,
+            script=&quot;calendarserver_config&quot;
+        )
</ins><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;RedirectHTTPToHTTPS&quot;], False)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableSearchAddressBook&quot;], False)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableCalDAV&quot;], True)
</span><span class="lines">@@ -360,8 +456,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Verify writeConfig updates the writable plist file only
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        results = yield self.runCommand(command_writeConfig,
-            script=&quot;calendarserver_config&quot;)
</del><ins>+        results = yield self.runCommand(
+            command_writeConfig,
+            script=&quot;calendarserver_config&quot;
+        )
</ins><span class="cx"> 
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableCalDAV&quot;], False)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableCardDAV&quot;], False)
</span><span class="lines">@@ -383,9 +481,9 @@
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;addReadProxy&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Principal&lt;/key&gt;
</span><del>-        &lt;string&gt;locations:location01&lt;/string&gt;
</del><ins>+        &lt;string&gt;location01&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;Proxy&lt;/key&gt;
</span><del>-        &lt;string&gt;users:user03&lt;/string&gt;
</del><ins>+        &lt;string&gt;user03&lt;/string&gt;
</ins><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -397,9 +495,9 @@
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;addWriteProxy&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Principal&lt;/key&gt;
</span><del>-        &lt;string&gt;locations:location01&lt;/string&gt;
</del><ins>+        &lt;string&gt;location01&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;Proxy&lt;/key&gt;
</span><del>-        &lt;string&gt;users:user01&lt;/string&gt;
</del><ins>+        &lt;string&gt;user01&lt;/string&gt;
</ins><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -422,7 +520,7 @@
</span><span class="cx">         &lt;/array&gt;
</span><span class="cx">         &lt;key&gt;StreetAddress&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;1 Infinite Loop\nCupertino, 95014\nCA&lt;/string&gt;
</span><del>-        &lt;key&gt;Geo&lt;/key&gt;
</del><ins>+        &lt;key&gt;GeographicLocation&lt;/key&gt;
</ins><span class="cx">         &lt;string&gt;geo:37.331,-122.030&lt;/string&gt;
</span><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="lines">@@ -435,8 +533,8 @@
</span><span class="cx"> &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;createLocation&lt;/string&gt;
</span><del>-        &lt;key&gt;AutoSchedule&lt;/key&gt;
-        &lt;true/&gt;
</del><ins>+        &lt;key&gt;AutoScheduleMode&lt;/key&gt;
+        &lt;string&gt;acceptIfFree&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;AutoAcceptGroup&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;E5A6142C-4189-4E9E-90B0-9CD0268B314B&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;GeneratedUID&lt;/key&gt;
</span><span class="lines">@@ -453,19 +551,21 @@
</span><span class="cx">         &lt;string&gt;Test Description&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Floor&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;First&lt;/string&gt;
</span><ins>+        &lt;!--
</ins><span class="cx">         &lt;key&gt;Capacity&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;40&lt;/string&gt;
</span><ins>+        --&gt;
</ins><span class="cx">         &lt;key&gt;AssociatedAddress&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;C701069D-9CA1-4925-A1A9-5CD94767B74B&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;ReadProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user03&lt;/string&gt;
-            &lt;string&gt;users:user04&lt;/string&gt;
</del><ins>+            &lt;string&gt;user03&lt;/string&gt;
+            &lt;string&gt;user04&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx">         &lt;key&gt;WriteProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user05&lt;/string&gt;
-            &lt;string&gt;users:user06&lt;/string&gt;
</del><ins>+            &lt;string&gt;user05&lt;/string&gt;
+            &lt;string&gt;user06&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="lines">@@ -478,31 +578,33 @@
</span><span class="cx"> &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;createResource&lt;/string&gt;
</span><del>-        &lt;key&gt;AutoSchedule&lt;/key&gt;
-        &lt;true/&gt;
</del><ins>+        &lt;key&gt;AutoScheduleMode&lt;/key&gt;
+        &lt;string&gt;declineIfBusy&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;GeneratedUID&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;RealName&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Laptop 1&lt;/string&gt;
</span><ins>+        &lt;!--
</ins><span class="cx">         &lt;key&gt;Comment&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Test Comment&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Description&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Test Description&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Type&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Computer&lt;/string&gt;
</span><ins>+        --&gt;
</ins><span class="cx">         &lt;key&gt;RecordName&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><span class="cx">                 &lt;string&gt;laptop1&lt;/string&gt;
</span><span class="cx">         &lt;/array&gt;
</span><span class="cx">         &lt;key&gt;ReadProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user03&lt;/string&gt;
-            &lt;string&gt;users:user04&lt;/string&gt;
</del><ins>+            &lt;string&gt;user03&lt;/string&gt;
+            &lt;string&gt;user04&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx">         &lt;key&gt;WriteProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user05&lt;/string&gt;
-            &lt;string&gt;users:user06&lt;/string&gt;
</del><ins>+            &lt;string&gt;user05&lt;/string&gt;
+            &lt;string&gt;user06&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="lines">@@ -617,9 +719,9 @@
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;removeReadProxy&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Principal&lt;/key&gt;
</span><del>-        &lt;string&gt;locations:location01&lt;/string&gt;
</del><ins>+        &lt;string&gt;location01&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;Proxy&lt;/key&gt;
</span><del>-        &lt;string&gt;users:user03&lt;/string&gt;
</del><ins>+        &lt;string&gt;user03&lt;/string&gt;
</ins><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -631,9 +733,9 @@
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;removeWriteProxy&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Principal&lt;/key&gt;
</span><del>-        &lt;string&gt;locations:location01&lt;/string&gt;
</del><ins>+        &lt;string&gt;location01&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;Proxy&lt;/key&gt;
</span><del>-        &lt;string&gt;users:user01&lt;/string&gt;
</del><ins>+        &lt;string&gt;user01&lt;/string&gt;
</ins><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -662,24 +764,53 @@
</span><span class="cx">         &lt;string&gt;Updated Test Description&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;Floor&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Second&lt;/string&gt;
</span><ins>+        &lt;!--
</ins><span class="cx">         &lt;key&gt;Capacity&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;41&lt;/string&gt;
</span><del>-        &lt;key&gt;StreetAddress&lt;/key&gt;
-        &lt;string&gt;2 Infinite Loop\nCupertino, 95014\nCA&lt;/string&gt;
</del><ins>+        --&gt;
</ins><span class="cx">         &lt;key&gt;ReadProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user03&lt;/string&gt;
</del><ins>+            &lt;string&gt;user03&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx">         &lt;key&gt;WriteProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><del>-            &lt;string&gt;users:user05&lt;/string&gt;
-            &lt;string&gt;users:user06&lt;/string&gt;
-            &lt;string&gt;users:user07&lt;/string&gt;
</del><ins>+            &lt;string&gt;user05&lt;/string&gt;
+            &lt;string&gt;user06&lt;/string&gt;
+            &lt;string&gt;user07&lt;/string&gt;
</ins><span class="cx">         &lt;/array&gt;
</span><span class="cx"> &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+command_setAddressOnLocation = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+&lt;dict&gt;
+        &lt;key&gt;command&lt;/key&gt;
+        &lt;string&gt;setLocationAttributes&lt;/string&gt;
+        &lt;key&gt;GeneratedUID&lt;/key&gt;
+        &lt;string&gt;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&lt;/string&gt;
+        &lt;key&gt;AssociatedAddress&lt;/key&gt;
+        &lt;string&gt;C701069D-9CA1-4925-A1A9-5CD94767B74B&lt;/string&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+&quot;&quot;&quot;
+
+command_removeAddressFromLocation = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+&lt;dict&gt;
+        &lt;key&gt;command&lt;/key&gt;
+        &lt;string&gt;setLocationAttributes&lt;/string&gt;
+        &lt;key&gt;GeneratedUID&lt;/key&gt;
+        &lt;string&gt;836B1B66-2E9A-4F46-8B1C-3DD6772C20B2&lt;/string&gt;
+        &lt;key&gt;AssociatedAddress&lt;/key&gt;
+        &lt;string&gt;&lt;/string&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+&quot;&quot;&quot;
+
+
</ins><span class="cx"> command_getLocationAttributes = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
</span><span class="cx"> &lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
</span><span class="cx"> &lt;plist version=&quot;1.0&quot;&gt;
</span><span class="lines">@@ -716,7 +847,7 @@
</span><span class="cx">         &lt;string&gt;Updated Address&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;StreetAddress&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;Updated Street Address&lt;/string&gt;
</span><del>-        &lt;key&gt;Geo&lt;/key&gt;
</del><ins>+        &lt;key&gt;GeographicLocation&lt;/key&gt;
</ins><span class="cx">         &lt;string&gt;Updated Geo&lt;/string&gt;
</span><span class="cx"> 
</span><span class="cx"> &lt;/dict&gt;
</span><span class="lines">@@ -730,8 +861,8 @@
</span><span class="cx"> &lt;dict&gt;
</span><span class="cx">         &lt;key&gt;command&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;setResourceAttributes&lt;/string&gt;
</span><del>-        &lt;key&gt;AutoSchedule&lt;/key&gt;
-        &lt;false/&gt;
</del><ins>+        &lt;key&gt;AutoScheduleMode&lt;/key&gt;
+        &lt;string&gt;acceptIfFree&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;GeneratedUID&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;AF575A61-CFA6-49E1-A0F6-B5662C9D9801&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;RealName&lt;/key&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_principalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_principals.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_principals.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_principals.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -17,29 +17,27 @@
</span><span class="cx"> import os
</span><span class="cx"> import sys
</span><span class="cx"> 
</span><ins>+from calendarserver.tools.principals import (
+    parseCreationArgs, matchStrings,
+    recordForPrincipalID, getProxies, setProxies
+)
</ins><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
</span><del>-
</del><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.directory import DirectoryError
-from twistedcaldav.directory import calendaruserproxy
</del><ins>+from twistedcaldav.test.util import (
+    TestCase, StoreTestCase, CapturingProcessProtocol, ErrorOutput
+)
</ins><span class="cx"> 
</span><del>-from twistedcaldav.test.util import TestCase, CapturingProcessProtocol, \
-    ErrorOutput
</del><span class="cx"> 
</span><del>-from calendarserver.tap.util import directoryFromConfig
-from calendarserver.tools.principals import (parseCreationArgs, matchStrings,
-    updateRecord, principalForPrincipalID, getProxies, setProxies)
</del><span class="cx"> 
</span><del>-
</del><span class="cx"> class ManagePrincipalsTestCase(TestCase):
</span><span class="cx"> 
</span><span class="cx">     def setUp(self):
</span><span class="cx">         super(ManagePrincipalsTestCase, self).setUp()
</span><span class="cx"> 
</span><del>-        # Since this test operates on proxy db, we need to assign the service:
-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
</del><ins>+        # # Since this test operates on proxy db, we need to assign the service:
+        # calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
</ins><span class="cx"> 
</span><span class="cx">         testRoot = os.path.join(os.path.dirname(__file__), &quot;principals&quot;)
</span><span class="cx">         templateName = os.path.join(testRoot, &quot;caldavd.plist&quot;)
</span><span class="lines">@@ -49,11 +47,11 @@
</span><span class="cx"> 
</span><span class="cx">         databaseRoot = os.path.abspath(&quot;_spawned_scripts_db&quot; + str(os.getpid()))
</span><span class="cx">         newConfig = template % {
</span><del>-            &quot;ServerRoot&quot; : os.path.abspath(config.ServerRoot),
-            &quot;DataRoot&quot; : os.path.abspath(config.DataRoot),
-            &quot;DatabaseRoot&quot; : databaseRoot,
-            &quot;DocumentRoot&quot; : os.path.abspath(config.DocumentRoot),
-            &quot;LogRoot&quot; : os.path.abspath(config.LogRoot),
</del><ins>+            &quot;ServerRoot&quot;: os.path.abspath(config.ServerRoot),
+            &quot;DataRoot&quot;: os.path.abspath(config.DataRoot),
+            &quot;DatabaseRoot&quot;: databaseRoot,
+            &quot;DocumentRoot&quot;: os.path.abspath(config.DocumentRoot),
+            &quot;LogRoot&quot;: os.path.abspath(config.LogRoot),
</ins><span class="cx">         }
</span><span class="cx">         configFilePath = FilePath(os.path.join(config.ConfigRoot, &quot;caldavd.plist&quot;))
</span><span class="cx">         configFilePath.setContent(newConfig)
</span><span class="lines">@@ -61,18 +59,33 @@
</span><span class="cx">         self.configFileName = configFilePath.path
</span><span class="cx">         config.load(self.configFileName)
</span><span class="cx"> 
</span><del>-        origUsersFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;principals&quot;, &quot;users-groups.xml&quot;))
</del><ins>+        origUsersFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;principals&quot;,
+                &quot;users-groups.xml&quot;
+            )
+        )
</ins><span class="cx">         copyUsersFile = FilePath(os.path.join(config.DataRoot, &quot;accounts.xml&quot;))
</span><span class="cx">         origUsersFile.copyTo(copyUsersFile)
</span><span class="cx"> 
</span><del>-        origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;principals&quot;, &quot;resources-locations.xml&quot;))
</del><ins>+        origResourcesFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;principals&quot;,
+                &quot;resources-locations.xml&quot;
+            )
+        )
</ins><span class="cx">         copyResourcesFile = FilePath(os.path.join(config.DataRoot, &quot;resources.xml&quot;))
</span><span class="cx">         origResourcesFile.copyTo(copyResourcesFile)
</span><span class="cx"> 
</span><del>-        origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
-            &quot;principals&quot;, &quot;augments.xml&quot;))
</del><ins>+        origAugmentFile = FilePath(
+            os.path.join(
+                os.path.dirname(__file__),
+                &quot;principals&quot;,
+                &quot;augments.xml&quot;
+            )
+        )
</ins><span class="cx">         copyAugmentFile = FilePath(os.path.join(config.DataRoot, &quot;augments.xml&quot;))
</span><span class="cx">         origAugmentFile.copyTo(copyAugmentFile)
</span><span class="cx"> 
</span><span class="lines">@@ -114,6 +127,7 @@
</span><span class="cx">         self.assertTrue(&quot;users&quot; in results)
</span><span class="cx">         self.assertTrue(&quot;locations&quot; in results)
</span><span class="cx">         self.assertTrue(&quot;resources&quot; in results)
</span><ins>+        self.assertTrue(&quot;addresses&quot; in results)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -133,28 +147,36 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_addRemove(self):
</span><del>-        results = yield self.runCommand(&quot;--add&quot;, &quot;resources&quot;, &quot;New Resource&quot;,
-            &quot;newresource&quot;, &quot;edaa6ae6-011b-4d89-ace3-6b688cdd91d9&quot;)
</del><ins>+        results = yield self.runCommand(
+            &quot;--add&quot;, &quot;resources&quot;,
+            &quot;New Resource&quot;, &quot;newresource&quot;, &quot;newresourceuid&quot;
+        )
</ins><span class="cx">         self.assertTrue(&quot;Added 'New Resource'&quot; in results)
</span><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--get-auto-schedule&quot;,
-            &quot;resources:newresource&quot;)
-        self.assertTrue(results.startswith('Auto-schedule for &quot;New Resource&quot; (resources:newresource) is true'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--get-auto-schedule-mode&quot;,
+            &quot;resources:newresource&quot;
+        )
+        self.assertTrue(
+            results.startswith(
+                'Auto-schedule mode for &quot;New Resource&quot; newresourceuid (resource) newresource is accept if free, decline if busy'
+            )
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--get-auto-schedule-mode&quot;,
-            &quot;resources:newresource&quot;)
-        self.assertTrue(results.startswith('Auto-schedule mode for &quot;New Resource&quot; (resources:newresource) is default'))
-
</del><span class="cx">         results = yield self.runCommand(&quot;--list-principals=resources&quot;)
</span><span class="cx">         self.assertTrue(&quot;newresource&quot; in results)
</span><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--add&quot;, &quot;resources&quot;, &quot;New Resource&quot;,
-            &quot;newresource1&quot;, &quot;edaa6ae6-011b-4d89-ace3-6b688cdd91d9&quot;)
-        self.assertTrue(&quot;Duplicate guid&quot; in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--add&quot;, &quot;resources&quot;, &quot;New Resource&quot;,
+            &quot;newresource1&quot;, &quot;newresourceuid&quot;
+        )
+        self.assertTrue(&quot;UID already in use: newresourceuid&quot; in results)
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--add&quot;, &quot;resources&quot;, &quot;New Resource&quot;,
-            &quot;newresource&quot;, &quot;fdaa6ae6-011b-4d89-ace3-6b688cdd91d9&quot;)
-        self.assertTrue(&quot;Duplicate shortName&quot; in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--add&quot;, &quot;resources&quot;, &quot;New Resource&quot;,
+            &quot;newresource&quot;, &quot;uniqueuid&quot;
+        )
+        self.assertTrue(&quot;Record name already in use&quot; in results)
</ins><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(&quot;--remove&quot;, &quot;resources:newresource&quot;)
</span><span class="cx">         self.assertTrue(&quot;Removed 'New Resource'&quot; in results)
</span><span class="lines">@@ -165,29 +187,13 @@
</span><span class="cx"> 
</span><span class="cx">     def test_parseCreationArgs(self):
</span><span class="cx"> 
</span><del>-        self.assertEquals((&quot;full name&quot;, None, None),
-            parseCreationArgs((&quot;full name&quot;,)))
-
-        self.assertEquals((&quot;full name&quot;, &quot;short name&quot;, None),
-            parseCreationArgs((&quot;full name&quot;, &quot;short name&quot;)))
-
-        guid = &quot;02C3DE93-E655-4856-47B76B8BB1A7BDCE&quot;
-
-        self.assertEquals((&quot;full name&quot;, &quot;short name&quot;, guid),
-            parseCreationArgs((&quot;full name&quot;, &quot;short name&quot;, guid)))
-
-        self.assertEquals((&quot;full name&quot;, &quot;short name&quot;, guid),
-            parseCreationArgs((&quot;full name&quot;, guid, &quot;short name&quot;)))
-
-        self.assertEquals((&quot;full name&quot;, None, guid),
-            parseCreationArgs((&quot;full name&quot;, guid)))
-
-        self.assertRaises(
-            ValueError,
-            parseCreationArgs, (&quot;full name&quot;, &quot;non guid&quot;, &quot;non guid&quot;)
</del><ins>+        self.assertEquals(
+            (&quot;full name&quot;, &quot;short name&quot;, &quot;uid&quot;),
+            parseCreationArgs((&quot;full name&quot;, &quot;short name&quot;, &quot;uid&quot;))
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_matchStrings(self):
</span><span class="cx">         self.assertEquals(&quot;abc&quot;, matchStrings(&quot;a&quot;, (&quot;abc&quot;, &quot;def&quot;)))
</span><span class="cx">         self.assertEquals(&quot;def&quot;, matchStrings(&quot;de&quot;, (&quot;abc&quot;, &quot;def&quot;)))
</span><span class="lines">@@ -199,161 +205,126 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_modifyWriteProxies(self):
</span><del>-        results = yield self.runCommand(&quot;--add-write-proxy=users:user01&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Added &quot;Test User 01&quot; (users:user01) as a write proxy for &quot;Room 01&quot; (locations:location01)'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--add-write-proxy=users:user01&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            results.startswith('Added &quot;User 01&quot; user01 (user) user01 as a write proxy for &quot;Room 01&quot; location01 (location) location01')
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--list-write-proxies&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(&quot;Test User 01&quot; in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--list-write-proxies&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(&quot;User 01&quot; in results)
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--remove-proxy=users:user01&quot;,
-            &quot;locations:location01&quot;)
</del><ins>+        results = yield self.runCommand(
+            &quot;--remove-proxy=users:user01&quot;, &quot;locations:location01&quot;
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--list-write-proxies&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue('No write proxies for &quot;Room 01&quot; (locations:location01)' in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--list-write-proxies&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            'No write proxies for &quot;Room 01&quot; location01 (location) location01' in results
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_modifyReadProxies(self):
</span><del>-        results = yield self.runCommand(&quot;--add-read-proxy=users:user01&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Added &quot;Test User 01&quot; (users:user01) as a read proxy for &quot;Room 01&quot; (locations:location01)'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--add-read-proxy=users:user01&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            results.startswith('Added &quot;User 01&quot; user01 (user) user01 as a read proxy for &quot;Room 01&quot; location01 (location) location01')
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--list-read-proxies&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(&quot;Test User 01&quot; in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--list-read-proxies&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(&quot;User 01&quot; in results)
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--remove-proxy=users:user01&quot;,
-            &quot;locations:location01&quot;)
</del><ins>+        results = yield self.runCommand(
+            &quot;--remove-proxy=users:user01&quot;, &quot;locations:location01&quot;
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--list-read-proxies&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue('No read proxies for &quot;Room 01&quot; (locations:location01)' in results)
</del><ins>+        results = yield self.runCommand(
+            &quot;--list-read-proxies&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            'No read proxies for &quot;Room 01&quot; location01 (location) location01' in results
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_autoSchedule(self):
-        results = yield self.runCommand(&quot;--get-auto-schedule&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Auto-schedule for &quot;Room 01&quot; (locations:location01) is false'))
-
-        results = yield self.runCommand(&quot;--set-auto-schedule=true&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Setting auto-schedule to true for &quot;Room 01&quot; (locations:location01)'))
-
-        results = yield self.runCommand(&quot;--get-auto-schedule&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Auto-schedule for &quot;Room 01&quot; (locations:location01) is true'))
-
-        results = yield self.runCommand(&quot;--set-auto-schedule=true&quot;,
-            &quot;users:user01&quot;)
-        self.assertTrue(results.startswith('Enabling auto-schedule for (users)user01 is not allowed.'))
-
-
-    @inlineCallbacks
</del><span class="cx">     def test_autoScheduleMode(self):
</span><del>-        results = yield self.runCommand(&quot;--get-auto-schedule-mode&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Auto-schedule mode for &quot;Room 01&quot; (locations:location01) is default'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--get-auto-schedule-mode&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            results.startswith('Auto-schedule mode for &quot;Room 01&quot; location01 (location) location01 is accept if free, decline if busy')
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--set-auto-schedule-mode=accept-if-free&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Setting auto-schedule mode to accept-if-free for &quot;Room 01&quot; (locations:location01)'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--set-auto-schedule-mode=accept-if-free&quot;, &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            results.startswith('Setting auto-schedule-mode to accept if free for &quot;Room 01&quot; location01 (location) location01')
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--get-auto-schedule-mode&quot;,
-            &quot;locations:location01&quot;)
-        self.assertTrue(results.startswith('Auto-schedule mode for &quot;Room 01&quot; (locations:location01) is accept-if-free'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--get-auto-schedule-mode&quot;,
+            &quot;locations:location01&quot;
+        )
+        self.assertTrue(
+            results.startswith('Auto-schedule mode for &quot;Room 01&quot; location01 (location) location01 is accept if free')
+        )
</ins><span class="cx"> 
</span><del>-        results = yield self.runCommand(&quot;--set-auto-schedule-mode=decline-if-busy&quot;,
-            &quot;users:user01&quot;)
-        self.assertTrue(results.startswith('Setting auto-schedule mode for (users)user01 is not allowed.'))
</del><ins>+        results = yield self.runCommand(
+            &quot;--set-auto-schedule-mode=decline-if-busy&quot;, &quot;users:user01&quot;
+        )
+        self.assertTrue(results.startswith('Setting auto-schedule-mode for &quot;User 01&quot; user01 (user) user01 is not allowed.'))
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            results = yield self.runCommand(&quot;--set-auto-schedule-mode=bogus&quot;,
-                &quot;users:user01&quot;)
</del><ins>+            results = yield self.runCommand(
+                &quot;--set-auto-schedule-mode=bogus&quot;,
+                &quot;users:user01&quot;
+            )
</ins><span class="cx">         except ErrorOutput:
</span><span class="cx">             pass
</span><span class="cx">         else:
</span><span class="cx">             self.fail(&quot;Expected command failure&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def test_updateRecord(self):
-        directory = directoryFromConfig(config)
-        guid = &quot;EEE28807-A8C5-46C8-A558-A08281C558A7&quot;
</del><span class="cx"> 
</span><del>-        (yield updateRecord(True, directory, &quot;locations&quot;,
-            guid=guid, fullName=&quot;Test Location&quot;, shortNames=[&quot;testlocation&quot;, ],)
-        )
-        try:
-            (yield updateRecord(True, directory, &quot;locations&quot;,
-                guid=guid, fullName=&quot;Test Location&quot;, shortNames=[&quot;testlocation&quot;, ],)
-            )
-        except DirectoryError:
-            # We're expecting an error for trying to create a record with
-            # an existing GUID
-            pass
-        else:
-            raise self.failureException(&quot;Duplicate guid expected&quot;)
</del><ins>+class SetProxiesTestCase(StoreTestCase):
</ins><span class="cx"> 
</span><del>-        record = directory.recordWithGUID(guid)
-        self.assertTrue(record is not None)
-        self.assertEquals(record.fullName, &quot;Test Location&quot;)
-        self.assertTrue(record.autoSchedule)
-
-        (yield updateRecord(False, directory, &quot;locations&quot;,
-            guid=guid, fullName=&quot;Changed&quot;, shortNames=[&quot;testlocation&quot;, ],)
-        )
-        record = directory.recordWithGUID(guid)
-        self.assertTrue(record is not None)
-        self.assertEquals(record.fullName, &quot;Changed&quot;)
-
-        directory.destroyRecord(&quot;locations&quot;, guid=guid)
-        record = directory.recordWithGUID(guid)
-        self.assertTrue(record is None)
-
-        # Create a user, change autoSchedule
-        guid = &quot;F0DE73A8-39D4-4830-8D32-1FA03ABA3470&quot;
-        (yield updateRecord(True, directory, &quot;users&quot;,
-            guid=guid, fullName=&quot;Test User&quot;, shortNames=[&quot;testuser&quot;, ],
-            autoSchedule=True)
-        )
-        record = directory.recordWithGUID(guid)
-        self.assertTrue(record is not None)
-        self.assertEquals(record.fullName, &quot;Test User&quot;)
-        self.assertTrue(record.autoSchedule)
-
-        (yield updateRecord(False, directory, &quot;users&quot;,
-            guid=guid, fullName=&quot;Test User&quot;, shortNames=[&quot;testuser&quot;, ],
-            autoSchedule=False)
-        )
-        record = directory.recordWithGUID(guid)
-        self.assertTrue(record is not None)
-        self.assertEquals(record.fullName, &quot;Test User&quot;)
-        self.assertFalse(record.autoSchedule)
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_setProxies(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Read and Write proxies can be set en masse
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        directory = directoryFromConfig(config)
</del><ins>+        directory = self.directory
+        record = yield recordForPrincipalID(directory, &quot;users:user01&quot;)
</ins><span class="cx"> 
</span><del>-        principal = principalForPrincipalID(&quot;users:user01&quot;, directory=directory)
-        readProxies, writeProxies = (yield getProxies(principal, directory=directory))
-        self.assertEquals(readProxies, []) # initially empty
-        self.assertEquals(writeProxies, []) # initially empty
</del><ins>+        readProxies, writeProxies = yield getProxies(record)
+        self.assertEquals(readProxies, [])  # initially empty
+        self.assertEquals(writeProxies, [])  # initially empty
</ins><span class="cx"> 
</span><del>-        (yield setProxies(None, principal, [&quot;users:user03&quot;, &quot;users:user04&quot;], [&quot;users:user05&quot;], directory=directory))
-        readProxies, writeProxies = (yield getProxies(principal, directory=directory))
-        self.assertEquals(set(readProxies), set([&quot;user03&quot;, &quot;user04&quot;]))
-        self.assertEquals(set(writeProxies), set([&quot;user05&quot;]))
</del><ins>+        readProxies = [
+            (yield recordForPrincipalID(directory, &quot;users:user03&quot;)),
+            (yield recordForPrincipalID(directory, &quot;users:user04&quot;)),
+        ]
+        writeProxies = [
+            (yield recordForPrincipalID(directory, &quot;users:user05&quot;)),
+        ]
+        yield setProxies(record, readProxies, writeProxies)
</ins><span class="cx"> 
</span><ins>+        readProxies, writeProxies = yield getProxies(record)
+        self.assertEquals(set([r.uid for r in readProxies]), set([&quot;user03&quot;, &quot;user04&quot;]))
+        self.assertEquals(set([r.uid for r in writeProxies]), set([&quot;user05&quot;]))
+
</ins><span class="cx">         # Using None for a proxy list indicates a no-op
</span><del>-        (yield setProxies(None, principal, [], None, directory=directory))
-        readProxies, writeProxies = (yield getProxies(principal, directory=directory))
-        self.assertEquals(readProxies, []) # now empty
-        self.assertEquals(set(writeProxies), set([&quot;user05&quot;])) # unchanged
</del><ins>+        yield setProxies(record, [], None)
+        readProxies, writeProxies = yield getProxies(record)
+        self.assertEquals(readProxies, [])  # now empty
+        self.assertEquals(set([r.uid for r in writeProxies]), set([&quot;user05&quot;]))  # unchanged
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_purgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -17,7 +17,6 @@
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tools.purge import PurgePrincipalService
</span><span class="cx"> 
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> 
</span><span class="lines">@@ -30,7 +29,6 @@
</span><span class="cx"> 
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> 
</span><del>-import os
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> future = DateTime.getNowUTC()
</span><span class="lines">@@ -770,7 +768,7 @@
</span><span class="cx"> DTSTART;TZID=US/Pacific:20100304T120000
</span><span class="cx"> DTSTAMP:20100303T195203Z
</span><span class="cx"> SEQUENCE:2
</span><del>-X-APPLE-DROPBOX:/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/dropbox/F2F14D94-B944-43D9-8F6F-97F95B2764CA.dropbox
</del><ins>+X-APPLE-DROPBOX:/calendars/__uids__/C76DB741-5A2A-4239-8112-10CF152AFCA4/dropbox/F2F14D94-B944-43D9-8F6F-97F95B2764CA.dropbox
</ins><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
</span><span class="lines">@@ -800,8 +798,8 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests for purging the data belonging to a given principal
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    uid = &quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;
-    uid2 = &quot;37DB0C90-4DB1-4932-BC69-3DAB66F374F5&quot;
</del><ins>+    uid = &quot;C76DB741-5A2A-4239-8112-10CF152AFCA4&quot;
+    uid2 = &quot;FFED7B62-2E08-496E-BD32-B2F95FFDDB6B&quot;
</ins><span class="cx"> 
</span><span class="cx">     metadata = {
</span><span class="cx">         &quot;accessMode&quot;: &quot;PUBLIC&quot;,
</span><span class="lines">@@ -832,48 +830,35 @@
</span><span class="cx"> 
</span><span class="cx">         # Add attachment to attachment.ics
</span><span class="cx">         self._sqlCalendarStore._dropbox_ok = True
</span><del>-        home = (yield txn.calendarHomeWithUID(self.uid))
-        calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
-        event = (yield calendar.calendarObjectWithName(&quot;attachment.ics&quot;))
-        attachment = (yield event.createAttachmentWithName(&quot;attachment.txt&quot;))
</del><ins>+        home = yield txn.calendarHomeWithUID(self.uid)
+        calendar = yield home.calendarWithName(&quot;calendar1&quot;)
+        event = yield calendar.calendarObjectWithName(&quot;attachment.ics&quot;)
+        attachment = yield event.createAttachmentWithName(&quot;attachment.txt&quot;)
</ins><span class="cx">         t = attachment.store(MimeType(&quot;text&quot;, &quot;x-fixture&quot;))
</span><span class="cx">         t.write(&quot;attachment&quot;)
</span><span class="cx">         t.write(&quot; text&quot;)
</span><del>-        (yield t.loseConnection())
</del><ins>+        yield t.loseConnection()
</ins><span class="cx">         self._sqlCalendarStore._dropbox_ok = False
</span><span class="cx"> 
</span><span class="cx">         # Share calendars each way
</span><del>-        home2 = (yield txn.calendarHomeWithUID(self.uid2))
-        calendar2 = (yield home2.calendarWithName(&quot;calendar2&quot;))
-        self.sharedName = (yield calendar2.shareWith(home, _BIND_MODE_WRITE))
-        self.sharedName2 = (yield calendar.shareWith(home2, _BIND_MODE_WRITE))
</del><ins>+        home2 = yield txn.calendarHomeWithUID(self.uid2)
+        calendar2 = yield home2.calendarWithName(&quot;calendar2&quot;)
+        self.sharedName = yield calendar2.shareWith(home, _BIND_MODE_WRITE)
+        self.sharedName2 = yield calendar.shareWith(home2, _BIND_MODE_WRITE)
</ins><span class="cx"> 
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(self.uid))
-        calendar2 = (yield home.childWithName(self.sharedName))
</del><ins>+        home = yield txn.calendarHomeWithUID(self.uid)
+        calendar2 = yield home.childWithName(self.sharedName)
</ins><span class="cx">         self.assertNotEquals(calendar2, None)
</span><del>-        home2 = (yield txn.calendarHomeWithUID(self.uid2))
-        calendar1 = (yield home2.childWithName(self.sharedName2))
</del><ins>+        home2 = yield txn.calendarHomeWithUID(self.uid2)
+        calendar1 = yield home2.childWithName(self.sharedName2)
</ins><span class="cx">         self.assertNotEquals(calendar1, None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def configure(self):
-        super(PurgePrincipalTests, self).configure()
-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
-            )
-        )
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def populate(self):
</span><span class="cx">         yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
</span><span class="lines">@@ -888,32 +873,39 @@
</span><span class="cx"> 
</span><span class="cx">         # Now you see it
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(self.uid))
</del><ins>+        home = yield txn.calendarHomeWithUID(self.uid)
</ins><span class="cx">         self.assertNotEquals(home, None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><del>-        count, ignored = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
</del><ins>+        count = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
</ins><span class="cx">             self.rootResource, (self.uid,), verbose=False, proxies=False, completely=True))
</span><span class="cx">         self.assertEquals(count, 2) # 2 events
</span><span class="cx"> 
</span><span class="cx">         # Now you don't
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(self.uid))
</del><ins>+        home = yield txn.calendarHomeWithUID(self.uid)
</ins><span class="cx">         self.assertEquals(home, None)
</span><span class="cx">         # Verify calendar1 was unshared to uid2
</span><del>-        home2 = (yield txn.calendarHomeWithUID(self.uid2))
</del><ins>+        home2 = yield txn.calendarHomeWithUID(self.uid2)
</ins><span class="cx">         self.assertEquals((yield home2.childWithName(self.sharedName)), None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><del>-        count, ignored = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
-            self.rootResource, (self.uid,), verbose=False, proxies=False, completely=True))
</del><ins>+        count = yield PurgePrincipalService.purgeUIDs(
+            self.storeUnderTest(),
+            self.directory,
+            self.rootResource,
+            (self.uid,),
+            verbose=False,
+            proxies=False,
+            completely=True
+        )
</ins><span class="cx">         self.assertEquals(count, 0)
</span><span class="cx"> 
</span><span class="cx">         # And you still don't (making sure it's not provisioned)
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(self.uid))
</del><ins>+        home = yield txn.calendarHomeWithUID(self.uid)
</ins><span class="cx">         self.assertEquals(home, None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -928,11 +920,11 @@
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">         home = (yield txn.calendarHomeWithUID(self.uid))
</span><span class="cx">         self.assertNotEquals(home, None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><del>-        count, ignored = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
</del><ins>+        count = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
</ins><span class="cx">             self.rootResource, (self.uid,), verbose=False, proxies=False, completely=False))
</span><del>-        self.assertEquals(count, 1) # 2 events
</del><ins>+        self.assertEquals(count, 1) # 1 event
</ins><span class="cx"> 
</span><span class="cx">         # Now you still see it
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="lines">@@ -941,14 +933,14 @@
</span><span class="cx">         # Verify calendar1 was unshared to uid2
</span><span class="cx">         home2 = (yield txn.calendarHomeWithUID(self.uid2))
</span><span class="cx">         self.assertEquals((yield home2.childWithName(self.sharedName)), None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins><span class="cx"> 
</span><del>-        count, ignored = (yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
-            self.rootResource, (self.uid,), verbose=False, proxies=False, completely=False))
</del><ins>+        count = yield PurgePrincipalService.purgeUIDs(self.storeUnderTest(), self.directory,
+            self.rootResource, (self.uid,), verbose=False, proxies=False, completely=False)
</ins><span class="cx">         self.assertEquals(count, 1)
</span><span class="cx"> 
</span><span class="cx">         # And you still do
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">         home = (yield txn.calendarHomeWithUID(self.uid))
</span><span class="cx">         self.assertNotEquals(home, None)
</span><del>-        (yield txn.commit())
</del><ins>+        yield txn.commit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_purge_old_eventspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge_old_events.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge_old_events.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_purge_old_events.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,25 +18,22 @@
</span><span class="cx"> Tests for calendarserver.tools.purge
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from calendarserver.tools.purge import PurgeOldEventsService, PurgeAttachmentsService, \
-    PurgePrincipalService
</del><ins>+import os
</ins><span class="cx"> 
</span><ins>+from calendarserver.tools.purge import (
+    PurgeOldEventsService, PurgeAttachmentsService, PurgePrincipalService
+)
</ins><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from pycalendar.timezone import Timezone
</span><del>-
</del><span class="cx"> from twext.enterprise.dal.syntax import Update, Delete
</span><del>-from txweb2.http_headers import MimeType
-
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><del>-
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> from twistedcaldav.vcard import Component as VCardComponent
</span><del>-
</del><span class="cx"> from txdav.common.datastore.sql_tables import schema
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><ins>+from txweb2.http_headers import MimeType
</ins><span class="cx"> 
</span><del>-import os
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> now = DateTime.getToday().getYear()
</span><span class="lines">@@ -415,16 +412,16 @@
</span><span class="cx">         # Turn off delayed indexing option so we can have some useful tests
</span><span class="cx">         self.patch(config, &quot;FreeBusyIndexDelayedExpand&quot;, False)
</span><span class="cx"> 
</span><del>-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
-            )
-        )
</del><ins>+        # self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
+        #     os.path.join(
+        #         os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
+        #     )
+        # )
+        # self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
+        #     os.path.join(
+        #         os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
+        #     )
+        # )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -679,9 +676,9 @@
</span><span class="cx">         (yield txn.commit())
</span><span class="cx"> 
</span><span class="cx">         # Purge home1
</span><del>-        total, ignored = (yield PurgePrincipalService.purgeUIDs(self._sqlCalendarStore, self.directory,
</del><ins>+        total = yield PurgePrincipalService.purgeUIDs(self._sqlCalendarStore, self.directory,
</ins><span class="cx">             self.rootResource, (&quot;home1&quot;,), verbose=False, proxies=False,
</span><del>-            when=DateTime(now, 4, 1, 12, 0, 0, 0, Timezone(utc=True))))
</del><ins>+            when=DateTime(now, 4, 1, 12, 0, 0, 0, Timezone(utc=True)))
</ins><span class="cx"> 
</span><span class="cx">         # 4 items deleted: 3 events and 1 vcard
</span><span class="cx">         self.assertEquals(total, 4)
</span><span class="lines">@@ -716,8 +713,8 @@
</span><span class="cx">         (yield txn.commit())
</span><span class="cx"> 
</span><span class="cx">         # Purge home1 completely
</span><del>-        total, ignored = (yield PurgePrincipalService.purgeUIDs(self._sqlCalendarStore, self.directory,
-            self.rootResource, (&quot;home1&quot;,), verbose=False, proxies=False, completely=True))
</del><ins>+        total = yield PurgePrincipalService.purgeUIDs(self._sqlCalendarStore, self.directory,
+            self.rootResource, (&quot;home1&quot;,), verbose=False, proxies=False, completely=True)
</ins><span class="cx"> 
</span><span class="cx">         # 9 items deleted: 8 events and 1 vcard
</span><span class="cx">         self.assertEquals(total, 9)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolstesttest_resourcespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_resources.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_resources.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/test/test_resources.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,19 +20,18 @@
</span><span class="cx">     from twisted.internet.defer import inlineCallbacks, succeed
</span><span class="cx">     from twistedcaldav.directory.directory import DirectoryService
</span><span class="cx">     from twistedcaldav.test.util import TestCase
</span><del>-    import dsattributes
-    strGUID = dsattributes.kDS1AttrGeneratedUID
-    strName = dsattributes.kDS1AttrDistinguishedName
-    RUN_TESTS = True
</del><ins>+    strGUID = &quot;dsAttrTypeStandard:GeneratedUID&quot;
+    strName = &quot;dsAttrTypeStandard:RealName&quot;
+
</ins><span class="cx"> except ImportError:
</span><del>-    RUN_TESTS = False
</del><ins>+    pass
</ins><span class="cx"> 
</span><del>-
-
-if RUN_TESTS:
</del><ins>+else:
</ins><span class="cx">     class StubDirectoryRecord(object):
</span><span class="cx"> 
</span><del>-        def __init__(self, recordType, guid=None, shortNames=None, fullName=None):
</del><ins>+        def __init__(
+            self, recordType, guid=None, shortNames=None, fullName=None
+        ):
</ins><span class="cx">             self.recordType = recordType
</span><span class="cx">             self.guid = guid
</span><span class="cx">             self.shortNames = shortNames
</span><span class="lines">@@ -51,13 +50,16 @@
</span><span class="cx">         def createRecords(self, data):
</span><span class="cx">             for recordType, recordData in data:
</span><span class="cx">                 guid = recordData[&quot;guid&quot;]
</span><del>-                record = StubDirectoryRecord(recordType, guid=guid,
-                    shortNames=recordData['shortNames'],
-                    fullName=recordData['fullName'])
</del><ins>+                record = StubDirectoryRecord(
+                    recordType, guid=guid,
+                    shortNames=recordData[&quot;shortNames&quot;],
+                    fullName=recordData[&quot;fullName&quot;]
+                )
</ins><span class="cx">                 self.records[guid] = record
</span><span class="cx"> 
</span><del>-        def updateRecord(self, recordType, guid=None, shortNames=None,
-            fullName=None):
</del><ins>+        def updateRecord(
+            self, recordType, guid=None, shortNames=None, fullName=None
+        ):
</ins><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -92,35 +94,46 @@
</span><span class="cx">         def test_migrateResources(self):
</span><span class="cx"> 
</span><span class="cx">             data = {
</span><del>-                    dsattributes.kDSStdRecordTypeResources :
-                    [
-                        ['projector1', {
-                            strGUID : '6C99E240-E915-4012-82FA-99E0F638D7EF',
-                            strName : 'Projector 1'
-                        }],
-                        ['projector2', {
-                            strGUID : '7C99E240-E915-4012-82FA-99E0F638D7EF',
-                            strName : 'Projector 2'
-                        }],
-                    ],
-                    dsattributes.kDSStdRecordTypePlaces :
-                    [
-                        ['office1', {
-                            strGUID : '8C99E240-E915-4012-82FA-99E0F638D7EF',
-                            strName : 'Office 1'
-                        }],
-                    ],
-                }
</del><ins>+                &quot;dsRecTypeStandard:Resources&quot;:
+                [
+                    [&quot;projector1&quot;, {
+                        strGUID: &quot;6C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                        strName: &quot;Projector 1&quot;
+                    }],
+                    [&quot;projector2&quot;, {
+                        strGUID: &quot;7C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                        strName: &quot;Projector 2&quot;
+                    }],
+                ],
+                &quot;dsRecTypeStandard:Places&quot;:
+                [
+                    [&quot;office1&quot;, {
+                        strGUID: &quot;8C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                        strName: &quot;Office 1&quot;
+                    }],
+                ],
+            }
</ins><span class="cx"> 
</span><span class="cx">             def queryMethod(sourceService, recordType, verbose=False):
</span><span class="cx">                 return data[recordType]
</span><span class="cx"> 
</span><span class="cx">             directoryService = StubDirectoryService(StubAugmentService())
</span><del>-            yield migrateResources(None, directoryService, queryMethod=queryMethod)
</del><ins>+            yield migrateResources(
+                None, directoryService, queryMethod=queryMethod
+            )
</ins><span class="cx">             for guid, recordType in (
</span><del>-                ('6C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
-                ('7C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
-                ('8C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_locations),
</del><ins>+                (
+                    &quot;6C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_resources
+                ),
+                (
+                    &quot;7C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_resources
+                ),
+                (
+                    &quot;8C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_locations
+                ),
</ins><span class="cx">             ):
</span><span class="cx">                 self.assertTrue(guid in directoryService.records)
</span><span class="cx">                 record = directoryService.records[guid]
</span><span class="lines">@@ -131,27 +144,44 @@
</span><span class="cx">             #
</span><span class="cx">             # Add more to OD and re-migrate
</span><span class="cx">             #
</span><del>-            data[dsattributes.kDSStdRecordTypeResources].append(
-                ['projector3', {
-                    strGUID : '9C99E240-E915-4012-82FA-99E0F638D7EF',
-                    strName : 'Projector 3'
</del><ins>+            data[&quot;dsRecTypeStandard:Resources&quot;].append(
+                [&quot;projector3&quot;, {
+                    strGUID: &quot;9C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    strName: &quot;Projector 3&quot;
</ins><span class="cx">                 }]
</span><span class="cx">             )
</span><del>-            data[dsattributes.kDSStdRecordTypePlaces].append(
-                ['office2', {
-                    strGUID : 'AC99E240-E915-4012-82FA-99E0F638D7EF',
-                    strName : 'Office 2'
</del><ins>+            data[&quot;dsRecTypeStandard:Places&quot;].append(
+                [&quot;office2&quot;, {
+                    strGUID: &quot;AC99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    strName: &quot;Office 2&quot;
</ins><span class="cx">                 }]
</span><span class="cx">             )
</span><span class="cx"> 
</span><del>-            yield migrateResources(None, directoryService, queryMethod=queryMethod)
</del><ins>+            yield migrateResources(
+                None, directoryService, queryMethod=queryMethod
+            )
</ins><span class="cx"> 
</span><span class="cx">             for guid, recordType in (
</span><del>-                ('6C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
-                ('7C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
-                ('9C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
-                ('8C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_locations),
-                ('AC99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_locations),
</del><ins>+                (
+                    &quot;6C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_resources
+                ),
+                (
+                    &quot;7C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_resources
+                ),
+                (
+                    &quot;9C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_resources
+                ),
+                (
+                    &quot;8C99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_locations
+                ),
+                (
+                    &quot;AC99E240-E915-4012-82FA-99E0F638D7EF&quot;,
+                    DirectoryService.recordType_locations
+                ),
</ins><span class="cx">             ):
</span><span class="cx">                 self.assertTrue(guid in directoryService.records)
</span><span class="cx">                 record = directoryService.records[guid]
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarservertoolsutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/tools/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</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">@@ -37,26 +35,19 @@
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-from twisted.python.filepath import FilePath
-from twisted.python.reflect import namedClass
</del><span class="cx"> from twext.python.log import Logger
</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><span class="cx"> 
</span><del>-from calendarserver.provision.root import RootResource
</del><span class="cx"> 
</span><span class="cx"> from twistedcaldav import memcachepool
</span><del>-from twistedcaldav.directory import calendaruserproxy
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
</del><span class="cx"> from txdav.who.groups import schedulePolledGroupCachingUpdate
</span><del>-from calendarserver.push.notifier import NotifierFactory
</del><span class="cx"> 
</span><del>-from txdav.common.datastore.file import CommonDataStore
</del><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def loadConfig(configFileName):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Helper method for command-line utilities to load configuration plist
</span><span class="lines">@@ -78,145 +69,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 class="lines">@@ -334,6 +325,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def principalForPrincipalID(principalID, checkOnly=False, directory=None):
</span><span class="cx"> 
</span><span class="cx">     # Allow a directory parameter to be passed in, but default to config.directory
</span><span class="lines">@@ -351,16 +343,16 @@
</span><span class="cx">             raise ValueError(&quot;Can't resolve all paths yet&quot;)
</span><span class="cx"> 
</span><span class="cx">         if checkOnly:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><del>-        return directory.principalCollection.principalForUID(uid)
</del><ins>+        returnValue((yield directory.principalCollection.principalForUID(uid)))
</ins><span class="cx"> 
</span><span class="cx">     if principalID.startswith(&quot;(&quot;):
</span><span class="cx">         try:
</span><span class="cx">             i = principalID.index(&quot;)&quot;)
</span><span class="cx"> 
</span><span class="cx">             if checkOnly:
</span><del>-                return None
</del><ins>+                returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">             recordType = principalID[1:i]
</span><span class="cx">             shortName = principalID[i + 1:]
</span><span class="lines">@@ -368,34 +360,87 @@
</span><span class="cx">             if not recordType or not shortName or &quot;(&quot; in recordType:
</span><span class="cx">                 raise ValueError()
</span><span class="cx"> 
</span><del>-            return directory.principalCollection.principalForShortName(recordType, shortName)
</del><ins>+            returnValue((yield directory.principalCollection.principalForShortName(recordType, shortName)))
</ins><span class="cx"> 
</span><span class="cx">         except ValueError:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx">     if &quot;:&quot; in principalID:
</span><span class="cx">         if checkOnly:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         recordType, shortName = principalID.split(&quot;:&quot;, 1)
</span><span class="cx"> 
</span><del>-        return directory.principalCollection.principalForShortName(recordType, shortName)
</del><ins>+        returnValue((yield directory.principalCollection.principalForShortName(recordType, shortName)))
</ins><span class="cx"> 
</span><span class="cx">     try:
</span><span class="cx">         UUID(principalID)
</span><span class="cx"> 
</span><span class="cx">         if checkOnly:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><del>-        x = directory.principalCollection.principalForUID(principalID)
-        return x
</del><ins>+        returnValue((yield directory.principalCollection.principalForUID(principalID)))
</ins><span class="cx">     except ValueError:
</span><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx">     raise ValueError(&quot;Invalid principal identifier: %s&quot; % (principalID,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
+def recordForPrincipalID(directory, principalID, checkOnly=False):
</ins><span class="cx"> 
</span><ins>+    if principalID.startswith(&quot;/&quot;):
+        segments = principalID.strip(&quot;/&quot;).split(&quot;/&quot;)
+        if (len(segments) == 3 and
+            segments[0] == &quot;principals&quot; and segments[1] == &quot;__uids__&quot;):
+            uid = segments[2]
+        else:
+            raise ValueError(&quot;Can't resolve all paths yet&quot;)
+
+        if checkOnly:
+            returnValue(None)
+
+        returnValue((yield directory.recordWithUID(uid)))
+
+    if principalID.startswith(&quot;(&quot;):
+        try:
+            i = principalID.index(&quot;)&quot;)
+
+            if checkOnly:
+                returnValue(None)
+
+            recordType = directory.oldNameToRecordType(principalID[1:i])
+            shortName = principalID[i + 1:]
+
+            if not recordType or not shortName or &quot;(&quot; in recordType:
+                raise ValueError()
+
+            returnValue((yield directory.recordWithShortName(recordType, shortName)))
+
+        except ValueError:
+            pass
+
+    if &quot;:&quot; in principalID:
+        if checkOnly:
+            returnValue(None)
+
+        recordType, shortName = principalID.split(&quot;:&quot;, 1)
+        recordType = directory.oldNameToRecordType(recordType)
+
+        returnValue((yield directory.recordWithShortName(recordType, shortName)))
+
+    try:
+        if checkOnly:
+            returnValue(None)
+
+        returnValue((yield directory.recordWithUID(principalID)))
+    except ValueError:
+        pass
+
+    raise ValueError(&quot;Invalid principal identifier: %s&quot; % (principalID,))
+
+
+
</ins><span class="cx"> def proxySubprincipal(principal, proxyType):
</span><span class="cx">     return principal.getChild(&quot;calendar-proxy-&quot; + proxyType)
</span><span class="cx"> 
</span><span class="lines">@@ -501,12 +546,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def prettyPrincipal(principal):
</span><del>-    record = principal.record
-    return &quot;\&quot;%s\&quot; (%s:%s)&quot; % (record.fullName, record.recordType,
-        record.shortNames[0])
</del><ins>+    prettyRecord(principal.record)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def prettyRecord(record):
+    return &quot;\&quot;{d}\&quot; {uid} ({rt}) {sn}&quot;.format(
+        d=record.displayName,
+        rt=record.recordType.name,
+        uid=record.uid,
+        sn=(&quot;, &quot;.join(record.shortNames))
+    )
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class ProxyError(Exception):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Raised when proxy assignments cannot be performed
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverwebadminprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/webadmin/principals.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/webadmin/principals.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/webadmin/principals.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -121,16 +121,17 @@
</span><span class="cx">         self._store = store
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            returnValue(self)
</ins><span class="cx"> 
</span><del>-        record = self._directory.recordWithUID(name)
</del><ins>+        record = yield self._directory.recordWithUID(name)
</ins><span class="cx"> 
</span><span class="cx">         if record:
</span><del>-            return PrincipalResource(record, self._store)
</del><ins>+            returnValue(PrincipalResource(record, self._store))
</ins><span class="cx">         else:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5calendarserverwebcalresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/calendarserver/webcal/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/calendarserver/webcal/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/calendarserver/webcal/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -48,15 +48,17 @@
</span><span class="cx"> class WebCalendarResource (ReadOnlyResourceMixIn, DAVFile):
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return davxml.ACL(
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
</del><ins>+        return succeed(
+            davxml.ACL(
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
</ins><span class="cx">                 ),
</span><del>-                davxml.Protected(),
-                TwistedACLInheritable(),
-            ),
</del><ins>+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confauthaccountstestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/conf/auth/accounts-test.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/auth/accounts-test.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/auth/accounts-test.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -16,174 +16,1716 @@
</span><span class="cx"> limitations under the License.
</span><span class="cx">  --&gt;
</span><span class="cx"> 
</span><del>-&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;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&gt;
+&lt;record&gt;
+    &lt;uid&gt;0C8BDE62-E600-4696-83D3-8B5ECABDFD2E&lt;/uid&gt;
+    &lt;guid&gt;0C8BDE62-E600-4696-83D3-8B5ECABDFD2E&lt;/guid&gt;
+    &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;
-    &lt;uid&gt;apprentice&lt;/uid&gt;
-    &lt;guid&gt;apprentice&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Super User&lt;/full-name&gt;
+    &lt;email&gt;admin@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;29B6C503-11DF-43EC-8CCA-40C7003149CE&lt;/uid&gt;
+    &lt;guid&gt;29B6C503-11DF-43EC-8CCA-40C7003149CE&lt;/guid&gt;
+    &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;
-    &lt;uid&gt;wsanchez&lt;/uid&gt;
-    &lt;guid&gt;wsanchez&lt;/guid&gt;
-    &lt;email-address&gt;wsanchez@example.com&lt;/email-address&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &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;
-    &lt;uid&gt;cdaboo&lt;/uid&gt;
-    &lt;guid&gt;cdaboo&lt;/guid&gt;
-    &lt;email-address&gt;cdaboo@example.com&lt;/email-address&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &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;
-    &lt;uid&gt;sagen&lt;/uid&gt;
-    &lt;guid&gt;sagen&lt;/guid&gt;
-    &lt;email-address&gt;sagen@example.com&lt;/email-address&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &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;
-    &lt;uid&gt;dre&lt;/uid&gt;
-    &lt;guid&gt;andre&lt;/guid&gt;
-    &lt;email-address&gt;dre@example.com&lt;/email-address&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &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;
-    &lt;uid&gt;glyph&lt;/uid&gt;
-    &lt;guid&gt;glyph&lt;/guid&gt;
-    &lt;email-address&gt;glyph@example.com&lt;/email-address&gt;
-    &lt;password&gt;test&lt;/password&gt;
-    &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;
-    &lt;uid&gt;i18nuser&lt;/uid&gt;
-    &lt;guid&gt;i18nuser&lt;/guid&gt;
-    &lt;email-address&gt;i18nuser@example.com&lt;/email-address&gt;
</del><ins>+    &lt;full-name&gt;Apprentice Super User&lt;/full-name&gt;
+    &lt;email&gt;apprentice@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;860B3EE9-6D7C-4296-9639-E6B998074A78&lt;/uid&gt;
+    &lt;guid&gt;860B3EE9-6D7C-4296-9639-E6B998074A78&lt;/guid&gt;
+    &lt;short-name&gt;i18nuser&lt;/short-name&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;
-  &lt;user repeat=&quot;101&quot;&gt;
-    &lt;uid&gt;user%02d&lt;/uid&gt;
-    &lt;uid&gt;User %02d&lt;/uid&gt;
-    &lt;guid&gt;user%02d&lt;/guid&gt;
-    &lt;password&gt;user%02d&lt;/password&gt;
-    &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;
-  &lt;user repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;public%02d&lt;/uid&gt;
-    &lt;guid&gt;public%02d&lt;/guid&gt;
-    &lt;password&gt;public%02d&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group01&lt;/uid&gt;
-    &lt;guid&gt;group01&lt;/guid&gt;
-    &lt;password&gt;group01&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group02&lt;/uid&gt;
-    &lt;guid&gt;group02&lt;/guid&gt;
-    &lt;password&gt;group02&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group03&lt;/uid&gt;
-    &lt;guid&gt;group03&lt;/guid&gt;
-    &lt;password&gt;group03&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group04&lt;/uid&gt;
-    &lt;guid&gt;group04&lt;/guid&gt;
-    &lt;password&gt;group04&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group05&lt;/uid&gt;
-    &lt;guid&gt;group05&lt;/guid&gt;
-    &lt;password&gt;group05&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group06&lt;/uid&gt;
-    &lt;guid&gt;group06&lt;/guid&gt;
-    &lt;password&gt;group06&lt;/password&gt;
-    &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;
-    &lt;uid&gt;group07&lt;/uid&gt;
-    &lt;guid&gt;group07&lt;/guid&gt;
-    &lt;password&gt;group07&lt;/password&gt;
-    &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;
-    &lt;uid&gt;disabledgroup&lt;/uid&gt;
-    &lt;guid&gt;disabledgroup&lt;/guid&gt;
-    &lt;password&gt;disabledgroup&lt;/password&gt;
-    &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;まだ&lt;/full-name&gt;
+    &lt;email&gt;i18nuser@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user01&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000001&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000001&lt;/guid&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;10000000-0000-0000-0000-000000000002&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000002&lt;/guid&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;10000000-0000-0000-0000-000000000003&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000003&lt;/guid&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;10000000-0000-0000-0000-000000000004&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000004&lt;/guid&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;10000000-0000-0000-0000-000000000005&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000005&lt;/guid&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;10000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000006&lt;/guid&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;10000000-0000-0000-0000-000000000007&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000007&lt;/guid&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;10000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000008&lt;/guid&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;10000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000009&lt;/guid&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;10000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000010&lt;/guid&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;10000000-0000-0000-0000-000000000011&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000011&lt;/guid&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;10000000-0000-0000-0000-000000000012&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000012&lt;/guid&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;10000000-0000-0000-0000-000000000013&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000013&lt;/guid&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;10000000-0000-0000-0000-000000000014&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000014&lt;/guid&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;10000000-0000-0000-0000-000000000015&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000015&lt;/guid&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;10000000-0000-0000-0000-000000000016&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000016&lt;/guid&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;10000000-0000-0000-0000-000000000017&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000017&lt;/guid&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;10000000-0000-0000-0000-000000000018&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000018&lt;/guid&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;10000000-0000-0000-0000-000000000019&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000019&lt;/guid&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;10000000-0000-0000-0000-000000000020&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000020&lt;/guid&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;10000000-0000-0000-0000-000000000021&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000021&lt;/guid&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;10000000-0000-0000-0000-000000000022&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000022&lt;/guid&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;10000000-0000-0000-0000-000000000023&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000023&lt;/guid&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;10000000-0000-0000-0000-000000000024&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000024&lt;/guid&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;10000000-0000-0000-0000-000000000025&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000025&lt;/guid&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;user&quot;&gt;
+    &lt;short-name&gt;user26&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000026&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000026&lt;/guid&gt;
+    &lt;password&gt;user26&lt;/password&gt;
+    &lt;full-name&gt;User 26&lt;/full-name&gt;
+    &lt;email&gt;user26@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user27&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000027&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000027&lt;/guid&gt;
+    &lt;password&gt;user27&lt;/password&gt;
+    &lt;full-name&gt;User 27&lt;/full-name&gt;
+    &lt;email&gt;user27@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user28&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000028&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000028&lt;/guid&gt;
+    &lt;password&gt;user28&lt;/password&gt;
+    &lt;full-name&gt;User 28&lt;/full-name&gt;
+    &lt;email&gt;user28@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user29&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000029&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000029&lt;/guid&gt;
+    &lt;password&gt;user29&lt;/password&gt;
+    &lt;full-name&gt;User 29&lt;/full-name&gt;
+    &lt;email&gt;user29@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user30&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000030&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000030&lt;/guid&gt;
+    &lt;password&gt;user30&lt;/password&gt;
+    &lt;full-name&gt;User 30&lt;/full-name&gt;
+    &lt;email&gt;user30@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user31&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000031&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000031&lt;/guid&gt;
+    &lt;password&gt;user31&lt;/password&gt;
+    &lt;full-name&gt;User 31&lt;/full-name&gt;
+    &lt;email&gt;user31@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user32&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000032&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000032&lt;/guid&gt;
+    &lt;password&gt;user32&lt;/password&gt;
+    &lt;full-name&gt;User 32&lt;/full-name&gt;
+    &lt;email&gt;user32@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user33&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000033&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000033&lt;/guid&gt;
+    &lt;password&gt;user33&lt;/password&gt;
+    &lt;full-name&gt;User 33&lt;/full-name&gt;
+    &lt;email&gt;user33@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user34&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000034&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000034&lt;/guid&gt;
+    &lt;password&gt;user34&lt;/password&gt;
+    &lt;full-name&gt;User 34&lt;/full-name&gt;
+    &lt;email&gt;user34@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user35&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000035&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000035&lt;/guid&gt;
+    &lt;password&gt;user35&lt;/password&gt;
+    &lt;full-name&gt;User 35&lt;/full-name&gt;
+    &lt;email&gt;user35@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user36&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000036&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000036&lt;/guid&gt;
+    &lt;password&gt;user36&lt;/password&gt;
+    &lt;full-name&gt;User 36&lt;/full-name&gt;
+    &lt;email&gt;user36@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user37&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000037&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000037&lt;/guid&gt;
+    &lt;password&gt;user37&lt;/password&gt;
+    &lt;full-name&gt;User 37&lt;/full-name&gt;
+    &lt;email&gt;user37@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user38&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000038&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000038&lt;/guid&gt;
+    &lt;password&gt;user38&lt;/password&gt;
+    &lt;full-name&gt;User 38&lt;/full-name&gt;
+    &lt;email&gt;user38@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user39&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000039&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000039&lt;/guid&gt;
+    &lt;password&gt;user39&lt;/password&gt;
+    &lt;full-name&gt;User 39&lt;/full-name&gt;
+    &lt;email&gt;user39@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user40&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000040&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000040&lt;/guid&gt;
+    &lt;password&gt;user40&lt;/password&gt;
+    &lt;full-name&gt;User 40&lt;/full-name&gt;
+    &lt;email&gt;user40@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user41&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000041&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000041&lt;/guid&gt;
+    &lt;password&gt;user41&lt;/password&gt;
+    &lt;full-name&gt;User 41&lt;/full-name&gt;
+    &lt;email&gt;user41@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user42&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000042&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000042&lt;/guid&gt;
+    &lt;password&gt;user42&lt;/password&gt;
+    &lt;full-name&gt;User 42&lt;/full-name&gt;
+    &lt;email&gt;user42@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user43&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000043&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000043&lt;/guid&gt;
+    &lt;password&gt;user43&lt;/password&gt;
+    &lt;full-name&gt;User 43&lt;/full-name&gt;
+    &lt;email&gt;user43@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user44&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000044&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000044&lt;/guid&gt;
+    &lt;password&gt;user44&lt;/password&gt;
+    &lt;full-name&gt;User 44&lt;/full-name&gt;
+    &lt;email&gt;user44@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user45&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000045&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000045&lt;/guid&gt;
+    &lt;password&gt;user45&lt;/password&gt;
+    &lt;full-name&gt;User 45&lt;/full-name&gt;
+    &lt;email&gt;user45@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user46&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000046&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000046&lt;/guid&gt;
+    &lt;password&gt;user46&lt;/password&gt;
+    &lt;full-name&gt;User 46&lt;/full-name&gt;
+    &lt;email&gt;user46@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user47&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000047&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000047&lt;/guid&gt;
+    &lt;password&gt;user47&lt;/password&gt;
+    &lt;full-name&gt;User 47&lt;/full-name&gt;
+    &lt;email&gt;user47@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user48&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000048&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000048&lt;/guid&gt;
+    &lt;password&gt;user48&lt;/password&gt;
+    &lt;full-name&gt;User 48&lt;/full-name&gt;
+    &lt;email&gt;user48@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user49&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000049&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000049&lt;/guid&gt;
+    &lt;password&gt;user49&lt;/password&gt;
+    &lt;full-name&gt;User 49&lt;/full-name&gt;
+    &lt;email&gt;user49@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user50&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000050&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000050&lt;/guid&gt;
+    &lt;password&gt;user50&lt;/password&gt;
+    &lt;full-name&gt;User 50&lt;/full-name&gt;
+    &lt;email&gt;user50@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user51&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000051&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000051&lt;/guid&gt;
+    &lt;password&gt;user51&lt;/password&gt;
+    &lt;full-name&gt;User 51&lt;/full-name&gt;
+    &lt;email&gt;user51@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user52&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000052&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000052&lt;/guid&gt;
+    &lt;password&gt;user52&lt;/password&gt;
+    &lt;full-name&gt;User 52&lt;/full-name&gt;
+    &lt;email&gt;user52@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user53&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000053&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000053&lt;/guid&gt;
+    &lt;password&gt;user53&lt;/password&gt;
+    &lt;full-name&gt;User 53&lt;/full-name&gt;
+    &lt;email&gt;user53@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user54&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000054&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000054&lt;/guid&gt;
+    &lt;password&gt;user54&lt;/password&gt;
+    &lt;full-name&gt;User 54&lt;/full-name&gt;
+    &lt;email&gt;user54@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user55&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000055&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000055&lt;/guid&gt;
+    &lt;password&gt;user55&lt;/password&gt;
+    &lt;full-name&gt;User 55&lt;/full-name&gt;
+    &lt;email&gt;user55@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user56&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000056&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000056&lt;/guid&gt;
+    &lt;password&gt;user56&lt;/password&gt;
+    &lt;full-name&gt;User 56&lt;/full-name&gt;
+    &lt;email&gt;user56@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user57&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000057&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000057&lt;/guid&gt;
+    &lt;password&gt;user57&lt;/password&gt;
+    &lt;full-name&gt;User 57&lt;/full-name&gt;
+    &lt;email&gt;user57@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user58&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000058&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000058&lt;/guid&gt;
+    &lt;password&gt;user58&lt;/password&gt;
+    &lt;full-name&gt;User 58&lt;/full-name&gt;
+    &lt;email&gt;user58@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user59&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000059&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000059&lt;/guid&gt;
+    &lt;password&gt;user59&lt;/password&gt;
+    &lt;full-name&gt;User 59&lt;/full-name&gt;
+    &lt;email&gt;user59@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user60&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000060&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000060&lt;/guid&gt;
+    &lt;password&gt;user60&lt;/password&gt;
+    &lt;full-name&gt;User 60&lt;/full-name&gt;
+    &lt;email&gt;user60@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user61&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000061&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000061&lt;/guid&gt;
+    &lt;password&gt;user61&lt;/password&gt;
+    &lt;full-name&gt;User 61&lt;/full-name&gt;
+    &lt;email&gt;user61@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user62&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000062&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000062&lt;/guid&gt;
+    &lt;password&gt;user62&lt;/password&gt;
+    &lt;full-name&gt;User 62&lt;/full-name&gt;
+    &lt;email&gt;user62@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user63&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000063&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000063&lt;/guid&gt;
+    &lt;password&gt;user63&lt;/password&gt;
+    &lt;full-name&gt;User 63&lt;/full-name&gt;
+    &lt;email&gt;user63@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user64&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000064&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000064&lt;/guid&gt;
+    &lt;password&gt;user64&lt;/password&gt;
+    &lt;full-name&gt;User 64&lt;/full-name&gt;
+    &lt;email&gt;user64@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user65&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000065&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000065&lt;/guid&gt;
+    &lt;password&gt;user65&lt;/password&gt;
+    &lt;full-name&gt;User 65&lt;/full-name&gt;
+    &lt;email&gt;user65@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user66&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000066&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000066&lt;/guid&gt;
+    &lt;password&gt;user66&lt;/password&gt;
+    &lt;full-name&gt;User 66&lt;/full-name&gt;
+    &lt;email&gt;user66@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user67&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000067&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000067&lt;/guid&gt;
+    &lt;password&gt;user67&lt;/password&gt;
+    &lt;full-name&gt;User 67&lt;/full-name&gt;
+    &lt;email&gt;user67@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user68&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000068&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000068&lt;/guid&gt;
+    &lt;password&gt;user68&lt;/password&gt;
+    &lt;full-name&gt;User 68&lt;/full-name&gt;
+    &lt;email&gt;user68@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user69&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000069&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000069&lt;/guid&gt;
+    &lt;password&gt;user69&lt;/password&gt;
+    &lt;full-name&gt;User 69&lt;/full-name&gt;
+    &lt;email&gt;user69@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user70&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000070&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000070&lt;/guid&gt;
+    &lt;password&gt;user70&lt;/password&gt;
+    &lt;full-name&gt;User 70&lt;/full-name&gt;
+    &lt;email&gt;user70@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user71&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000071&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000071&lt;/guid&gt;
+    &lt;password&gt;user71&lt;/password&gt;
+    &lt;full-name&gt;User 71&lt;/full-name&gt;
+    &lt;email&gt;user71@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user72&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000072&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000072&lt;/guid&gt;
+    &lt;password&gt;user72&lt;/password&gt;
+    &lt;full-name&gt;User 72&lt;/full-name&gt;
+    &lt;email&gt;user72@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user73&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000073&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000073&lt;/guid&gt;
+    &lt;password&gt;user73&lt;/password&gt;
+    &lt;full-name&gt;User 73&lt;/full-name&gt;
+    &lt;email&gt;user73@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user74&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000074&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000074&lt;/guid&gt;
+    &lt;password&gt;user74&lt;/password&gt;
+    &lt;full-name&gt;User 74&lt;/full-name&gt;
+    &lt;email&gt;user74@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user75&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000075&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000075&lt;/guid&gt;
+    &lt;password&gt;user75&lt;/password&gt;
+    &lt;full-name&gt;User 75&lt;/full-name&gt;
+    &lt;email&gt;user75@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user76&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000076&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000076&lt;/guid&gt;
+    &lt;password&gt;user76&lt;/password&gt;
+    &lt;full-name&gt;User 76&lt;/full-name&gt;
+    &lt;email&gt;user76@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user77&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000077&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000077&lt;/guid&gt;
+    &lt;password&gt;user77&lt;/password&gt;
+    &lt;full-name&gt;User 77&lt;/full-name&gt;
+    &lt;email&gt;user77@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user78&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000078&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000078&lt;/guid&gt;
+    &lt;password&gt;user78&lt;/password&gt;
+    &lt;full-name&gt;User 78&lt;/full-name&gt;
+    &lt;email&gt;user78@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user79&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000079&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000079&lt;/guid&gt;
+    &lt;password&gt;user79&lt;/password&gt;
+    &lt;full-name&gt;User 79&lt;/full-name&gt;
+    &lt;email&gt;user79@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user80&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000080&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000080&lt;/guid&gt;
+    &lt;password&gt;user80&lt;/password&gt;
+    &lt;full-name&gt;User 80&lt;/full-name&gt;
+    &lt;email&gt;user80@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user81&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000081&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000081&lt;/guid&gt;
+    &lt;password&gt;user81&lt;/password&gt;
+    &lt;full-name&gt;User 81&lt;/full-name&gt;
+    &lt;email&gt;user81@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user82&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000082&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000082&lt;/guid&gt;
+    &lt;password&gt;user82&lt;/password&gt;
+    &lt;full-name&gt;User 82&lt;/full-name&gt;
+    &lt;email&gt;user82@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user83&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000083&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000083&lt;/guid&gt;
+    &lt;password&gt;user83&lt;/password&gt;
+    &lt;full-name&gt;User 83&lt;/full-name&gt;
+    &lt;email&gt;user83@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user84&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000084&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000084&lt;/guid&gt;
+    &lt;password&gt;user84&lt;/password&gt;
+    &lt;full-name&gt;User 84&lt;/full-name&gt;
+    &lt;email&gt;user84@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user85&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000085&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000085&lt;/guid&gt;
+    &lt;password&gt;user85&lt;/password&gt;
+    &lt;full-name&gt;User 85&lt;/full-name&gt;
+    &lt;email&gt;user85@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user86&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000086&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000086&lt;/guid&gt;
+    &lt;password&gt;user86&lt;/password&gt;
+    &lt;full-name&gt;User 86&lt;/full-name&gt;
+    &lt;email&gt;user86@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user87&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000087&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000087&lt;/guid&gt;
+    &lt;password&gt;user87&lt;/password&gt;
+    &lt;full-name&gt;User 87&lt;/full-name&gt;
+    &lt;email&gt;user87@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user88&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000088&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000088&lt;/guid&gt;
+    &lt;password&gt;user88&lt;/password&gt;
+    &lt;full-name&gt;User 88&lt;/full-name&gt;
+    &lt;email&gt;user88@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user89&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000089&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000089&lt;/guid&gt;
+    &lt;password&gt;user89&lt;/password&gt;
+    &lt;full-name&gt;User 89&lt;/full-name&gt;
+    &lt;email&gt;user89@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user90&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000090&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000090&lt;/guid&gt;
+    &lt;password&gt;user90&lt;/password&gt;
+    &lt;full-name&gt;User 90&lt;/full-name&gt;
+    &lt;email&gt;user90@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user91&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000091&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000091&lt;/guid&gt;
+    &lt;password&gt;user91&lt;/password&gt;
+    &lt;full-name&gt;User 91&lt;/full-name&gt;
+    &lt;email&gt;user91@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user92&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000092&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000092&lt;/guid&gt;
+    &lt;password&gt;user92&lt;/password&gt;
+    &lt;full-name&gt;User 92&lt;/full-name&gt;
+    &lt;email&gt;user92@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user93&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000093&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000093&lt;/guid&gt;
+    &lt;password&gt;user93&lt;/password&gt;
+    &lt;full-name&gt;User 93&lt;/full-name&gt;
+    &lt;email&gt;user93@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user94&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000094&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000094&lt;/guid&gt;
+    &lt;password&gt;user94&lt;/password&gt;
+    &lt;full-name&gt;User 94&lt;/full-name&gt;
+    &lt;email&gt;user94@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user95&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000095&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000095&lt;/guid&gt;
+    &lt;password&gt;user95&lt;/password&gt;
+    &lt;full-name&gt;User 95&lt;/full-name&gt;
+    &lt;email&gt;user95@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user96&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000096&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000096&lt;/guid&gt;
+    &lt;password&gt;user96&lt;/password&gt;
+    &lt;full-name&gt;User 96&lt;/full-name&gt;
+    &lt;email&gt;user96@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user97&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000097&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000097&lt;/guid&gt;
+    &lt;password&gt;user97&lt;/password&gt;
+    &lt;full-name&gt;User 97&lt;/full-name&gt;
+    &lt;email&gt;user97@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user98&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000098&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000098&lt;/guid&gt;
+    &lt;password&gt;user98&lt;/password&gt;
+    &lt;full-name&gt;User 98&lt;/full-name&gt;
+    &lt;email&gt;user98@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user99&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000099&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000099&lt;/guid&gt;
+    &lt;password&gt;user99&lt;/password&gt;
+    &lt;full-name&gt;User 99&lt;/full-name&gt;
+    &lt;email&gt;user99@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user100&lt;/short-name&gt;
+    &lt;uid&gt;10000000-0000-0000-0000-000000000100&lt;/uid&gt;
+    &lt;guid&gt;10000000-0000-0000-0000-000000000100&lt;/guid&gt;
+    &lt;password&gt;user100&lt;/password&gt;
+    &lt;full-name&gt;User 100&lt;/full-name&gt;
+    &lt;email&gt;user100@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public01&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000001&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000001&lt;/guid&gt;
+    &lt;password&gt;public01&lt;/password&gt;
+    &lt;full-name&gt;Public 01&lt;/full-name&gt;
+    &lt;email&gt;public01@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public02&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000002&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000002&lt;/guid&gt;
+    &lt;password&gt;public02&lt;/password&gt;
+    &lt;full-name&gt;Public 02&lt;/full-name&gt;
+    &lt;email&gt;public02@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public03&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000003&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000003&lt;/guid&gt;
+    &lt;password&gt;public03&lt;/password&gt;
+    &lt;full-name&gt;Public 03&lt;/full-name&gt;
+    &lt;email&gt;public03@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public04&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000004&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000004&lt;/guid&gt;
+    &lt;password&gt;public04&lt;/password&gt;
+    &lt;full-name&gt;Public 04&lt;/full-name&gt;
+    &lt;email&gt;public04@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public05&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000005&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000005&lt;/guid&gt;
+    &lt;password&gt;public05&lt;/password&gt;
+    &lt;full-name&gt;Public 05&lt;/full-name&gt;
+    &lt;email&gt;public05@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public06&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000006&lt;/guid&gt;
+    &lt;password&gt;public06&lt;/password&gt;
+    &lt;full-name&gt;Public 06&lt;/full-name&gt;
+    &lt;email&gt;public06@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public07&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000007&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000007&lt;/guid&gt;
+    &lt;password&gt;public07&lt;/password&gt;
+    &lt;full-name&gt;Public 07&lt;/full-name&gt;
+    &lt;email&gt;public07@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public08&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000008&lt;/guid&gt;
+    &lt;password&gt;public08&lt;/password&gt;
+    &lt;full-name&gt;Public 08&lt;/full-name&gt;
+    &lt;email&gt;public08@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public09&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000009&lt;/guid&gt;
+    &lt;password&gt;public09&lt;/password&gt;
+    &lt;full-name&gt;Public 09&lt;/full-name&gt;
+    &lt;email&gt;public09@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public10&lt;/short-name&gt;
+    &lt;uid&gt;50000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;guid&gt;50000000-0000-0000-0000-000000000010&lt;/guid&gt;
+    &lt;password&gt;public10&lt;/password&gt;
+    &lt;full-name&gt;Public 10&lt;/full-name&gt;
+    &lt;email&gt;public10@example.com&lt;/email&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group01&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000001&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000001&lt;/guid&gt;
+    &lt;full-name&gt;Group 01&lt;/full-name&gt;
+    &lt;email&gt;group01@example.com&lt;/email&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000001&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group02&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000002&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000002&lt;/guid&gt;
+    &lt;full-name&gt;Group 02&lt;/full-name&gt;
+    &lt;email&gt;group02@example.com&lt;/email&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000006&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000007&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group03&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000003&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000003&lt;/guid&gt;
+    &lt;full-name&gt;Group 03&lt;/full-name&gt;
+    &lt;email&gt;group03@example.com&lt;/email&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000008&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000009&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group04&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000004&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000004&lt;/guid&gt;
+    &lt;full-name&gt;Group 04&lt;/full-name&gt;
+    &lt;email&gt;group04@example.com&lt;/email&gt;
+    &lt;member-uid&gt;20000000-0000-0000-0000-000000000002&lt;/member-uid&gt;
+    &lt;member-uid&gt;20000000-0000-0000-0000-000000000003&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000010&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group05&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000005&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000005&lt;/guid&gt;
+    &lt;full-name&gt;Group 05&lt;/full-name&gt;
+    &lt;email&gt;group05@example.com&lt;/email&gt;
+    &lt;member-uid&gt;20000000-0000-0000-0000-000000000006&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000020&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group06&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000006&lt;/guid&gt;
+    &lt;full-name&gt;Group 06&lt;/full-name&gt;
+    &lt;email&gt;group06@example.com&lt;/email&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000021&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group07&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000007&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000007&lt;/guid&gt;
+    &lt;full-name&gt;Group 07&lt;/full-name&gt;
+    &lt;email&gt;group07@example.com&lt;/email&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000022&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000023&lt;/member-uid&gt;
+    &lt;member-uid&gt;10000000-0000-0000-0000-000000000024&lt;/member-uid&gt;
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group08&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000008&lt;/guid&gt;
+    &lt;full-name&gt;Group 08&lt;/full-name&gt;
+    &lt;email&gt;group08@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group09&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000009&lt;/guid&gt;
+    &lt;full-name&gt;Group 09&lt;/full-name&gt;
+    &lt;email&gt;group09@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group10&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000010&lt;/guid&gt;
+    &lt;full-name&gt;Group 10&lt;/full-name&gt;
+    &lt;email&gt;group10@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group11&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000011&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000011&lt;/guid&gt;
+    &lt;full-name&gt;Group 11&lt;/full-name&gt;
+    &lt;email&gt;group11@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group12&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000012&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000012&lt;/guid&gt;
+    &lt;full-name&gt;Group 12&lt;/full-name&gt;
+    &lt;email&gt;group12@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group13&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000013&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000013&lt;/guid&gt;
+    &lt;full-name&gt;Group 13&lt;/full-name&gt;
+    &lt;email&gt;group13@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group14&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000014&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000014&lt;/guid&gt;
+    &lt;full-name&gt;Group 14&lt;/full-name&gt;
+    &lt;email&gt;group14@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group15&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000015&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000015&lt;/guid&gt;
+    &lt;full-name&gt;Group 15&lt;/full-name&gt;
+    &lt;email&gt;group15@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group16&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000016&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000016&lt;/guid&gt;
+    &lt;full-name&gt;Group 16&lt;/full-name&gt;
+    &lt;email&gt;group16@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group17&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000017&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000017&lt;/guid&gt;
+    &lt;full-name&gt;Group 17&lt;/full-name&gt;
+    &lt;email&gt;group17@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group18&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000018&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000018&lt;/guid&gt;
+    &lt;full-name&gt;Group 18&lt;/full-name&gt;
+    &lt;email&gt;group18@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group19&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000019&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000019&lt;/guid&gt;
+    &lt;full-name&gt;Group 19&lt;/full-name&gt;
+    &lt;email&gt;group19@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group20&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000020&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000020&lt;/guid&gt;
+    &lt;full-name&gt;Group 20&lt;/full-name&gt;
+    &lt;email&gt;group20@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group21&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000021&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000021&lt;/guid&gt;
+    &lt;full-name&gt;Group 21&lt;/full-name&gt;
+    &lt;email&gt;group21@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group22&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000022&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000022&lt;/guid&gt;
+    &lt;full-name&gt;Group 22&lt;/full-name&gt;
+    &lt;email&gt;group22@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group23&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000023&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000023&lt;/guid&gt;
+    &lt;full-name&gt;Group 23&lt;/full-name&gt;
+    &lt;email&gt;group23@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group24&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000024&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000024&lt;/guid&gt;
+    &lt;full-name&gt;Group 24&lt;/full-name&gt;
+    &lt;email&gt;group24@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group25&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000025&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000025&lt;/guid&gt;
+    &lt;full-name&gt;Group 25&lt;/full-name&gt;
+    &lt;email&gt;group25@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group26&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000026&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000026&lt;/guid&gt;
+    &lt;full-name&gt;Group 26&lt;/full-name&gt;
+    &lt;email&gt;group26@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group27&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000027&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000027&lt;/guid&gt;
+    &lt;full-name&gt;Group 27&lt;/full-name&gt;
+    &lt;email&gt;group27@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group28&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000028&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000028&lt;/guid&gt;
+    &lt;full-name&gt;Group 28&lt;/full-name&gt;
+    &lt;email&gt;group28@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group29&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000029&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000029&lt;/guid&gt;
+    &lt;full-name&gt;Group 29&lt;/full-name&gt;
+    &lt;email&gt;group29@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group30&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000030&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000030&lt;/guid&gt;
+    &lt;full-name&gt;Group 30&lt;/full-name&gt;
+    &lt;email&gt;group30@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group31&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000031&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000031&lt;/guid&gt;
+    &lt;full-name&gt;Group 31&lt;/full-name&gt;
+    &lt;email&gt;group31@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group32&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000032&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000032&lt;/guid&gt;
+    &lt;full-name&gt;Group 32&lt;/full-name&gt;
+    &lt;email&gt;group32@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group33&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000033&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000033&lt;/guid&gt;
+    &lt;full-name&gt;Group 33&lt;/full-name&gt;
+    &lt;email&gt;group33@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group34&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000034&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000034&lt;/guid&gt;
+    &lt;full-name&gt;Group 34&lt;/full-name&gt;
+    &lt;email&gt;group34@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group35&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000035&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000035&lt;/guid&gt;
+    &lt;full-name&gt;Group 35&lt;/full-name&gt;
+    &lt;email&gt;group35@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group36&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000036&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000036&lt;/guid&gt;
+    &lt;full-name&gt;Group 36&lt;/full-name&gt;
+    &lt;email&gt;group36@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group37&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000037&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000037&lt;/guid&gt;
+    &lt;full-name&gt;Group 37&lt;/full-name&gt;
+    &lt;email&gt;group37@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group38&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000038&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000038&lt;/guid&gt;
+    &lt;full-name&gt;Group 38&lt;/full-name&gt;
+    &lt;email&gt;group38@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group39&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000039&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000039&lt;/guid&gt;
+    &lt;full-name&gt;Group 39&lt;/full-name&gt;
+    &lt;email&gt;group39@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group40&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000040&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000040&lt;/guid&gt;
+    &lt;full-name&gt;Group 40&lt;/full-name&gt;
+    &lt;email&gt;group40@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group41&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000041&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000041&lt;/guid&gt;
+    &lt;full-name&gt;Group 41&lt;/full-name&gt;
+    &lt;email&gt;group41@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group42&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000042&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000042&lt;/guid&gt;
+    &lt;full-name&gt;Group 42&lt;/full-name&gt;
+    &lt;email&gt;group42@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group43&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000043&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000043&lt;/guid&gt;
+    &lt;full-name&gt;Group 43&lt;/full-name&gt;
+    &lt;email&gt;group43@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group44&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000044&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000044&lt;/guid&gt;
+    &lt;full-name&gt;Group 44&lt;/full-name&gt;
+    &lt;email&gt;group44@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group45&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000045&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000045&lt;/guid&gt;
+    &lt;full-name&gt;Group 45&lt;/full-name&gt;
+    &lt;email&gt;group45@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group46&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000046&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000046&lt;/guid&gt;
+    &lt;full-name&gt;Group 46&lt;/full-name&gt;
+    &lt;email&gt;group46@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group47&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000047&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000047&lt;/guid&gt;
+    &lt;full-name&gt;Group 47&lt;/full-name&gt;
+    &lt;email&gt;group47@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group48&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000048&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000048&lt;/guid&gt;
+    &lt;full-name&gt;Group 48&lt;/full-name&gt;
+    &lt;email&gt;group48@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group49&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000049&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000049&lt;/guid&gt;
+    &lt;full-name&gt;Group 49&lt;/full-name&gt;
+    &lt;email&gt;group49@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group50&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000050&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000050&lt;/guid&gt;
+    &lt;full-name&gt;Group 50&lt;/full-name&gt;
+    &lt;email&gt;group50@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group51&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000051&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000051&lt;/guid&gt;
+    &lt;full-name&gt;Group 51&lt;/full-name&gt;
+    &lt;email&gt;group51@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group52&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000052&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000052&lt;/guid&gt;
+    &lt;full-name&gt;Group 52&lt;/full-name&gt;
+    &lt;email&gt;group52@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group53&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000053&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000053&lt;/guid&gt;
+    &lt;full-name&gt;Group 53&lt;/full-name&gt;
+    &lt;email&gt;group53@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group54&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000054&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000054&lt;/guid&gt;
+    &lt;full-name&gt;Group 54&lt;/full-name&gt;
+    &lt;email&gt;group54@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group55&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000055&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000055&lt;/guid&gt;
+    &lt;full-name&gt;Group 55&lt;/full-name&gt;
+    &lt;email&gt;group55@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group56&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000056&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000056&lt;/guid&gt;
+    &lt;full-name&gt;Group 56&lt;/full-name&gt;
+    &lt;email&gt;group56@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group57&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000057&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000057&lt;/guid&gt;
+    &lt;full-name&gt;Group 57&lt;/full-name&gt;
+    &lt;email&gt;group57@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group58&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000058&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000058&lt;/guid&gt;
+    &lt;full-name&gt;Group 58&lt;/full-name&gt;
+    &lt;email&gt;group58@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group59&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000059&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000059&lt;/guid&gt;
+    &lt;full-name&gt;Group 59&lt;/full-name&gt;
+    &lt;email&gt;group59@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group60&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000060&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000060&lt;/guid&gt;
+    &lt;full-name&gt;Group 60&lt;/full-name&gt;
+    &lt;email&gt;group60@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group61&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000061&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000061&lt;/guid&gt;
+    &lt;full-name&gt;Group 61&lt;/full-name&gt;
+    &lt;email&gt;group61@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group62&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000062&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000062&lt;/guid&gt;
+    &lt;full-name&gt;Group 62&lt;/full-name&gt;
+    &lt;email&gt;group62@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group63&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000063&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000063&lt;/guid&gt;
+    &lt;full-name&gt;Group 63&lt;/full-name&gt;
+    &lt;email&gt;group63@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group64&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000064&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000064&lt;/guid&gt;
+    &lt;full-name&gt;Group 64&lt;/full-name&gt;
+    &lt;email&gt;group64@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group65&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000065&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000065&lt;/guid&gt;
+    &lt;full-name&gt;Group 65&lt;/full-name&gt;
+    &lt;email&gt;group65@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group66&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000066&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000066&lt;/guid&gt;
+    &lt;full-name&gt;Group 66&lt;/full-name&gt;
+    &lt;email&gt;group66@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group67&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000067&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000067&lt;/guid&gt;
+    &lt;full-name&gt;Group 67&lt;/full-name&gt;
+    &lt;email&gt;group67@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group68&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000068&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000068&lt;/guid&gt;
+    &lt;full-name&gt;Group 68&lt;/full-name&gt;
+    &lt;email&gt;group68@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group69&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000069&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000069&lt;/guid&gt;
+    &lt;full-name&gt;Group 69&lt;/full-name&gt;
+    &lt;email&gt;group69@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group70&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000070&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000070&lt;/guid&gt;
+    &lt;full-name&gt;Group 70&lt;/full-name&gt;
+    &lt;email&gt;group70@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group71&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000071&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000071&lt;/guid&gt;
+    &lt;full-name&gt;Group 71&lt;/full-name&gt;
+    &lt;email&gt;group71@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group72&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000072&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000072&lt;/guid&gt;
+    &lt;full-name&gt;Group 72&lt;/full-name&gt;
+    &lt;email&gt;group72@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group73&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000073&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000073&lt;/guid&gt;
+    &lt;full-name&gt;Group 73&lt;/full-name&gt;
+    &lt;email&gt;group73@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group74&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000074&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000074&lt;/guid&gt;
+    &lt;full-name&gt;Group 74&lt;/full-name&gt;
+    &lt;email&gt;group74@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group75&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000075&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000075&lt;/guid&gt;
+    &lt;full-name&gt;Group 75&lt;/full-name&gt;
+    &lt;email&gt;group75@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group76&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000076&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000076&lt;/guid&gt;
+    &lt;full-name&gt;Group 76&lt;/full-name&gt;
+    &lt;email&gt;group76@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group77&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000077&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000077&lt;/guid&gt;
+    &lt;full-name&gt;Group 77&lt;/full-name&gt;
+    &lt;email&gt;group77@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group78&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000078&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000078&lt;/guid&gt;
+    &lt;full-name&gt;Group 78&lt;/full-name&gt;
+    &lt;email&gt;group78@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group79&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000079&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000079&lt;/guid&gt;
+    &lt;full-name&gt;Group 79&lt;/full-name&gt;
+    &lt;email&gt;group79@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group80&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000080&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000080&lt;/guid&gt;
+    &lt;full-name&gt;Group 80&lt;/full-name&gt;
+    &lt;email&gt;group80@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group81&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000081&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000081&lt;/guid&gt;
+    &lt;full-name&gt;Group 81&lt;/full-name&gt;
+    &lt;email&gt;group81@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group82&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000082&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000082&lt;/guid&gt;
+    &lt;full-name&gt;Group 82&lt;/full-name&gt;
+    &lt;email&gt;group82@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group83&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000083&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000083&lt;/guid&gt;
+    &lt;full-name&gt;Group 83&lt;/full-name&gt;
+    &lt;email&gt;group83@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group84&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000084&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000084&lt;/guid&gt;
+    &lt;full-name&gt;Group 84&lt;/full-name&gt;
+    &lt;email&gt;group84@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group85&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000085&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000085&lt;/guid&gt;
+    &lt;full-name&gt;Group 85&lt;/full-name&gt;
+    &lt;email&gt;group85@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group86&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000086&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000086&lt;/guid&gt;
+    &lt;full-name&gt;Group 86&lt;/full-name&gt;
+    &lt;email&gt;group86@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group87&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000087&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000087&lt;/guid&gt;
+    &lt;full-name&gt;Group 87&lt;/full-name&gt;
+    &lt;email&gt;group87@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group88&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000088&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000088&lt;/guid&gt;
+    &lt;full-name&gt;Group 88&lt;/full-name&gt;
+    &lt;email&gt;group88@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group89&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000089&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000089&lt;/guid&gt;
+    &lt;full-name&gt;Group 89&lt;/full-name&gt;
+    &lt;email&gt;group89@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group90&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000090&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000090&lt;/guid&gt;
+    &lt;full-name&gt;Group 90&lt;/full-name&gt;
+    &lt;email&gt;group90@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group91&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000091&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000091&lt;/guid&gt;
+    &lt;full-name&gt;Group 91&lt;/full-name&gt;
+    &lt;email&gt;group91@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group92&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000092&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000092&lt;/guid&gt;
+    &lt;full-name&gt;Group 92&lt;/full-name&gt;
+    &lt;email&gt;group92@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group93&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000093&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000093&lt;/guid&gt;
+    &lt;full-name&gt;Group 93&lt;/full-name&gt;
+    &lt;email&gt;group93@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group94&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000094&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000094&lt;/guid&gt;
+    &lt;full-name&gt;Group 94&lt;/full-name&gt;
+    &lt;email&gt;group94@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group95&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000095&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000095&lt;/guid&gt;
+    &lt;full-name&gt;Group 95&lt;/full-name&gt;
+    &lt;email&gt;group95@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group96&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000096&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000096&lt;/guid&gt;
+    &lt;full-name&gt;Group 96&lt;/full-name&gt;
+    &lt;email&gt;group96@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group97&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000097&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000097&lt;/guid&gt;
+    &lt;full-name&gt;Group 97&lt;/full-name&gt;
+    &lt;email&gt;group97@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group98&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000098&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000098&lt;/guid&gt;
+    &lt;full-name&gt;Group 98&lt;/full-name&gt;
+    &lt;email&gt;group98@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group99&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000099&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000099&lt;/guid&gt;
+    &lt;full-name&gt;Group 99&lt;/full-name&gt;
+    &lt;email&gt;group99@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group100&lt;/short-name&gt;
+    &lt;uid&gt;20000000-0000-0000-0000-000000000100&lt;/uid&gt;
+    &lt;guid&gt;20000000-0000-0000-0000-000000000100&lt;/guid&gt;
+    &lt;full-name&gt;Group 100&lt;/full-name&gt;
+    &lt;email&gt;group100@example.com&lt;/email&gt;
+    
+&lt;/record&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confauthaugmentstestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/conf/auth/augments-test.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/auth/augments-test.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/auth/augments-test.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,185 +1,80 @@
</span><span class="cx"> &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
</span><del>-&lt;!DOCTYPE augments SYSTEM &quot;augments.dtd&quot;&gt;
</del><span class="cx"> 
</span><ins>+&lt;!--
+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.
+ --&gt;
+
</ins><span class="cx"> &lt;augments&gt;
</span><del>-  &lt;record&gt;
</del><ins>+&lt;record&gt;
</ins><span class="cx">     &lt;uid&gt;Default&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-  &lt;/record&gt;
-  &lt;record repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;location%02d&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;Default-Location&lt;/uid&gt;
</ins><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record repeat=&quot;4&quot;&gt;
-    &lt;uid&gt;resource%02d&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><ins>+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;Default-Resource&lt;/uid&gt;
</ins><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource05&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
</del><ins>+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000005&lt;/uid&gt;
</ins><span class="cx">     &lt;auto-schedule-mode&gt;none&lt;/auto-schedule-mode&gt;
</span><del>-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource06&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</del><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;accept-always&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource07&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;auto-schedule-mode&gt;accept-always&lt;/auto-schedule-mode&gt;
</ins><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;decline-always&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource08&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><del>-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;accept-if-free&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource09&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;decline-if-busy&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource10&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;resource11&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
</del><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000007&lt;/uid&gt;
</ins><span class="cx">     &lt;auto-schedule-mode&gt;decline-always&lt;/auto-schedule-mode&gt;
</span><del>-    &lt;auto-accept-group&gt;group01&lt;/auto-accept-group&gt;
-  &lt;/record&gt;
-  &lt;record repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;group%02d&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;disabledgroup&lt;/uid&gt;
-    &lt;enable&gt;false&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;delegatedroom&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;false&lt;/enable-addressbook&gt;
-    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;03DFF660-8BCC-4198-8588-DD77F776F518&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</del><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;80689D41-DAF8-4189-909C-DB017B271892&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;auto-schedule-mode&gt;accept-if-free&lt;/auto-schedule-mode&gt;
</ins><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;C38BEE7A-36EE-478C-9DCB-CBF4612AFE65&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;auto-schedule-mode&gt;decline-if-busy&lt;/auto-schedule-mode&gt;
</ins><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-    &lt;auto-accept-group&gt;group01&lt;/auto-accept-group&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
</ins><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;0CE0BF31-5F9E-4801-A489-8C70CF287F5F&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000011&lt;/uid&gt;
+    &lt;auto-schedule-mode&gt;decline-always&lt;/auto-schedule-mode&gt;
</ins><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><del>-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><del>-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;06E3BDCB-9C19-485A-B14E-F146A80ADDC6&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;4D66A20A-1437-437D-8069-2F14E8322234&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
-    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
-    &lt;enable-login&gt;true&lt;/enable-login&gt;
-    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
-    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
-  &lt;/record&gt;
</del><ins>+    &lt;auto-accept-group&gt;20000000-0000-0000-0000-000000000001&lt;/auto-accept-group&gt;
+&lt;/record&gt;
</ins><span class="cx"> &lt;/augments&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confauthgenerate_test_accountspyfromrev13157CalendarServerbranchesuserssagenmove2who4confauthgenerate_test_accountspy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/conf/auth/generate_test_accounts.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/conf/auth/generate_test_accounts.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/auth/generate_test_accounts.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/auth/generate_test_accounts.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,341 @@
</span><ins>+#!/usr/bin/env python
+
+# Generates test directory records in accounts-test.xml, resources-test.xml,
+# augments-test.xml and proxies-test.xml (overwriting them if they exist in
+# the current directory).
+
+prefix = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+
+&lt;!--
+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.
+ --&gt;
+
+&quot;&quot;&quot;
+
+# The uids and guids for CDT test accounts are the same
+# The short name is of the form userNN
+USERGUIDS = &quot;10000000-0000-0000-0000-000000000%03d&quot;
+GROUPGUIDS = &quot;20000000-0000-0000-0000-000000000%03d&quot;
+LOCATIONGUIDS = &quot;30000000-0000-0000-0000-000000000%03d&quot;
+RESOURCEGUIDS = &quot;40000000-0000-0000-0000-000000000%03d&quot;
+PUBLICGUIDS = &quot;50000000-0000-0000-0000-0000000000%02d&quot;
+
+# accounts-test.xml
+
+out = file(&quot;accounts-test.xml&quot;, &quot;w&quot;)
+out.write(prefix)
+out.write('&lt;directory realm=&quot;Test Realm&quot;&gt;\n')
+
+
+for uid, fullName, guid in (
+    (&quot;admin&quot;, &quot;Super User&quot;, &quot;0C8BDE62-E600-4696-83D3-8B5ECABDFD2E&quot;),
+    (&quot;apprentice&quot;, &quot;Apprentice Super User&quot;, &quot;29B6C503-11DF-43EC-8CCA-40C7003149CE&quot;),
+    (&quot;i18nuser&quot;, u&quot;\u307e\u3060&quot;.encode(&quot;utf-8&quot;), &quot;860B3EE9-6D7C-4296-9639-E6B998074A78&quot;),
+):
+    out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;uid&gt;{guid}&lt;/uid&gt;
+    &lt;guid&gt;{guid}&lt;/guid&gt;
+    &lt;short-name&gt;{uid}&lt;/short-name&gt;
+    &lt;password&gt;{uid}&lt;/password&gt;
+    &lt;full-name&gt;{fullName}&lt;/full-name&gt;
+    &lt;email&gt;{uid}@example.com&lt;/email&gt;
+&lt;/record&gt;
+&quot;&quot;&quot;.format(uid=uid, guid=guid, fullName=fullName))
+
+# user01-100
+for i in xrange(1, 101):
+    out.write(&quot;&quot;&quot;&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;user%02d&lt;/short-name&gt;
+    &lt;uid&gt;%s&lt;/uid&gt;
+    &lt;guid&gt;%s&lt;/guid&gt;
+    &lt;password&gt;user%02d&lt;/password&gt;
+    &lt;full-name&gt;User %02d&lt;/full-name&gt;
+    &lt;email&gt;user%02d@example.com&lt;/email&gt;
+&lt;/record&gt;
+&quot;&quot;&quot; % (i, USERGUIDS % i, USERGUIDS % i, i, i, i))
+
+# public01-10
+for i in xrange(1, 11):
+    out.write(&quot;&quot;&quot;&lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;public%02d&lt;/short-name&gt;
+    &lt;uid&gt;%s&lt;/uid&gt;
+    &lt;guid&gt;%s&lt;/guid&gt;
+    &lt;password&gt;public%02d&lt;/password&gt;
+    &lt;full-name&gt;Public %02d&lt;/full-name&gt;
+    &lt;email&gt;public%02d@example.com&lt;/email&gt;
+&lt;/record&gt;
+&quot;&quot;&quot; % (i, PUBLICGUIDS % i, PUBLICGUIDS % i, i, i, i))
+
+# group01-100
+members = {
+    GROUPGUIDS % 1: (USERGUIDS % 1,),
+    GROUPGUIDS % 2: (USERGUIDS % 6, USERGUIDS % 7),
+    GROUPGUIDS % 3: (USERGUIDS % 8, USERGUIDS % 9),
+    GROUPGUIDS % 4: (GROUPGUIDS % 2, GROUPGUIDS % 3, USERGUIDS % 10),
+    GROUPGUIDS % 5: (GROUPGUIDS % 6, USERGUIDS % 20),
+    GROUPGUIDS % 6: (USERGUIDS % 21,),
+    GROUPGUIDS % 7: (USERGUIDS % 22, USERGUIDS % 23, USERGUIDS % 24),
+}
+
+for i in xrange(1, 101):
+
+    memberElements = []
+    groupUID = GROUPGUIDS % i
+    if groupUID in members:
+        for uid in members[groupUID]:
+            memberElements.append(&quot;&lt;member-uid&gt;{}&lt;/member-uid&gt;&quot;.format(uid))
+        memberString = &quot;\n    &quot;.join(memberElements)
+    else:
+        memberString = &quot;&quot;
+
+    out.write(&quot;&quot;&quot;&lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;group%02d&lt;/short-name&gt;
+    &lt;uid&gt;%s&lt;/uid&gt;
+    &lt;guid&gt;%s&lt;/guid&gt;
+    &lt;full-name&gt;Group %02d&lt;/full-name&gt;
+    &lt;email&gt;group%02d@example.com&lt;/email&gt;
+    %s
+&lt;/record&gt;
+&quot;&quot;&quot; % (i, GROUPGUIDS % i, GROUPGUIDS % i, i, i, memberString))
+
+out.write(&quot;&lt;/directory&gt;\n&quot;)
+out.close()
+
+
+# resources-test.xml
+
+out = file(&quot;resources-test.xml&quot;, &quot;w&quot;)
+out.write(prefix)
+out.write('&lt;directory realm=&quot;Test Realm&quot;&gt;\n')
+
+out.write(&quot;&quot;&quot;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;pretend&lt;/short-name&gt;
+    &lt;uid&gt;pretend&lt;/uid&gt;
+    &lt;full-name&gt;Pretend Conference Room&lt;/full-name&gt;
+    &lt;associated-address&gt;il1&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il1&lt;/short-name&gt;
+    &lt;uid&gt;il1&lt;/uid&gt;
+    &lt;full-name&gt;IL1&lt;/full-name&gt;
+    &lt;street-address&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.331741,-122.030333&lt;/geographic-location&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;fantastic&lt;/short-name&gt;
+    &lt;uid&gt;fantastic&lt;/uid&gt;
+    &lt;full-name&gt;Fantastic Conference Room&lt;/full-name&gt;
+    &lt;associated-address&gt;il2&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il2&lt;/short-name&gt;
+    &lt;uid&gt;il2&lt;/uid&gt;
+    &lt;full-name&gt;IL2&lt;/full-name&gt;
+    &lt;street-address&gt;2 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.332633,-122.030502&lt;/geographic-location&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;delegatedroom&lt;/short-name&gt;
+    &lt;uid&gt;delegatedroom&lt;/uid&gt;
+    &lt;full-name&gt;Delegated Conference Room&lt;/full-name&gt;
+  &lt;/record&gt;
+
+&quot;&quot;&quot;)
+
+for i in xrange(1, 101):
+    out.write(&quot;&quot;&quot;&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location%02d&lt;/short-name&gt;
+    &lt;uid&gt;%s&lt;/uid&gt;
+    &lt;guid&gt;%s&lt;/guid&gt;
+    &lt;full-name&gt;Location %02d&lt;/full-name&gt;
+&lt;/record&gt;
+&quot;&quot;&quot; % (i, LOCATIONGUIDS % i, LOCATIONGUIDS % i, i))
+
+
+for i in xrange(1, 101):
+    out.write(&quot;&quot;&quot;&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource%02d&lt;/short-name&gt;
+    &lt;uid&gt;%s&lt;/uid&gt;
+    &lt;guid&gt;%s&lt;/guid&gt;
+    &lt;full-name&gt;Resource %02d&lt;/full-name&gt;
+&lt;/record&gt;
+&quot;&quot;&quot; % (i, RESOURCEGUIDS % i, RESOURCEGUIDS % i, i))
+
+out.write(&quot;&lt;/directory&gt;\n&quot;)
+out.close()
+
+
+# augments-test.xml
+
+out = file(&quot;augments-test.xml&quot;, &quot;w&quot;)
+out.write(prefix)
+out.write(&quot;&lt;augments&gt;\n&quot;)
+
+augments = (
+    # resource05
+    (RESOURCEGUIDS % 5, {
+        &quot;auto-schedule-mode&quot;: &quot;none&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource06
+    (RESOURCEGUIDS % 6, {
+        &quot;auto-schedule-mode&quot;: &quot;accept-always&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource07
+    (RESOURCEGUIDS % 7, {
+        &quot;auto-schedule-mode&quot;: &quot;decline-always&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource08
+    (RESOURCEGUIDS % 8, {
+        &quot;auto-schedule-mode&quot;: &quot;accept-if-free&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource09
+    (RESOURCEGUIDS % 9, {
+        &quot;auto-schedule-mode&quot;: &quot;decline-if-busy&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource10
+    (RESOURCEGUIDS % 10, {
+        &quot;auto-schedule-mode&quot;: &quot;automatic&quot;,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+    # resource11
+    (RESOURCEGUIDS % 11, {
+        &quot;auto-schedule-mode&quot;: &quot;decline-always&quot;,
+        &quot;auto-accept-group&quot;: GROUPGUIDS % 1,
+        &quot;enable-calendar&quot;: &quot;true&quot;,
+        &quot;enable-addressbook&quot;: &quot;true&quot;,
+    }),
+)
+
+out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;uid&gt;Default&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+&lt;/record&gt;
+&quot;&quot;&quot;)
+
+out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;uid&gt;Default-Location&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+&lt;/record&gt;
+&quot;&quot;&quot;)
+
+out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;uid&gt;Default-Resource&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+&lt;/record&gt;
+&quot;&quot;&quot;)
+
+for uid, settings in augments:
+    elements = []
+    for key, value in settings.iteritems():
+        elements.append(&quot;&lt;{key}&gt;{value}&lt;/{key}&gt;&quot;.format(key=key, value=value))
+    elementsString = &quot;\n    &quot;.join(elements)
+
+    out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;uid&gt;{uid}&lt;/uid&gt;
+    {elements}
+&lt;/record&gt;
+&quot;&quot;&quot;.format(uid=uid, elements=elementsString))
+
+out.write(&quot;&lt;/augments&gt;\n&quot;)
+out.close()
+
+
+# proxies-test.xml
+
+out = file(&quot;proxies-test.xml&quot;, &quot;w&quot;)
+out.write(prefix)
+out.write(&quot;&lt;proxies&gt;\n&quot;)
+
+proxies = (
+    (RESOURCEGUIDS % 1, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 2, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 3, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 4, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 5, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 6, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 7, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 8, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 9, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (RESOURCEGUIDS % 10, {
+        &quot;proxies&quot;: (USERGUIDS % 1,),
+        &quot;read-only-proxies&quot;: (USERGUIDS % 3,),
+    }),
+    (&quot;delegatedroom&quot;, {
+        &quot;proxies&quot;: (GROUPGUIDS % 5,),
+        &quot;read-only-proxies&quot;: (),
+    }),
+)
+
+for uid, settings in proxies:
+    elements = []
+    for key, values in settings.iteritems():
+        elements.append(&quot;&lt;{key}&gt;&quot;.format(key=key))
+        for value in values:
+            elements.append(&quot;&lt;member&gt;{value}&lt;/member&gt;&quot;.format(value=value))
+        elements.append(&quot;&lt;/{key}&gt;&quot;.format(key=key))
+    elementsString = &quot;\n    &quot;.join(elements)
+
+    out.write(&quot;&quot;&quot;&lt;record&gt;
+    &lt;guid&gt;{uid}&lt;/guid&gt;
+    {elements}
+&lt;/record&gt;
+&quot;&quot;&quot;.format(uid=uid, elements=elementsString))
+
+out.write(&quot;&lt;/proxies&gt;\n&quot;)
+out.close()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confauthproxiestestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/conf/auth/proxies-test.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/auth/proxies-test.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/auth/proxies-test.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,7 +1,7 @@
</span><span class="cx"> &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
</span><span class="cx"> 
</span><span class="cx"> &lt;!--
</span><del>-Copyright (c) 2009-2014 Apple Inc. All rights reserved.
</del><ins>+Copyright (c) 2006-2014 Apple Inc. All rights reserved.
</ins><span class="cx"> 
</span><span class="cx"> Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> you may not use this file except in compliance with the License.
</span><span class="lines">@@ -16,25 +16,103 @@
</span><span class="cx"> limitations under the License.
</span><span class="cx">  --&gt;
</span><span class="cx"> 
</span><del>-&lt;!DOCTYPE proxies SYSTEM &quot;proxies.dtd&quot;&gt;
-
</del><span class="cx"> &lt;proxies&gt;
</span><del>-  &lt;record repeat=&quot;10&quot;&gt;
-    &lt;guid&gt;resource%02d&lt;/guid&gt;
</del><ins>+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000001&lt;/guid&gt;
</ins><span class="cx">     &lt;proxies&gt;
</span><del>-      &lt;member&gt;user01&lt;/member&gt;
</del><ins>+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
</ins><span class="cx">     &lt;/proxies&gt;
</span><span class="cx">     &lt;read-only-proxies&gt;
</span><del>-      &lt;member&gt;user03&lt;/member&gt;
</del><ins>+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
</ins><span class="cx">     &lt;/read-only-proxies&gt;
</span><del>-  &lt;/record&gt;
-  &lt;record&gt;
</del><ins>+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000002&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000003&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000004&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000005&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000006&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000007&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000008&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000009&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000010&lt;/guid&gt;
+    &lt;proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000001&lt;/member&gt;
+    &lt;/proxies&gt;
+    &lt;read-only-proxies&gt;
+    &lt;member&gt;10000000-0000-0000-0000-000000000003&lt;/member&gt;
+    &lt;/read-only-proxies&gt;
+&lt;/record&gt;
+&lt;record&gt;
</ins><span class="cx">     &lt;guid&gt;delegatedroom&lt;/guid&gt;
</span><span class="cx">     &lt;proxies&gt;
</span><del>-      &lt;member&gt;group05&lt;/member&gt;
</del><ins>+    &lt;member&gt;20000000-0000-0000-0000-000000000005&lt;/member&gt;
</ins><span class="cx">     &lt;/proxies&gt;
</span><span class="cx">     &lt;read-only-proxies&gt;
</span><del>-      &lt;member&gt;group07&lt;/member&gt;
</del><span class="cx">     &lt;/read-only-proxies&gt;
</span><del>-  &lt;/record&gt;
</del><ins>+&lt;/record&gt;
</ins><span class="cx"> &lt;/proxies&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confauthresourcestestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/conf/auth/resources-test.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/auth/resources-test.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/auth/resources-test.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,135 +1,1253 @@
</span><del>-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;location repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;location%02d&lt;/uid&gt;
-    &lt;guid&gt;location%02d&lt;/guid&gt;
-    &lt;password&gt;location%02d&lt;/password&gt;
-    &lt;name&gt;Room %02d&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;resource repeat=&quot;20&quot;&gt;
-    &lt;uid&gt;resource%02d&lt;/uid&gt;
-    &lt;guid&gt;resource%02d&lt;/guid&gt;
-    &lt;password&gt;resource%02d&lt;/password&gt;
-    &lt;name&gt;Resource %02d&lt;/name&gt;
-  &lt;/resource&gt;
-  &lt;location&gt;
</del><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+
+&lt;!--
+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.
+ --&gt;
+
+&lt;directory realm=&quot;Test Realm&quot;&gt;
+
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;pretend&lt;/short-name&gt;
+    &lt;uid&gt;pretend&lt;/uid&gt;
+    &lt;full-name&gt;Pretend Conference Room&lt;/full-name&gt;
+    &lt;associated-address&gt;il1&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il1&lt;/short-name&gt;
+    &lt;uid&gt;il1&lt;/uid&gt;
+    &lt;full-name&gt;IL1&lt;/full-name&gt;
+    &lt;street-address&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.331741,-122.030333&lt;/geographic-location&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;fantastic&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;fantastic&lt;/uid&gt;
</span><del>-    &lt;guid&gt;4D66A20A-1437-437D-8069-2F14E8322234&lt;/guid&gt;
-    &lt;name&gt;Fantastic Conference Room&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;associatedAddress&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/associatedAddress&gt;
-    &lt;/extras&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;jupiter&lt;/uid&gt;
-    &lt;guid&gt;jupiter&lt;/guid&gt;
-    &lt;name&gt;Jupiter Conference Room, Building 2, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;uranus&lt;/uid&gt;
-    &lt;guid&gt;uranus&lt;/guid&gt;
-    &lt;name&gt;Uranus Conference Room, Building 3, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;morgensroom&lt;/uid&gt;
-    &lt;guid&gt;03DFF660-8BCC-4198-8588-DD77F776F518&lt;/guid&gt;
-    &lt;name&gt;Morgen's Room&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;mercury&lt;/uid&gt;
-    &lt;guid&gt;mercury&lt;/guid&gt;
-    &lt;name&gt;Mercury Conference Room, Building 1, 2nd Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
</del><ins>+    &lt;full-name&gt;Fantastic Conference Room&lt;/full-name&gt;
+    &lt;associated-address&gt;il2&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il2&lt;/short-name&gt;
+    &lt;uid&gt;il2&lt;/uid&gt;
+    &lt;full-name&gt;IL2&lt;/full-name&gt;
+    &lt;street-address&gt;2 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.332633,-122.030502&lt;/geographic-location&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;delegatedroom&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;delegatedroom&lt;/uid&gt;
</span><del>-    &lt;guid&gt;delegatedroom&lt;/guid&gt;
-    &lt;name&gt;Delegated Conference Room&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;mars&lt;/uid&gt;
-    &lt;guid&gt;redplanet&lt;/guid&gt;
-    &lt;name&gt;Mars Conference Room, Building 1, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;sharissroom&lt;/uid&gt;
-    &lt;guid&gt;80689D41-DAF8-4189-909C-DB017B271892&lt;/guid&gt;
-    &lt;name&gt;Shari's Room&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;associatedAddress&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/associatedAddress&gt;
-    &lt;/extras&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;pluto&lt;/uid&gt;
-    &lt;guid&gt;pluto&lt;/guid&gt;
-    &lt;name&gt;Pluto Conference Room, Building 2, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;saturn&lt;/uid&gt;
-    &lt;guid&gt;saturn&lt;/guid&gt;
-    &lt;name&gt;Saturn Conference Room, Building 2, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;pretend&lt;/uid&gt;
-    &lt;guid&gt;06E3BDCB-9C19-485A-B14E-F146A80ADDC6&lt;/guid&gt;
-    &lt;name&gt;Pretend Conference Room&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;associatedAddress&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/associatedAddress&gt;
-    &lt;/extras&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;neptune&lt;/uid&gt;
-    &lt;guid&gt;neptune&lt;/guid&gt;
-    &lt;name&gt;Neptune Conference Room, Building 2, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;Earth&lt;/uid&gt;
-    &lt;guid&gt;Earth&lt;/guid&gt;
-    &lt;name&gt;Earth Conference Room, Building 1, 1st Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;venus&lt;/uid&gt;
-    &lt;guid&gt;venus&lt;/guid&gt;
-    &lt;name&gt;Venus Conference Room, Building 1, 2nd Floor&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;sharisotherresource&lt;/uid&gt;
-    &lt;guid&gt;CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9&lt;/guid&gt;
-    &lt;name&gt;Shari's Other Resource&lt;/name&gt;
-  &lt;/resource&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;sharisresource&lt;/uid&gt;
-    &lt;guid&gt;C38BEE7A-36EE-478C-9DCB-CBF4612AFE65&lt;/guid&gt;
-    &lt;name&gt;Shari's Resource&lt;/name&gt;
-  &lt;/resource&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;sharisotherresource1&lt;/uid&gt;
-    &lt;guid&gt;0CE0BF31-5F9E-4801-A489-8C70CF287F5F&lt;/guid&gt;
-    &lt;name&gt;Shari's Other Resource1&lt;/name&gt;
-  &lt;/resource&gt;
-  &lt;address&gt;
-    &lt;uid&gt;testaddress1&lt;/uid&gt;
-    &lt;guid&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/guid&gt;
-    &lt;name&gt;Test Address One&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;streetAddress&gt;20300 Stevens Creek Blvd, Cupertino, CA 95014&lt;/streetAddress&gt;
-      &lt;geo&gt;37.322281,-122.028345&lt;/geo&gt;
-    &lt;/extras&gt;
-  &lt;/address&gt;
-  &lt;address&gt;
-    &lt;uid&gt;il2&lt;/uid&gt;
-    &lt;guid&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/guid&gt;
-    &lt;name&gt;IL2&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;streetAddress&gt;2 Infinite Loop, Cupertino, CA 95014&lt;/streetAddress&gt;
-      &lt;geo&gt;37.332633,-122.030502&lt;/geo&gt;
-    &lt;/extras&gt;
-  &lt;/address&gt;
-  &lt;address&gt;
-    &lt;uid&gt;il1&lt;/uid&gt;
-    &lt;guid&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/guid&gt;
-    &lt;name&gt;IL1&lt;/name&gt;
-    &lt;extras&gt;
-      &lt;streetAddress&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/streetAddress&gt;
-      &lt;geo&gt;37.331741,-122.030333&lt;/geo&gt;
-    &lt;/extras&gt;
-  &lt;/address&gt;
-&lt;/accounts&gt;
</del><ins>+    &lt;full-name&gt;Delegated Conference Room&lt;/full-name&gt;
+  &lt;/record&gt;
+
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location01&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000001&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000001&lt;/guid&gt;
+    &lt;full-name&gt;Location 01&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location02&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000002&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000002&lt;/guid&gt;
+    &lt;full-name&gt;Location 02&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location03&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000003&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000003&lt;/guid&gt;
+    &lt;full-name&gt;Location 03&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location04&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000004&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000004&lt;/guid&gt;
+    &lt;full-name&gt;Location 04&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location05&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000005&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000005&lt;/guid&gt;
+    &lt;full-name&gt;Location 05&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location06&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000006&lt;/guid&gt;
+    &lt;full-name&gt;Location 06&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location07&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000007&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000007&lt;/guid&gt;
+    &lt;full-name&gt;Location 07&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location08&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000008&lt;/guid&gt;
+    &lt;full-name&gt;Location 08&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location09&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000009&lt;/guid&gt;
+    &lt;full-name&gt;Location 09&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location10&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000010&lt;/guid&gt;
+    &lt;full-name&gt;Location 10&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location11&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000011&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000011&lt;/guid&gt;
+    &lt;full-name&gt;Location 11&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location12&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000012&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000012&lt;/guid&gt;
+    &lt;full-name&gt;Location 12&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location13&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000013&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000013&lt;/guid&gt;
+    &lt;full-name&gt;Location 13&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location14&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000014&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000014&lt;/guid&gt;
+    &lt;full-name&gt;Location 14&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location15&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000015&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000015&lt;/guid&gt;
+    &lt;full-name&gt;Location 15&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location16&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000016&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000016&lt;/guid&gt;
+    &lt;full-name&gt;Location 16&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location17&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000017&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000017&lt;/guid&gt;
+    &lt;full-name&gt;Location 17&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location18&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000018&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000018&lt;/guid&gt;
+    &lt;full-name&gt;Location 18&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location19&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000019&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000019&lt;/guid&gt;
+    &lt;full-name&gt;Location 19&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location20&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000020&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000020&lt;/guid&gt;
+    &lt;full-name&gt;Location 20&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location21&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000021&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000021&lt;/guid&gt;
+    &lt;full-name&gt;Location 21&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location22&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000022&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000022&lt;/guid&gt;
+    &lt;full-name&gt;Location 22&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location23&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000023&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000023&lt;/guid&gt;
+    &lt;full-name&gt;Location 23&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location24&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000024&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000024&lt;/guid&gt;
+    &lt;full-name&gt;Location 24&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location25&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000025&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000025&lt;/guid&gt;
+    &lt;full-name&gt;Location 25&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location26&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000026&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000026&lt;/guid&gt;
+    &lt;full-name&gt;Location 26&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location27&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000027&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000027&lt;/guid&gt;
+    &lt;full-name&gt;Location 27&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location28&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000028&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000028&lt;/guid&gt;
+    &lt;full-name&gt;Location 28&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location29&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000029&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000029&lt;/guid&gt;
+    &lt;full-name&gt;Location 29&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location30&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000030&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000030&lt;/guid&gt;
+    &lt;full-name&gt;Location 30&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location31&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000031&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000031&lt;/guid&gt;
+    &lt;full-name&gt;Location 31&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location32&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000032&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000032&lt;/guid&gt;
+    &lt;full-name&gt;Location 32&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location33&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000033&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000033&lt;/guid&gt;
+    &lt;full-name&gt;Location 33&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location34&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000034&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000034&lt;/guid&gt;
+    &lt;full-name&gt;Location 34&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location35&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000035&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000035&lt;/guid&gt;
+    &lt;full-name&gt;Location 35&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location36&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000036&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000036&lt;/guid&gt;
+    &lt;full-name&gt;Location 36&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location37&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000037&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000037&lt;/guid&gt;
+    &lt;full-name&gt;Location 37&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location38&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000038&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000038&lt;/guid&gt;
+    &lt;full-name&gt;Location 38&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location39&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000039&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000039&lt;/guid&gt;
+    &lt;full-name&gt;Location 39&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location40&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000040&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000040&lt;/guid&gt;
+    &lt;full-name&gt;Location 40&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location41&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000041&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000041&lt;/guid&gt;
+    &lt;full-name&gt;Location 41&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location42&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000042&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000042&lt;/guid&gt;
+    &lt;full-name&gt;Location 42&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location43&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000043&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000043&lt;/guid&gt;
+    &lt;full-name&gt;Location 43&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location44&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000044&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000044&lt;/guid&gt;
+    &lt;full-name&gt;Location 44&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location45&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000045&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000045&lt;/guid&gt;
+    &lt;full-name&gt;Location 45&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location46&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000046&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000046&lt;/guid&gt;
+    &lt;full-name&gt;Location 46&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location47&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000047&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000047&lt;/guid&gt;
+    &lt;full-name&gt;Location 47&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location48&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000048&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000048&lt;/guid&gt;
+    &lt;full-name&gt;Location 48&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location49&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000049&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000049&lt;/guid&gt;
+    &lt;full-name&gt;Location 49&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location50&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000050&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000050&lt;/guid&gt;
+    &lt;full-name&gt;Location 50&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location51&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000051&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000051&lt;/guid&gt;
+    &lt;full-name&gt;Location 51&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location52&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000052&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000052&lt;/guid&gt;
+    &lt;full-name&gt;Location 52&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location53&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000053&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000053&lt;/guid&gt;
+    &lt;full-name&gt;Location 53&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location54&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000054&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000054&lt;/guid&gt;
+    &lt;full-name&gt;Location 54&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location55&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000055&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000055&lt;/guid&gt;
+    &lt;full-name&gt;Location 55&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location56&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000056&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000056&lt;/guid&gt;
+    &lt;full-name&gt;Location 56&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location57&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000057&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000057&lt;/guid&gt;
+    &lt;full-name&gt;Location 57&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location58&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000058&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000058&lt;/guid&gt;
+    &lt;full-name&gt;Location 58&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location59&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000059&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000059&lt;/guid&gt;
+    &lt;full-name&gt;Location 59&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location60&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000060&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000060&lt;/guid&gt;
+    &lt;full-name&gt;Location 60&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location61&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000061&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000061&lt;/guid&gt;
+    &lt;full-name&gt;Location 61&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location62&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000062&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000062&lt;/guid&gt;
+    &lt;full-name&gt;Location 62&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location63&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000063&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000063&lt;/guid&gt;
+    &lt;full-name&gt;Location 63&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location64&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000064&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000064&lt;/guid&gt;
+    &lt;full-name&gt;Location 64&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location65&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000065&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000065&lt;/guid&gt;
+    &lt;full-name&gt;Location 65&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location66&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000066&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000066&lt;/guid&gt;
+    &lt;full-name&gt;Location 66&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location67&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000067&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000067&lt;/guid&gt;
+    &lt;full-name&gt;Location 67&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location68&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000068&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000068&lt;/guid&gt;
+    &lt;full-name&gt;Location 68&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location69&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000069&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000069&lt;/guid&gt;
+    &lt;full-name&gt;Location 69&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location70&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000070&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000070&lt;/guid&gt;
+    &lt;full-name&gt;Location 70&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location71&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000071&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000071&lt;/guid&gt;
+    &lt;full-name&gt;Location 71&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location72&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000072&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000072&lt;/guid&gt;
+    &lt;full-name&gt;Location 72&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location73&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000073&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000073&lt;/guid&gt;
+    &lt;full-name&gt;Location 73&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location74&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000074&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000074&lt;/guid&gt;
+    &lt;full-name&gt;Location 74&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location75&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000075&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000075&lt;/guid&gt;
+    &lt;full-name&gt;Location 75&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location76&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000076&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000076&lt;/guid&gt;
+    &lt;full-name&gt;Location 76&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location77&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000077&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000077&lt;/guid&gt;
+    &lt;full-name&gt;Location 77&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location78&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000078&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000078&lt;/guid&gt;
+    &lt;full-name&gt;Location 78&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location79&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000079&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000079&lt;/guid&gt;
+    &lt;full-name&gt;Location 79&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location80&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000080&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000080&lt;/guid&gt;
+    &lt;full-name&gt;Location 80&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location81&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000081&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000081&lt;/guid&gt;
+    &lt;full-name&gt;Location 81&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location82&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000082&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000082&lt;/guid&gt;
+    &lt;full-name&gt;Location 82&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location83&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000083&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000083&lt;/guid&gt;
+    &lt;full-name&gt;Location 83&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location84&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000084&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000084&lt;/guid&gt;
+    &lt;full-name&gt;Location 84&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location85&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000085&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000085&lt;/guid&gt;
+    &lt;full-name&gt;Location 85&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location86&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000086&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000086&lt;/guid&gt;
+    &lt;full-name&gt;Location 86&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location87&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000087&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000087&lt;/guid&gt;
+    &lt;full-name&gt;Location 87&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location88&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000088&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000088&lt;/guid&gt;
+    &lt;full-name&gt;Location 88&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location89&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000089&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000089&lt;/guid&gt;
+    &lt;full-name&gt;Location 89&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location90&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000090&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000090&lt;/guid&gt;
+    &lt;full-name&gt;Location 90&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location91&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000091&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000091&lt;/guid&gt;
+    &lt;full-name&gt;Location 91&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location92&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000092&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000092&lt;/guid&gt;
+    &lt;full-name&gt;Location 92&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location93&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000093&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000093&lt;/guid&gt;
+    &lt;full-name&gt;Location 93&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location94&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000094&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000094&lt;/guid&gt;
+    &lt;full-name&gt;Location 94&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location95&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000095&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000095&lt;/guid&gt;
+    &lt;full-name&gt;Location 95&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location96&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000096&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000096&lt;/guid&gt;
+    &lt;full-name&gt;Location 96&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location97&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000097&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000097&lt;/guid&gt;
+    &lt;full-name&gt;Location 97&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location98&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000098&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000098&lt;/guid&gt;
+    &lt;full-name&gt;Location 98&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location99&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000099&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000099&lt;/guid&gt;
+    &lt;full-name&gt;Location 99&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location100&lt;/short-name&gt;
+    &lt;uid&gt;30000000-0000-0000-0000-000000000100&lt;/uid&gt;
+    &lt;guid&gt;30000000-0000-0000-0000-000000000100&lt;/guid&gt;
+    &lt;full-name&gt;Location 100&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource01&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000001&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000001&lt;/guid&gt;
+    &lt;full-name&gt;Resource 01&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource02&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000002&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000002&lt;/guid&gt;
+    &lt;full-name&gt;Resource 02&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource03&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000003&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000003&lt;/guid&gt;
+    &lt;full-name&gt;Resource 03&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource04&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000004&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000004&lt;/guid&gt;
+    &lt;full-name&gt;Resource 04&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource05&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000005&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000005&lt;/guid&gt;
+    &lt;full-name&gt;Resource 05&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource06&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000006&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000006&lt;/guid&gt;
+    &lt;full-name&gt;Resource 06&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource07&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000007&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000007&lt;/guid&gt;
+    &lt;full-name&gt;Resource 07&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource08&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000008&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000008&lt;/guid&gt;
+    &lt;full-name&gt;Resource 08&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource09&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000009&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000009&lt;/guid&gt;
+    &lt;full-name&gt;Resource 09&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource10&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000010&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000010&lt;/guid&gt;
+    &lt;full-name&gt;Resource 10&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource11&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000011&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000011&lt;/guid&gt;
+    &lt;full-name&gt;Resource 11&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource12&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000012&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000012&lt;/guid&gt;
+    &lt;full-name&gt;Resource 12&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource13&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000013&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000013&lt;/guid&gt;
+    &lt;full-name&gt;Resource 13&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource14&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000014&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000014&lt;/guid&gt;
+    &lt;full-name&gt;Resource 14&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource15&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000015&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000015&lt;/guid&gt;
+    &lt;full-name&gt;Resource 15&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource16&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000016&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000016&lt;/guid&gt;
+    &lt;full-name&gt;Resource 16&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource17&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000017&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000017&lt;/guid&gt;
+    &lt;full-name&gt;Resource 17&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource18&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000018&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000018&lt;/guid&gt;
+    &lt;full-name&gt;Resource 18&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource19&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000019&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000019&lt;/guid&gt;
+    &lt;full-name&gt;Resource 19&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource20&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000020&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000020&lt;/guid&gt;
+    &lt;full-name&gt;Resource 20&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource21&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000021&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000021&lt;/guid&gt;
+    &lt;full-name&gt;Resource 21&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource22&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000022&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000022&lt;/guid&gt;
+    &lt;full-name&gt;Resource 22&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource23&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000023&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000023&lt;/guid&gt;
+    &lt;full-name&gt;Resource 23&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource24&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000024&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000024&lt;/guid&gt;
+    &lt;full-name&gt;Resource 24&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource25&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000025&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000025&lt;/guid&gt;
+    &lt;full-name&gt;Resource 25&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource26&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000026&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000026&lt;/guid&gt;
+    &lt;full-name&gt;Resource 26&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource27&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000027&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000027&lt;/guid&gt;
+    &lt;full-name&gt;Resource 27&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource28&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000028&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000028&lt;/guid&gt;
+    &lt;full-name&gt;Resource 28&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource29&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000029&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000029&lt;/guid&gt;
+    &lt;full-name&gt;Resource 29&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource30&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000030&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000030&lt;/guid&gt;
+    &lt;full-name&gt;Resource 30&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource31&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000031&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000031&lt;/guid&gt;
+    &lt;full-name&gt;Resource 31&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource32&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000032&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000032&lt;/guid&gt;
+    &lt;full-name&gt;Resource 32&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource33&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000033&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000033&lt;/guid&gt;
+    &lt;full-name&gt;Resource 33&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource34&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000034&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000034&lt;/guid&gt;
+    &lt;full-name&gt;Resource 34&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource35&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000035&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000035&lt;/guid&gt;
+    &lt;full-name&gt;Resource 35&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource36&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000036&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000036&lt;/guid&gt;
+    &lt;full-name&gt;Resource 36&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource37&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000037&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000037&lt;/guid&gt;
+    &lt;full-name&gt;Resource 37&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource38&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000038&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000038&lt;/guid&gt;
+    &lt;full-name&gt;Resource 38&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource39&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000039&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000039&lt;/guid&gt;
+    &lt;full-name&gt;Resource 39&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource40&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000040&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000040&lt;/guid&gt;
+    &lt;full-name&gt;Resource 40&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource41&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000041&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000041&lt;/guid&gt;
+    &lt;full-name&gt;Resource 41&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource42&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000042&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000042&lt;/guid&gt;
+    &lt;full-name&gt;Resource 42&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource43&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000043&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000043&lt;/guid&gt;
+    &lt;full-name&gt;Resource 43&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource44&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000044&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000044&lt;/guid&gt;
+    &lt;full-name&gt;Resource 44&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource45&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000045&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000045&lt;/guid&gt;
+    &lt;full-name&gt;Resource 45&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource46&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000046&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000046&lt;/guid&gt;
+    &lt;full-name&gt;Resource 46&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource47&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000047&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000047&lt;/guid&gt;
+    &lt;full-name&gt;Resource 47&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource48&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000048&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000048&lt;/guid&gt;
+    &lt;full-name&gt;Resource 48&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource49&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000049&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000049&lt;/guid&gt;
+    &lt;full-name&gt;Resource 49&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource50&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000050&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000050&lt;/guid&gt;
+    &lt;full-name&gt;Resource 50&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource51&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000051&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000051&lt;/guid&gt;
+    &lt;full-name&gt;Resource 51&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource52&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000052&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000052&lt;/guid&gt;
+    &lt;full-name&gt;Resource 52&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource53&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000053&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000053&lt;/guid&gt;
+    &lt;full-name&gt;Resource 53&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource54&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000054&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000054&lt;/guid&gt;
+    &lt;full-name&gt;Resource 54&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource55&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000055&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000055&lt;/guid&gt;
+    &lt;full-name&gt;Resource 55&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource56&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000056&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000056&lt;/guid&gt;
+    &lt;full-name&gt;Resource 56&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource57&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000057&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000057&lt;/guid&gt;
+    &lt;full-name&gt;Resource 57&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource58&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000058&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000058&lt;/guid&gt;
+    &lt;full-name&gt;Resource 58&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource59&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000059&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000059&lt;/guid&gt;
+    &lt;full-name&gt;Resource 59&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource60&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000060&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000060&lt;/guid&gt;
+    &lt;full-name&gt;Resource 60&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource61&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000061&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000061&lt;/guid&gt;
+    &lt;full-name&gt;Resource 61&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource62&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000062&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000062&lt;/guid&gt;
+    &lt;full-name&gt;Resource 62&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource63&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000063&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000063&lt;/guid&gt;
+    &lt;full-name&gt;Resource 63&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource64&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000064&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000064&lt;/guid&gt;
+    &lt;full-name&gt;Resource 64&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource65&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000065&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000065&lt;/guid&gt;
+    &lt;full-name&gt;Resource 65&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource66&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000066&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000066&lt;/guid&gt;
+    &lt;full-name&gt;Resource 66&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource67&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000067&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000067&lt;/guid&gt;
+    &lt;full-name&gt;Resource 67&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource68&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000068&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000068&lt;/guid&gt;
+    &lt;full-name&gt;Resource 68&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource69&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000069&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000069&lt;/guid&gt;
+    &lt;full-name&gt;Resource 69&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource70&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000070&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000070&lt;/guid&gt;
+    &lt;full-name&gt;Resource 70&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource71&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000071&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000071&lt;/guid&gt;
+    &lt;full-name&gt;Resource 71&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource72&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000072&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000072&lt;/guid&gt;
+    &lt;full-name&gt;Resource 72&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource73&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000073&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000073&lt;/guid&gt;
+    &lt;full-name&gt;Resource 73&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource74&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000074&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000074&lt;/guid&gt;
+    &lt;full-name&gt;Resource 74&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource75&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000075&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000075&lt;/guid&gt;
+    &lt;full-name&gt;Resource 75&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource76&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000076&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000076&lt;/guid&gt;
+    &lt;full-name&gt;Resource 76&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource77&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000077&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000077&lt;/guid&gt;
+    &lt;full-name&gt;Resource 77&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource78&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000078&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000078&lt;/guid&gt;
+    &lt;full-name&gt;Resource 78&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource79&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000079&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000079&lt;/guid&gt;
+    &lt;full-name&gt;Resource 79&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource80&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000080&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000080&lt;/guid&gt;
+    &lt;full-name&gt;Resource 80&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource81&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000081&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000081&lt;/guid&gt;
+    &lt;full-name&gt;Resource 81&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource82&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000082&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000082&lt;/guid&gt;
+    &lt;full-name&gt;Resource 82&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource83&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000083&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000083&lt;/guid&gt;
+    &lt;full-name&gt;Resource 83&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource84&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000084&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000084&lt;/guid&gt;
+    &lt;full-name&gt;Resource 84&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource85&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000085&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000085&lt;/guid&gt;
+    &lt;full-name&gt;Resource 85&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource86&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000086&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000086&lt;/guid&gt;
+    &lt;full-name&gt;Resource 86&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource87&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000087&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000087&lt;/guid&gt;
+    &lt;full-name&gt;Resource 87&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource88&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000088&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000088&lt;/guid&gt;
+    &lt;full-name&gt;Resource 88&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource89&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000089&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000089&lt;/guid&gt;
+    &lt;full-name&gt;Resource 89&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource90&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000090&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000090&lt;/guid&gt;
+    &lt;full-name&gt;Resource 90&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource91&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000091&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000091&lt;/guid&gt;
+    &lt;full-name&gt;Resource 91&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource92&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000092&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000092&lt;/guid&gt;
+    &lt;full-name&gt;Resource 92&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource93&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000093&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000093&lt;/guid&gt;
+    &lt;full-name&gt;Resource 93&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource94&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000094&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000094&lt;/guid&gt;
+    &lt;full-name&gt;Resource 94&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource95&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000095&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000095&lt;/guid&gt;
+    &lt;full-name&gt;Resource 95&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource96&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000096&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000096&lt;/guid&gt;
+    &lt;full-name&gt;Resource 96&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource97&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000097&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000097&lt;/guid&gt;
+    &lt;full-name&gt;Resource 97&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource98&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000098&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000098&lt;/guid&gt;
+    &lt;full-name&gt;Resource 98&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource99&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000099&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000099&lt;/guid&gt;
+    &lt;full-name&gt;Resource 99&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource100&lt;/short-name&gt;
+    &lt;uid&gt;40000000-0000-0000-0000-000000000100&lt;/uid&gt;
+    &lt;guid&gt;40000000-0000-0000-0000-000000000100&lt;/guid&gt;
+    &lt;full-name&gt;Resource 100&lt;/full-name&gt;
+&lt;/record&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5confcaldavdtestplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/conf/caldavd-test.plist (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/conf/caldavd-test.plist        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/conf/caldavd-test.plist        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -464,7 +464,7 @@
</span><span class="cx">     &lt;!-- Principals with &quot;DAV:all&quot; access (relative URLs) --&gt;
</span><span class="cx">     &lt;key&gt;AdminPrincipals&lt;/key&gt;
</span><span class="cx">     &lt;array&gt;
</span><del>-      &lt;string&gt;/principals/__uids__/admin/&lt;/string&gt;
</del><ins>+      &lt;string&gt;/principals/__uids__/0C8BDE62-E600-4696-83D3-8B5ECABDFD2E/&lt;/string&gt;
</ins><span class="cx">     &lt;/array&gt;
</span><span class="cx"> 
</span><span class="cx">     &lt;!-- Principals with &quot;DAV:read&quot; access (relative URLs) --&gt;
</span><span class="lines">@@ -577,7 +577,7 @@
</span><span class="cx"> 
</span><span class="cx">     &lt;!-- Log levels --&gt;
</span><span class="cx">     &lt;key&gt;DefaultLogLevel&lt;/key&gt;
</span><del>-    &lt;string&gt;info&lt;/string&gt; &lt;!-- debug, info, warn, error --&gt;
</del><ins>+    &lt;string&gt;debug&lt;/string&gt; &lt;!-- debug, info, warn, error --&gt;
</ins><span class="cx"> 
</span><span class="cx">     &lt;!-- Log level overrides for specific functionality --&gt;
</span><span class="cx">     &lt;key&gt;LogLevels&lt;/key&gt;
</span><span class="lines">@@ -1017,6 +1017,8 @@
</span><span class="cx">       &lt;string&gt;en&lt;/string&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><del>-
</del><ins>+    &lt;!-- Directory Address Book --&gt;
+    &lt;key&gt;EnableSearchAddressBook&lt;/key&gt;
+    &lt;true/&gt;
</ins><span class="cx">   &lt;/dict&gt;
</span><span class="cx"> &lt;/plist&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5contribperformanceloadtesttest_simpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/contrib/performance/loadtest/test_sim.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/contrib/performance/loadtest/test_sim.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/contrib/performance/loadtest/test_sim.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5requirementspy_developtxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/requirements/py_develop.txt (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/requirements/py_develop.txt        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/requirements/py_develop.txt        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -4,7 +4,9 @@
</span><span class="cx"> 
</span><span class="cx"> pyflakes
</span><span class="cx"> docutils&gt;=0.11
</span><ins>+mockldap&gt;=0.1.4
+q
</ins><span class="cx"> 
</span><span class="cx"> -e svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk#egg=CalDAVClientLibrary
</span><span class="cx"> 
</span><del>--e svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk#egg=CalDAVTester
</del><ins>+-e svn+http://svn.calendarserver.org/repository/calendarserver/CalendarServer/branches/users/sagen/move2who-cdt#egg=CalDAVTester
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavcachepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/cache.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/cache.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/cache.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -69,6 +69,7 @@
</span><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class DisabledCacheNotifier(object):
</span><span class="cx">     def __init__(self, *args, **kwargs):
</span><span class="cx">         pass
</span><span class="lines">@@ -164,6 +165,7 @@
</span><span class="cx">             raise URINotFoundException(uri)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _canonicalizeURIForRequest(self, uri, request):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Always use canonicalized forms of the URIs for caching (i.e. __uids__ paths).
</span><span class="lines">@@ -174,21 +176,24 @@
</span><span class="cx">         uribits = uri.split(&quot;/&quot;)
</span><span class="cx">         if len(uribits) &gt; 1 and uribits[1] in (&quot;principals&quot;, &quot;calendars&quot;, &quot;addressbooks&quot;):
</span><span class="cx">             if uribits[2] == &quot;__uids__&quot;:
</span><del>-                return succeed(uri)
</del><ins>+                returnValue(uri)
</ins><span class="cx">             else:
</span><span class="cx">                 recordType = uribits[2]
</span><span class="cx">                 recordName = uribits[3]
</span><span class="cx">                 directory = request.site.resource.getDirectory()
</span><del>-                record = directory.recordWithShortName(recordType, recordName)
</del><ins>+                record = yield directory.recordWithShortName(
+                    directory.oldNameToRecordType(recordType),
+                    recordName
+                )
</ins><span class="cx">                 if record is not None:
</span><span class="cx">                     uribits[2] = &quot;__uids__&quot;
</span><del>-                    uribits[3] = record.uid
-                    return succeed(&quot;/&quot;.join(uribits))
</del><ins>+                    uribits[3] = record.uid.encode(&quot;utf-8&quot;)
+                    returnValue(&quot;/&quot;.join(uribits))
</ins><span class="cx"> 
</span><span class="cx">         # Fall back to the locateResource approach
</span><span class="cx">         try:
</span><del>-            return request.locateResource(uri).addCallback(
-                lambda resrc: resrc.url()).addErrback(self._uriNotFound, uri)
</del><ins>+            resrc = yield request.locateResource(uri)
+            returnValue(resrc.url())
</ins><span class="cx">         except AssertionError:
</span><span class="cx">             raise URINotFoundException(uri)
</span><span class="cx"> 
</span><span class="lines">@@ -252,7 +257,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the current token for a particular URI.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-
</del><ins>+        if isinstance(uri, unicode):
+            uri = uri.encode(&quot;utf-8&quot;)
</ins><span class="cx">         if cachePoolHandle:
</span><span class="cx">             result = (yield defaultCachePool(cachePoolHandle).get('cacheToken:%s' % (uri,)))
</span><span class="cx">         else:
</span><span class="lines">@@ -436,8 +442,9 @@
</span><span class="cx">                     cTokens,
</span><span class="cx">                 )
</span><span class="cx">             )
</span><del>-            yield self.getCachePool().set(key, cacheEntry,
-                expireTime=config.ResponseCacheTimeout * 60)
</del><ins>+            yield self.getCachePool().set(
+                key, cacheEntry, expireTime=config.ResponseCacheTimeout * 60
+            )
</ins><span class="cx"> 
</span><span class="cx">         except URINotFoundException, e:
</span><span class="cx">             self.log.debug(&quot;Could not locate URI: {e!r}&quot;, e=e)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavcustomxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/customxml.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/customxml.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/customxml.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1456,6 +1456,8 @@
</span><span class="cx"> 
</span><span class="cx"> ResourceType.calendarproxyread = ResourceType(Principal(), Collection(), CalendarProxyRead())
</span><span class="cx"> ResourceType.calendarproxywrite = ResourceType(Principal(), Collection(), CalendarProxyWrite())
</span><ins>+ResourceType.calendarproxyreadfor = ResourceType(Principal(), Collection(), CalendarProxyReadFor())
+ResourceType.calendarproxywritefor = ResourceType(Principal(), Collection(), CalendarProxyWriteFor())
</ins><span class="cx"> 
</span><span class="cx"> ResourceType.timezones = ResourceType(Timezones())
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/addressbook.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/addressbook.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/addressbook.py        2014-04-04 17:20:27 UTC (rev 13158)
</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,14 +57,14 @@
</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="cx">     DAVResource,
</span><span class="cx"> ):
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return config.ProvisioningResourceACL
</del><ins>+        return succeed(config.ProvisioningResourceACL)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def etag(self):
</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">@@ -93,19 +92,39 @@
</span><span class="cx"> 
</span><span class="cx">         super(DirectoryAddressBookHomeProvisioningResource, self).__init__()
</span><span class="cx"> 
</span><del>-        self.directory = IDirectoryService(directory)
</del><ins>+        # MOVE2WHO
+        self.directory = directory  # IDirectoryService(directory)
</ins><span class="cx">         self._url = url
</span><span class="cx">         self._newStore = store
</span><span class="cx"> 
</span><span class="cx">         # FIXME: Smells like a hack
</span><span class="cx">         directory.addressBookHomesCollection = self
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        for recordType in self.directory.recordTypes():
-            self.putChild(recordType, DirectoryAddressBookHomeTypeProvisioningResource(self, recordType))
</del><ins>+        # ...just users, locations, and resources though.  If we iterate all of
+        # the directory's recordTypes, we also get the proxy sub principal types
+        # and other things which don't have addressbooks.
</ins><span class="cx"> 
</span><ins>+        self.supportedChildTypes = (
+            self.directory.recordType.user,
+            self.directory.recordType.location,
+            self.directory.recordType.resource,
+        )
+
+        for recordType, recordTypeName in [
+            (r, self.directory.recordTypeToOldName(r)) for r in
+            self.supportedChildTypes
+        ]:
+            self.putChild(
+                recordTypeName,
+                DirectoryAddressBookHomeTypeProvisioningResource(
+                    self, recordTypeName, recordType
+                )
+            )
+
</ins><span class="cx">         self.putChild(uidsResourceName, DirectoryAddressBookHomeUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -114,7 +133,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><del>-        return self.directory.recordTypes()
</del><ins>+        return [
+            self.directory.recordTypeToOldName(r) for r in
+            self.supportedChildTypes
+        ]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalCollections(self):
</span><span class="lines">@@ -129,12 +151,13 @@
</span><span class="cx">         return self.directory.principalCollection.principalForRecord(record)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def homeForDirectoryRecord(self, record, request):
</span><del>-        uidResource = self.getChild(uidsResourceName)
</del><ins>+        uidResource = yield self.getChild(uidsResourceName)
</ins><span class="cx">         if uidResource is None:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx">         else:
</span><del>-            return uidResource.homeResourceForRecord(record, request)
</del><ins>+            returnValue((yield uidResource.homeResourceForRecord(record, request)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -151,42 +174,46 @@
</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="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, parent, recordType):
</del><ins>+    def __init__(self, parent, name, recordType):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param parent: the parent of this resource
</span><span class="cx">         @param recordType: the directory record type to provision.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         assert parent is not None
</span><ins>+        assert name is not None
</ins><span class="cx">         assert recordType is not None
</span><span class="cx"> 
</span><span class="cx">         super(DirectoryAddressBookHomeTypeProvisioningResource, self).__init__()
</span><span class="cx"> 
</span><span class="cx">         self.directory = parent.directory
</span><ins>+        self.name = name
</ins><span class="cx">         self.recordType = recordType
</span><span class="cx">         self._parent = parent
</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.name)
</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.recordsWithRecordType(self.recordType)
+            ):
+                if getattr(record, &quot;hasContacts&quot;, False):
+                    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">@@ -205,7 +232,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def displayName(self):
</span><del>-        return self.recordType
</del><ins>+        return self.directory.recordTypeToOldName(self.recordType)
</ins><span class="cx"> 
</span><span class="cx">     ##
</span><span class="cx">     # ACL
</span><span class="lines">@@ -222,13 +249,13 @@
</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><del>-    enabledAttribute = 'enabledForAddressBooks'
</del><ins>+    enabledAttribute = 'hasContacts'
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def homeResourceCreator(self, record, transaction):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaggregatepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/aggregate.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/aggregate.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/aggregate.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryappleopendirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/appleopendirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/appleopendirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/appleopendirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryaugmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/augment.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/augment.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/augment.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -46,6 +46,7 @@
</span><span class="cx">     &quot;automatic&quot;,
</span><span class="cx"> ))
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class AugmentRecord(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Augmented directory record information
</span><span class="lines">@@ -59,7 +60,7 @@
</span><span class="cx">         enabledForCalendaring=False,
</span><span class="cx">         autoSchedule=False,
</span><span class="cx">         autoScheduleMode=&quot;default&quot;,
</span><del>-        autoAcceptGroup=&quot;&quot;,
</del><ins>+        autoAcceptGroup=None,
</ins><span class="cx">         enabledForAddressBooks=False,
</span><span class="cx">         enabledForLogin=True,
</span><span class="cx">     ):
</span><span class="lines">@@ -75,13 +76,15 @@
</span><span class="cx">         self.clonedFromDefault = False
</span><span class="cx"> 
</span><span class="cx"> recordTypesMap = {
</span><del>-    &quot;users&quot; : &quot;User&quot;,
-    &quot;groups&quot; : &quot;Group&quot;,
-    &quot;locations&quot; : &quot;Location&quot;,
-    &quot;resources&quot; : &quot;Resource&quot;,
-    &quot;addresses&quot; : &quot;Address&quot;,
</del><ins>+    &quot;users&quot;: &quot;User&quot;,
+    &quot;groups&quot;: &quot;Group&quot;,
+    &quot;locations&quot;: &quot;Location&quot;,
+    &quot;resources&quot;: &quot;Resource&quot;,
+    &quot;addresses&quot;: &quot;Address&quot;,
+    &quot;wikis&quot;: &quot;Wiki&quot;,
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class AugmentDB(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Abstract base class for an augment record database.
</span><span class="lines">@@ -128,7 +131,6 @@
</span><span class="cx"> 
</span><span class="cx">         @return: L{Deferred}
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-
</del><span class="cx">         recordType = recordTypesMap[recordType]
</span><span class="cx"> 
</span><span class="cx">         result = (yield self._lookupAugmentRecord(uid))
</span><span class="lines">@@ -266,9 +268,9 @@
</span><span class="cx">         self.xmlFiles = [fullServerPath(config.DataRoot, path) for path in xmlFiles]
</span><span class="cx">         self.xmlFileStats = {}
</span><span class="cx">         for path in self.xmlFiles:
</span><del>-            self.xmlFileStats[path] = (0, 0) # mtime, size
</del><ins>+            self.xmlFileStats[path] = (0, 0)  # mtime, size
</ins><span class="cx"> 
</span><del>-        self.statSeconds = statSeconds # Don't stat more often than this value
</del><ins>+        self.statSeconds = statSeconds  # Don't stat more often than this value
</ins><span class="cx">         self.lastCached = 0
</span><span class="cx">         self.db = {}
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycachingdirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/cachingdirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/cachingdirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/cachingdirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendar.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendar.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendar.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -35,11 +35,10 @@
</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><del>-from twistedcaldav.directory.wiki import getWikiACL
</del><ins>+from txdav.who.wiki import getWikiACL
</ins><span class="cx"> from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource, \
</span><span class="cx">     DAVResourceWithChildrenMixin
</span><span class="cx"> from twistedcaldav.resource import CalendarHomeResource
</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">@@ -65,7 +67,7 @@
</span><span class="cx">     DAVResource,
</span><span class="cx"> ):
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return config.ProvisioningResourceACL
</del><ins>+        return succeed(config.ProvisioningResourceACL)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def etag(self):
</span><span class="lines">@@ -91,7 +93,8 @@
</span><span class="cx"> 
</span><span class="cx">         super(DirectoryCalendarHomeProvisioningResource, self).__init__()
</span><span class="cx"> 
</span><del>-        self.directory = IDirectoryService(directory)
</del><ins>+        # MOVE2WHO
+        self.directory = directory  # IDirectoryService(directory)
</ins><span class="cx">         self._url = url
</span><span class="cx">         self._newStore = store
</span><span class="cx"> 
</span><span class="lines">@@ -101,9 +104,27 @@
</span><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        for recordType in self.directory.recordTypes():
-            self.putChild(recordType, DirectoryCalendarHomeTypeProvisioningResource(self, recordType))
</del><ins>+        # ...just users, locations, and resources though.  If we iterate all of
+        # the directory's recordTypes, we also get the proxy sub principal types
+        # and other things which don't have calendars.
</ins><span class="cx"> 
</span><ins>+        self.supportedChildTypes = (
+            self.directory.recordType.user,
+            self.directory.recordType.location,
+            self.directory.recordType.resource,
+        )
+
+        for recordType, recordTypeName in [
+            (r, self.directory.recordTypeToOldName(r)) for r in
+            self.supportedChildTypes
+        ]:
+            self.putChild(
+                recordTypeName,
+                DirectoryCalendarHomeTypeProvisioningResource(
+                    self, recordTypeName, recordType
+                )
+            )
+
</ins><span class="cx">         self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -112,7 +133,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><del>-        return self.directory.recordTypes()
</del><ins>+        return [
+            self.directory.recordTypeToOldName(r) for r in
+            self.supportedChildTypes
+        ]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalCollections(self):
</span><span class="lines">@@ -127,12 +151,13 @@
</span><span class="cx">         return self.directory.principalCollection.principalForRecord(record)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def homeForDirectoryRecord(self, record, request):
</span><del>-        uidResource = self.getChild(uidsResourceName)
</del><ins>+        uidResource = yield self.getChild(uidsResourceName)
</ins><span class="cx">         if uidResource is None:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx">         else:
</span><del>-            return uidResource.homeResourceForRecord(record, request)
</del><ins>+            returnValue((yield uidResource.homeResourceForRecord(record, request)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -149,42 +174,43 @@
</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="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, parent, recordType):
</del><ins>+    def __init__(self, parent, name, recordType):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param parent: the parent of this resource
</span><span class="cx">         @param recordType: the directory record type to provision.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         assert parent is not None
</span><ins>+        assert name is not None
</ins><span class="cx">         assert recordType is not None
</span><span class="cx"> 
</span><span class="cx">         super(DirectoryCalendarHomeTypeProvisioningResource, self).__init__()
</span><span class="cx"> 
</span><span class="cx">         self.directory = parent.directory
</span><ins>+        self.name = name
</ins><span class="cx">         self.recordType = recordType
</span><span class="cx">         self._parent = parent
</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.name)
</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><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.recordsWithRecordType(self.recordType)):
+                if record.hasCalendars:
+                    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">@@ -203,7 +229,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def displayName(self):
</span><del>-        return self.recordType
</del><ins>+        return self.name
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -220,13 +246,13 @@
</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><del>-    enabledAttribute = 'enabledForCalendaring'
</del><ins>+    enabledAttribute = 'hasCalendars'
</ins><span class="cx"> 
</span><span class="cx">     def homeResourceCreator(self, record, transaction):
</span><span class="cx">         return DirectoryCalendarHomeResource.createHomeResource(
</span><span class="lines">@@ -258,7 +284,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 # ...otherwise permissions are fixed, and are not subject to
</span><span class="cx">                 # inheritance rules, etc.
</span><del>-                return succeed(self.defaultAccessControlList())
</del><ins>+                return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx">         d = getWikiACL(self, request)
</span><span class="cx">         d.addCallback(gotACL)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendaruserproxypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxy.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxy.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxy.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -29,37 +29,41 @@
</span><span class="cx"> 
</span><span class="cx"> import itertools
</span><span class="cx"> import time
</span><ins>+import uuid
</ins><span class="cx"> 
</span><ins>+from twext.python.log import Logger
+from twext.who.idirectory import RecordType as BaseRecordType
</ins><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, returnValue
</span><del>-from txweb2 import responsecode
-from txweb2.http import HTTPError, StatusResponse
-from txdav.xml import element as davxml
-from txdav.xml.base import dav_namespace
-from txweb2.dav.util import joinURL
-from txweb2.dav.noneprops import NonePropertyStore
</del><ins>+from twisted.python.modules import getModule
+from twisted.web.template import XMLFile, Element, renderer
+from twistedcaldav.config import config, fullServerPath
+from twistedcaldav.database import (
+    AbstractADBAPIDatabase, ADBAPISqliteMixin, ADBAPIPostgreSQLMixin
+)
+from twistedcaldav.directory.util import normalizeUUID
+from twistedcaldav.directory.util import (
+    formatLink, formatLinks, formatPrincipals
+)
</ins><span class="cx"> 
</span><del>-from twext.python.log import Logger
-
-from twisted.web.template import XMLFile, Element, renderer
-from twisted.python.modules import getModule
</del><ins>+from twistedcaldav.extensions import (
+    DAVPrincipalResource, DAVResourceWithChildrenMixin
+)
</ins><span class="cx"> from twistedcaldav.extensions import DirectoryElement
</span><del>-from twistedcaldav.directory.principal import formatLink
-from twistedcaldav.directory.principal import formatLinks
-from twistedcaldav.directory.principal import formatPrincipals
-
-from twistedcaldav.directory.util import normalizeUUID
-from twistedcaldav.config import config, fullServerPath
-from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin, \
-    ADBAPIPostgreSQLMixin
-from twistedcaldav.extensions import DAVPrincipalResource, \
-    DAVResourceWithChildrenMixin
</del><span class="cx"> from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> from twistedcaldav.resource import CalDAVComplianceMixIn
</span><ins>+from txdav.who.delegates import RecordType as DelegateRecordType
+from txdav.xml import element as davxml
+from txdav.xml.base import dav_namespace
+from txweb2 import responsecode
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.util import joinURL
+from txweb2.http import HTTPError, StatusResponse
</ins><span class="cx"> 
</span><span class="cx"> thisModule = getModule(__name__)
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
</span><span class="cx">     def defaultAccessControlList(self):
</span><span class="cx">         aces = (
</span><span class="lines">@@ -86,13 +90,13 @@
</span><span class="cx">             for principal in config.AdminPrincipals
</span><span class="cx">         ))
</span><span class="cx"> 
</span><del>-        return davxml.ACL(*aces)
</del><ins>+        return succeed(davxml.ACL(*aces))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def accessControlList(self, request, inheritance=True, expanding=False,
</span><span class="cx">                           inherited_aces=None):
</span><span class="cx">         # Permissions here are fixed, and are not subject to inheritance rules, etc.
</span><del>-        return succeed(self.defaultAccessControlList())
</del><ins>+        return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -119,13 +123,20 @@
</span><span class="cx">         record = self.resource.parent.record
</span><span class="cx">         resource = self.resource
</span><span class="cx">         parent = self.resource.parent
</span><ins>+        try:
+            if isinstance(record.guid, uuid.UUID):
+                guid = str(record.guid).upper()
+            else:
+                guid = record.guid
+        except AttributeError:
+            guid = &quot;&quot;
</ins><span class="cx">         return tag.fillSlots(
</span><span class="cx">             directoryGUID=record.service.guid,
</span><span class="cx">             realm=record.service.realmName,
</span><del>-            guid=record.guid,
-            recordType=record.recordType,
</del><ins>+            guid=guid,
+            recordType=record.service.recordTypeToOldName(record.recordType),
</ins><span class="cx">             shortNames=record.shortNames,
</span><del>-            fullName=record.fullName,
</del><ins>+            fullName=record.displayName,
</ins><span class="cx">             principalUID=parent.principalUID(),
</span><span class="cx">             principalURL=formatLink(parent.principalURL()),
</span><span class="cx">             proxyPrincipalUID=resource.principalUID(),
</span><span class="lines">@@ -209,9 +220,13 @@
</span><span class="cx"> 
</span><span class="cx">     def resourceType(self):
</span><span class="cx">         if self.proxyType == &quot;calendar-proxy-read&quot;:
</span><del>-            return davxml.ResourceType.calendarproxyread #@UndefinedVariable
</del><ins>+            return davxml.ResourceType.calendarproxyread  # @UndefinedVariable
</ins><span class="cx">         elif self.proxyType == &quot;calendar-proxy-write&quot;:
</span><del>-            return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
</del><ins>+            return davxml.ResourceType.calendarproxywrite  # @UndefinedVariable
+        elif self.proxyType == &quot;calendar-proxy-read-for&quot;:
+            return davxml.ResourceType.calendarproxyreadfor  # @UndefinedVariable
+        elif self.proxyType == &quot;calendar-proxy-write-for&quot;:
+            return davxml.ResourceType.calendarproxywritefor  # @UndefinedVariable
</ins><span class="cx">         else:
</span><span class="cx">             return super(CalendarUserProxyPrincipalResource, self).resourceType()
</span><span class="cx"> 
</span><span class="lines">@@ -270,7 +285,7 @@
</span><span class="cx">         principals = []
</span><span class="cx">         newUIDs = set()
</span><span class="cx">         for uri in members:
</span><del>-            principal = self.pcollection._principalForURI(uri)
</del><ins>+            principal = yield self.pcollection._principalForURI(uri)
</ins><span class="cx">             # Invalid principals MUST result in an error.
</span><span class="cx">             if principal is None or principal.principalURL() != uri:
</span><span class="cx">                 raise HTTPError(StatusResponse(
</span><span class="lines">@@ -282,7 +297,9 @@
</span><span class="cx">             newUIDs.add(principal.principalUID())
</span><span class="cx"> 
</span><span class="cx">         # Get the old set of UIDs
</span><del>-        oldUIDs = (yield self._index().getMembers(self.uid))
</del><ins>+        # oldUIDs = (yield self._index().getMembers(self.uid))
+        oldPrincipals = yield self.groupMembers()
+        oldUIDs = [p.principalUID() for p in oldPrincipals]
</ins><span class="cx"> 
</span><span class="cx">         # Change membership
</span><span class="cx">         yield self.setGroupMemberSetPrincipals(principals)
</span><span class="lines">@@ -293,19 +310,24 @@
</span><span class="cx"> 
</span><span class="cx">         changedUIDs = newUIDs.symmetric_difference(oldUIDs)
</span><span class="cx">         for uid in changedUIDs:
</span><del>-            principal = self.pcollection.principalForUID(uid)
</del><ins>+            principal = yield self.pcollection.principalForUID(uid)
</ins><span class="cx">             if principal:
</span><span class="cx">                 yield principal.cacheNotifier.changed()
</span><span class="cx"> 
</span><span class="cx">         returnValue(True)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def setGroupMemberSetPrincipals(self, principals):
</span><del>-        # Map the principals to UIDs.
-        return self._index().setGroupMembers(
-            self.uid,
-            [p.principalUID() for p in principals],
</del><ins>+
+        # Find our pseudo-record
+        record = yield self.parent.record.service.recordWithShortName(
+            self._recordTypeFromProxyType(),
+            self.parent.principalUID()
</ins><span class="cx">         )
</span><ins>+        # Set the members
+        memberRecords = [p.record for p in principals]
+        yield record.setMembers(memberRecords)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -349,7 +371,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _expandMemberUIDs(self, uid=None, relatives=None, uids=None, infinity=False):
</del><ins>+    def _expandMemberPrincipals(self, uid=None, relatives=None, uids=None, infinity=False):
</ins><span class="cx">         if uid is None:
</span><span class="cx">             uid = self.principalUID()
</span><span class="cx">         if relatives is None:
</span><span class="lines">@@ -360,14 +382,14 @@
</span><span class="cx">         if uid not in uids:
</span><span class="cx">             from twistedcaldav.directory.principal import DirectoryPrincipalResource
</span><span class="cx">             uids.add(uid)
</span><del>-            principal = self.pcollection.principalForUID(uid)
</del><ins>+            principal = yield self.pcollection.principalForUID(uid)
</ins><span class="cx">             if isinstance(principal, CalendarUserProxyPrincipalResource):
</span><span class="cx">                 members = yield self._directGroupMembers()
</span><span class="cx">                 for member in members:
</span><span class="cx">                     if member.principalUID() not in uids:
</span><span class="cx">                         relatives.add(member)
</span><span class="cx">                         if infinity:
</span><del>-                            yield self._expandMemberUIDs(member.principalUID(), relatives, uids, infinity=infinity)
</del><ins>+                            yield self._expandMemberPrincipals(member.principalUID(), relatives, uids, infinity=infinity)
</ins><span class="cx">             elif isinstance(principal, DirectoryPrincipalResource):
</span><span class="cx">                 if infinity:
</span><span class="cx">                     members = yield principal.expandedGroupMembers()
</span><span class="lines">@@ -378,30 +400,45 @@
</span><span class="cx">         returnValue(relatives)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _recordTypeFromProxyType(self):
+        return {
+            &quot;calendar-proxy-read&quot;: DelegateRecordType.readDelegateGroup,
+            &quot;calendar-proxy-write&quot;: DelegateRecordType.writeDelegateGroup,
+            &quot;calendar-proxy-read-for&quot;: DelegateRecordType.readDelegatorGroup,
+            &quot;calendar-proxy-write-for&quot;: DelegateRecordType.writeDelegatorGroup,
+        }.get(self.proxyType)
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _directGroupMembers(self):
</span><del>-        # Get member UIDs from database and map to principal resources
-        members = yield self._index().getMembers(self.uid)
-        found = []
-        for uid in members:
-            p = self.pcollection.principalForUID(uid)
-            if p:
-                # Only principals enabledForLogin can be a delegate
-                # (and groups as well)
-                if (p.record.enabledForLogin or
-                    p.record.recordType == p.record.service.recordType_groups):
-                    found.append(p)
-                # Make sure any outstanding deletion timer entries for
-                # existing principals are removed
-                yield self._index().refreshPrincipal(uid)
-            else:
-                self.log.warn(&quot;Delegate is missing from directory: %s&quot; % (uid,))
</del><ins>+        &quot;&quot;&quot;
+        Fault in the record representing the sub principal for this proxy type
+        (either read-only or read-write), then fault in the direct members of
+        that record.
+        &quot;&quot;&quot;
+        memberPrincipals = []
+        record = yield self.parent.record.service.recordWithShortName(
+            self._recordTypeFromProxyType(),
+            self.parent.principalUID()
+        )
+        if record is not None:
+            memberRecords = yield record.members()
+            for record in memberRecords:
+                if record is not None:
+                    principal = yield self.pcollection.principalForRecord(
+                        record
+                    )
+                    if principal is not None:
+                        if (
+                            principal.record.loginAllowed or
+                            principal.record.recordType is BaseRecordType.group
+                        ):
+                            memberPrincipals.append(principal)
+        returnValue(memberPrincipals)
</ins><span class="cx"> 
</span><del>-        returnValue(found)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def groupMembers(self):
</span><del>-        return self._expandMemberUIDs()
</del><ins>+        return self._expandMemberPrincipals()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -410,18 +447,12 @@
</span><span class="cx">         Return the complete, flattened set of principals belonging to this
</span><span class="cx">         group.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        returnValue((yield self._expandMemberUIDs(infinity=True)))
</del><ins>+        returnValue((yield self._expandMemberPrincipals(infinity=True)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def groupMemberships(self):
</span><del>-        # Get membership UIDs and map to principal resources
-        d = self._index().getMemberships(self.uid)
-        d.addCallback(lambda memberships: [
-            p for p
-            in [self.pcollection.principalForUID(uid) for uid in memberships]
-            if p
-        ])
-        return d
</del><ins>+        # Unlikely to ever want to put a subprincipal into a group
+        return succeed([])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -437,7 +468,7 @@
</span><span class="cx">         @return: True if principal is a proxy (of the correct type) of our parent
</span><span class="cx">         @rtype: C{boolean}
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        readWrite = self.isProxyType(True) # is read-write
</del><ins>+        readWrite = self.isProxyType(True)  # is read-write
</ins><span class="cx">         if principal and self.parent in (yield principal.proxyFor(readWrite)):
</span><span class="cx">             returnValue(True)
</span><span class="cx">         returnValue(False)
</span><span class="lines">@@ -630,7 +661,7 @@
</span><span class="cx"> 
</span><span class="cx">             overdue = yield self._memcacher.checkDeletionTimer(principalUID)
</span><span class="cx"> 
</span><del>-            if overdue == False:
</del><ins>+            if overdue is False:
</ins><span class="cx">                 # Do nothing
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -855,9 +886,9 @@
</span><span class="cx">         )
</span><span class="cx">         if alreadyDone is None:
</span><span class="cx">             for (groupname, member) in (
</span><del>-                    (yield self._db_all_values_for_sql(
-                        &quot;select GROUPNAME, MEMBER from GROUPS&quot;))
-                ):
</del><ins>+                (yield self._db_all_values_for_sql(
+                    &quot;select GROUPNAME, MEMBER from GROUPS&quot;))
+            ):
</ins><span class="cx">                 grouplist = groupname.split(&quot;#&quot;)
</span><span class="cx">                 grouplist[0] = normalizeUUID(grouplist[0])
</span><span class="cx">                 newGroupName = &quot;#&quot;.join(grouplist)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycalendaruserproxyloaderpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxyloader.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxyloader.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/calendaruserproxyloader.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -29,7 +29,6 @@
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config, fullServerPath
</span><del>-from twistedcaldav.directory import calendaruserproxy
</del><span class="cx"> from twistedcaldav.xmlutil import readXML
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -44,6 +43,7 @@
</span><span class="cx"> 
</span><span class="cx"> ATTRIBUTE_REPEAT = &quot;repeat&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class XMLCalendarUserProxyLoader(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     XML calendar user proxy configuration file parser and loader.
</span><span class="lines">@@ -52,10 +52,11 @@
</span><span class="cx">         return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.xmlFile)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, xmlFile):
</del><ins>+    def __init__(self, xmlFile, service):
</ins><span class="cx"> 
</span><span class="cx">         self.items = []
</span><span class="cx">         self.xmlFile = fullServerPath(config.DataRoot, xmlFile)
</span><ins>+        self.service = service
</ins><span class="cx"> 
</span><span class="cx">         # Read in XML
</span><span class="cx">         try:
</span><span class="lines">@@ -131,7 +132,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def updateProxyDB(self):
</span><span class="cx"> 
</span><del>-        db = calendaruserproxy.ProxyDBService
</del><ins>+        db = self.service
</ins><span class="cx">         for item in self.items:
</span><span class="cx">             guid, write_proxies, read_proxies = item
</span><span class="cx">             yield db.setGroupMembers(&quot;%s#%s&quot; % (guid, &quot;calendar-proxy-write&quot;), write_proxies)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorycommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/common.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/common.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/common.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -37,6 +37,7 @@
</span><span class="cx"> 
</span><span class="cx"> uidsResourceName = &quot;__uids__&quot;
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class CommonUIDProvisioningResource(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Common ancestor for addressbook/calendar UID provisioning resources.
</span><span class="lines">@@ -68,10 +69,10 @@
</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><del>-        if not getattr(record, self.enabledAttribute):
</del><ins>+        if not getattr(record, self.enabledAttribute, False):
</ins><span class="cx">             log.debug(&quot;Directory record %r is not enabled for %s&quot; % (
</span><span class="cx">                 record, self.homeResourceTypeName))
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -94,7 +95,7 @@
</span><span class="cx">         if name == &quot;&quot;:
</span><span class="cx">             returnValue((self, ()))
</span><span class="cx"> 
</span><del>-        record = self.directory.recordWithUID(name)
</del><ins>+        record = yield self.directory.recordWithUID(name)
</ins><span class="cx">         if record:
</span><span class="cx">             child = yield self.homeResourceForRecord(record, request)
</span><span class="cx">             returnValue((child, segments[1:]))
</span><span class="lines">@@ -149,7 +150,7 @@
</span><span class="cx">         if name == &quot;&quot;:
</span><span class="cx">             returnValue((self, segments[1:]))
</span><span class="cx"> 
</span><del>-        record = self.directory.recordWithShortName(self.recordType, name)
</del><ins>+        record = yield self.directory.recordWithShortName(self.recordType, name)
</ins><span class="cx">         if record is None:
</span><span class="cx">             returnValue(
</span><span class="cx">                 (NotFoundResource(principalCollections=self._parent.principalCollections()), [])
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorydirectoryprincipalresourcehtml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory-principal-resource.html (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory-principal-resource.html        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory-principal-resource.html        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -11,10 +11,7 @@
</span><span class="cx"> GUID: &lt;t:slot name=&quot;principalGUID&quot;/&gt;
</span><span class="cx"> Record type: &lt;t:slot name=&quot;recordType&quot;/&gt;
</span><span class="cx"> Short names: &lt;t:slot name=&quot;shortNames&quot;/&gt;
</span><del>-Security Identities: &lt;t:slot name=&quot;securityIDs&quot;/&gt;
</del><span class="cx"> Full name: &lt;t:slot name=&quot;fullName&quot;/&gt;
</span><del>-First name: &lt;t:slot name=&quot;firstName&quot;/&gt;
-Last name: &lt;t:slot name=&quot;lastName&quot;/&gt;
</del><span class="cx"> Email addresses:
</span><span class="cx"> &lt;t:slot name=&quot;emailAddresses&quot; /&gt;Principal UID: &lt;t:slot name=&quot;principalUID&quot;/&gt;
</span><span class="cx"> Principal URL: &lt;t:slot name=&quot;principalURL&quot;/&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorydirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/directory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,1509 +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.enabledForCalendaring 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.enabledForCalendaring:
-                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:
-            delegatedGUIDs = 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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryidirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/idirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/idirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/idirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,180 +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 interfaces.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;IDirectoryService&quot;,
-    &quot;IDirectoryRecord&quot;,
-]
-
-from zope.interface import Attribute, Interface
-
-class IDirectoryService(Interface):
-    &quot;&quot;&quot;
-    Directory Service
-    &quot;&quot;&quot;
-    realmName = Attribute(&quot;The name of the authentication realm this service represents.&quot;)
-    guid = Attribute(&quot;A GUID for this service.&quot;)
-
-    def recordTypes(): #@NoSelf
-        &quot;&quot;&quot;
-        @return: a sequence of strings denoting the record types that
-            are kept in the directory.  For example: C{[&quot;users&quot;,
-            &quot;groups&quot;, &quot;resources&quot;]}.
-        &quot;&quot;&quot;
-
-    def listRecords(recordType): #@NoSelf
-        &quot;&quot;&quot;
-        @param type: the type of records to retrieve.
-        @return: an iterable of records of the given type.
-        &quot;&quot;&quot;
-
-    def recordWithShortName(recordType, shortName): #@NoSelf
-        &quot;&quot;&quot;
-        @param recordType: the type of the record to look up.
-        @param shortName: the short name of the record to look up.
-        @return: an L{IDirectoryRecord} with the given short name, or
-            C{None} if no such record exists.
-        &quot;&quot;&quot;
-
-    def recordWithUID(uid): #@NoSelf
-        &quot;&quot;&quot;
-        @param uid: the UID of the record to look up.
-        @return: an L{IDirectoryRecord} with the given UID, or C{None}
-            if no such record exists.
-        &quot;&quot;&quot;
-
-    def recordWithGUID(guid): #@NoSelf
-        &quot;&quot;&quot;
-        @param guid: the GUID of the record to look up.
-        @return: an L{IDirectoryRecord} with the given GUID, or
-            C{None} if no such record exists.
-        &quot;&quot;&quot;
-
-    def recordWithCalendarUserAddress(address): #@NoSelf
-        &quot;&quot;&quot;
-        @param address: the calendar user address of the record to look up.
-        @type address: C{str}
-
-        @return: an L{IDirectoryRecord} with the given calendar user
-            address, or C{None} if no such record is found.  Note that
-            some directory services may not be able to locate records
-            by calendar user address, or may return partial results.
-            Note also that the calendar server may add to the list of
-            valid calendar user addresses for a user, and the
-            directory service may not be aware of these addresses.
-        &quot;&quot;&quot;
-
-    def recordWithCachedGroupsAlias(recordType, alias): #@NoSelf
-        &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;
-
-    def recordsMatchingFields(fields): #@NoSelf
-        &quot;&quot;&quot;
-        @return: a deferred sequence of L{IDirectoryRecord}s which
-            match the given fields.
-        &quot;&quot;&quot;
-
-    def recordsMatchingTokens(tokens, context=None): #@NoSelf
-        &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;
-
-    def setRealm(realmName): #@NoSelf
-        &quot;&quot;&quot;
-        Set a new realm name for this (and nested services if any)
-
-        @param realmName: the realm name this service should use.
-        &quot;&quot;&quot;
-
-
-
-class IDirectoryRecord(Interface):
-    &quot;&quot;&quot;
-    Directory Record
-    &quot;&quot;&quot;
-    service = Attribute(&quot;The L{IDirectoryService} this record exists in.&quot;)
-    recordType = Attribute(&quot;The type of this record.&quot;)
-    guid = Attribute(&quot;The GUID of this record.&quot;)
-    uid = Attribute(&quot;The UID of this record.&quot;)
-    enabled = Attribute(&quot;Determines whether this record should allow a principal to be created.&quot;)
-    serverID = Attribute(&quot;Identifies the server that actually hosts data for the record.&quot;)
-    shortNames = Attribute(&quot;The names for this record.&quot;)
-    authIDs = Attribute(&quot;Alternative security identities for this record.&quot;)
-    fullName = Attribute(&quot;The full name of this record.&quot;)
-    firstName = Attribute(&quot;The first name of this record.&quot;)
-    lastName = Attribute(&quot;The last name of this record.&quot;)
-    emailAddresses = Attribute(&quot;The email addresses of this record.&quot;)
-    enabledForCalendaring = Attribute(&quot;Determines whether this record creates a principal with a calendar home.&quot;)
-    enabledForAddressBooks = Attribute(&quot;Determines whether this record creates a principal with an address book home.&quot;)
-    calendarUserAddresses = Attribute(
-        &quot;&quot;&quot;
-        An iterable of C{str}s representing calendar user addresses for this
-        L{IDirectoryRecord}.
-
-        A &quot;calendar user address&quot;, as defined by U{RFC 2445 section
-        4.3.3&lt;http://xml.resource.org/public/rfc/html/rfc2445.html#anchor50&gt;},
-        is simply an URI which identifies this user.  Some of these URIs are
-        relative references to URLs from the root of the calendar server's HTTP
-        hierarchy.
-        &quot;&quot;&quot;
-    )
-
-    def members(): #@NoSelf
-        &quot;&quot;&quot;
-        @return: an iterable of L{IDirectoryRecord}s for the members of this
-            (group) record.
-        &quot;&quot;&quot;
-
-    def groups(): #@NoSelf
-        &quot;&quot;&quot;
-        @return: an iterable of L{IDirectoryRecord}s for the groups this
-            record is a member of.
-        &quot;&quot;&quot;
-
-    def verifyCredentials(credentials): #@NoSelf
-        &quot;&quot;&quot;
-        Verify that the given credentials can authenticate the principal
-        represented by this record.
-        @param credentials: the credentials to authenticate with.
-        @return: C{True} if the given credentials match this record,
-            C{False} otherwise.
-        &quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryldapdirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/ldapdirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/ldapdirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/ldapdirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryopendirectorybackerpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/opendirectorybacker.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/opendirectorybacker.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/opendirectorybacker.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,1960 +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;
-Apple Open Directory directory service implementation for backing up directory-backed address books
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;OpenDirectoryBackingService&quot;, &quot;VCardRecord&quot;,
-]
-
-from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
-
-from pycalendar.datetime import DateTime
-from pycalendar.vcard.adr import Adr
-from pycalendar.vcard.n import N
-
-
-from twext.python.filepath import CachingFilePath as FilePath
-
-from txweb2.dav.resource import DAVPropertyMixIn
-from txweb2.dav.util import joinURL
-from txweb2.http_headers import MimeType, generateContentType, ETag
-
-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator, succeed
-
-from twistedcaldav import customxml, carddavxml
-from twistedcaldav.config import config
-from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
-from twistedcaldav.vcard import Component, Property, vCardProductID
-
-from txdav.carddav.datastore.query.filter import IsNotDefined, ParameterFilter, \
-    TextMatch
-from txdav.xml import element as davxml
-from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
-
-from os import listdir
-from os.path import join, abspath
-from random import random
-from socket import getfqdn
-from tempfile import mkstemp, gettempdir
-from xmlrpclib import datetime
-import hashlib
-import os
-import sys
-import time
-import traceback
-
-class OpenDirectoryBackingService(DirectoryService):
-    &quot;&quot;&quot;
-    Open Directory implementation of L{IDirectoryService}.
-    &quot;&quot;&quot;
-
-    baseGUID = &quot;BF07A1A2-5BB5-4A4D-A59A-67260EA7E143&quot;
-
-    def __repr__(self):
-        return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.realmName,)
-
-
-    def __init__(self, params):
-        self._actuallyConfigure(**params)
-
-
-    def _actuallyConfigure(
-        self, queryPeopleRecords=True,
-        peopleNode=&quot;/Search/Contacts&quot;,
-        queryUserRecords=True,
-        userNode=&quot;/Search&quot;,
-        maxDSQueryRecords=0,            # maximum number of records requested for any ds query
-
-        queryDSLocal=False,             # query in DSLocal -- debug
-        dsLocalCacheTimeout=30,
-        ignoreSystemRecords=True,
-
-        liveQuery=True,                 # query directory service as needed
-        fakeETag=True,                  # eTag is not reliable if True
-
-        cacheQuery=False,
-        cacheTimeout=30,                # cache timeout
-
-        addDSAttrXProperties=False,        # add dsattributes to vcards as &quot;X-&quot; attributes
-        standardizeSyntheticUIDs=False,  # use simple synthetic UIDs --- good for testing
-        appleInternalServer=False,
-
-        additionalAttributes=[],
-        allowedAttributes=[],
-        directoryBackedAddressBook=None
-    ):
-        &quot;&quot;&quot;
-        @queryPeopleRecords: C{True} to query for People records
-        @queryUserRecords: C{True} to query for User records
-        @maxDSQueryRecords: maximum number of (unfiltered) ds records retrieved before raising
-            NumberOfMatchesWithinLimits exception or returning results
-        @dsLocalCacheTimeout: how log to keep cache of DSLocal records
-        @liveQuery: C{True} to query the directory as needed
-        @fakeETag: C{True} to use a fake eTag; allows ds queries with partial attributes
-        @cacheQuery: C{True} to query the directory and cache results
-        @cacheTimeout: if caching, the average cache timeout
-        @standardizeSyntheticUIDs: C{True} when creating synthetic UID (==f(Node, Type, Record Name)),
-            use a standard Node name. This allows testing with the same UID on different hosts
-        @allowedAttributes: list of DSAttributes that are used to create VCards
-
-        &quot;&quot;&quot;
-        assert directoryBackedAddressBook is not None
-        self.directoryBackedAddressBook = directoryBackedAddressBook
-
-        self.peopleDirectory = None
-        self.peopleNode = None
-        self.userDirectory = None
-        self.userNode = None
-
-        self.realmName = None # needed for super
-
-        if queryPeopleRecords or not queryUserRecords:
-            self.peopleNode = peopleNode
-            try:
-                self.peopleDirectory = opendirectory.odInit(peopleNode)
-            except opendirectory.ODError, e:
-                self.log.error(&quot;Open Directory (node=%s) Initialization error: %s&quot; % (peopleNode, e))
-                raise
-            self.realmName = peopleNode
-
-        if queryUserRecords:
-            if self.peopleNode == userNode:          # use sane directory and node if they are equal
-                self.userNode = self.peopleNode
-                self.userDirectory = self.peopleDirectory
-            else:
-                self.userNode = userNode
-                try:
-                    self.userDirectory = opendirectory.odInit(userNode)
-                except opendirectory.ODError, e:
-                    self.log.error(&quot;Open Directory (node=%s) Initialization error: %s&quot; % (userNode, e))
-                    raise
-                if self.realmName:
-                    self.realmName += &quot;+&quot; + userNode
-                else:
-                    self.realmName = userNode
-
-        self.maxDSQueryRecords = maxDSQueryRecords
-
-        self.ignoreSystemRecords = ignoreSystemRecords
-        self.queryDSLocal = queryDSLocal
-        self.dsLocalCacheTimeout = dsLocalCacheTimeout
-
-        self.liveQuery = liveQuery or not cacheQuery
-        self.fakeETag = fakeETag
-
-        self.cacheQuery = cacheQuery
-
-        self.cacheTimeout = cacheTimeout if cacheTimeout &gt; 0 else 30
-
-        self.addDSAttrXProperties = addDSAttrXProperties
-        self.standardizeSyntheticUIDs = standardizeSyntheticUIDs
-        self.appleInternalServer = appleInternalServer
-
-        self.additionalAttributes = additionalAttributes
-        # filter allows attributes, but make sure there are a minimum of attributes for functionality
-        if allowedAttributes:
-            self.allowedDSQueryAttributes = sorted(list(set(
-                                                [attr for attr in VCardRecord.allDSQueryAttributes
-                                                    if (isinstance(attr, str) and attr in allowedAttributes) or
-                                                       (isinstance(attr, tuple) and attr[0] in allowedAttributes)] +
-                                                VCardRecord.dsqueryAttributesForProperty.get(&quot;X-INTERNAL-REQUIRED&quot;)
-                                                )))
-            if (self.allowedDSQueryAttributes != VCardRecord.allDSQueryAttributes):
-                self.log.info(&quot;Allowed DS query attributes = %r&quot; % (self.allowedDSQueryAttributes,))
-        else:
-            self.allowedDSQueryAttributes = VCardRecord.allDSQueryAttributes
-
-        #self.returnedAttributes = VCardRecord.allDSQueryAttributes
-        self.returnedAttributes = self.allowedDSQueryAttributes
-
-        self._dsLocalRecords = []
-        self._nextDSLocalQueryTime = 0
-
-        # get this now once
-        hostname = getfqdn()
-        if hostname:
-            self.defaultNodeName = &quot;/LDAPv3/&quot; + hostname
-        else:
-            self.defaultNodeName = None
-
-        #cleanup
-        self._cleanupTime = time.time()
-
-        # file system locks
-        self._initLockPath = join(config.DocumentRoot, &quot;.directory_address_book_create_lock&quot;)
-        self._createdLockPath = join(config.DocumentRoot, &quot;.directory_address_book_created_lock&quot;)
-        self._updateLockPath = join(config.DocumentRoot, &quot;.directory_address_book_update_lock&quot;)
-        self._tmpDirAddressBookLockPath = join(config.DocumentRoot, &quot;.directory_address_book_tmpFolder_lock&quot;)
-
-        self._updateLock = MemcacheLock(&quot;OpenDirectoryBacker&quot;, self._updateLockPath)
-        self._tmpDirAddressBookLock = MemcacheLock(&quot;OpenDirectoryBacker&quot;, self._tmpDirAddressBookLockPath)
-
-        # optimization so we don't have to always get create lock
-        self._triedCreateLock = False
-        self._created = False
-
-
-    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
-
-
-    @inlineCallbacks
-    def available(self):
-        if not self._triedCreateLock:
-            returnValue(False)
-        elif not self._created:
-            createdLock = MemcacheLock(&quot;OpenDirectoryBacker&quot;, self._createdLockPath)
-            self.log.debug(&quot;blocking on lock of: \&quot;%s\&quot;)&quot; % self._createdLockPath)
-            self._created = (yield createdLock.locked())
-
-        returnValue(self._created)
-
-
-    def updateLock(self):
-        return self._updateLock
-
-
-    @inlineCallbacks
-    def createCache(self):
-        &quot;&quot;&quot;
-        If caching, create the cache for the first time.
-        &quot;&quot;&quot;
-
-        if not self.liveQuery:
-            self.log.info(&quot;loading directory address book&quot;)
-
-            # get init lock
-            initLock = MemcacheLock(&quot;OpenDirectoryBacker&quot;, self._initLockPath, timeout=0)
-            self.log.debug(&quot;Attempt lock of: \&quot;%s\&quot;)&quot; % self._initLockPath)
-            gotCreateLock = False
-            try:
-                yield initLock.acquire()
-                gotCreateLock = True
-            except MemcacheLockTimeoutError:
-                pass
-
-            self._triedCreateLock = True
-
-            if gotCreateLock:
-                self.log.debug(&quot;Got lock!&quot;)
-                yield self._refreshCache(flushCache=False, creating=True)
-            else:
-                self.log.debug(&quot;Could not get lock - directory address book will be filled by peer&quot;)
-
-
-    @inlineCallbacks
-    def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0):
-        &quot;&quot;&quot;
-        refresh the cache.
-        &quot;&quot;&quot;
-
-        #print(&quot;_refreshCache:, flushCache=%s, creating=%s, reschedule=%s, query = %s&quot; % (flushCache, creating, reschedule, &quot;None&quot; if query is None else query.generate(),))
-
-        def refreshLater():
-            #
-            # Add jitter/fuzz factor to avoid stampede for large OD query
-            #
-            cacheTimeout = min(self.cacheTimeout, 60) * 60
-            cacheTimeout = (cacheTimeout * random()) - (cacheTimeout / 2)
-            cacheTimeout += self.cacheTimeout * 60
-            reactor.callLater(cacheTimeout, self._refreshCache) #@UndefinedVariable
-            self.log.info(&quot;Refresh directory address book in %d minutes %d seconds&quot; % divmod(cacheTimeout, 60))
-
-        def cleanupLater():
-
-            # try to cancel previous call if last clean up was less than 15 minutes ago
-            if (time.time() - self._cleanupTime) &lt; 15 * 60:
-                try:
-                    self._lastCleanupCall.cancel()
-                except:
-                    pass
-
-            #
-            # Add jitter/fuzz factor
-            #
-            nom = 120
-            later = nom * (random() + .5)
-            self._lastCleanupCall = reactor.callLater(later, removeTmpAddressBooks) #@UndefinedVariable
-            self.log.info(&quot;Remove temporary directory address books in %d minutes %d seconds&quot; % divmod(later, 60))
-
-
-        def getTmpDirAndTmpFilePrefixSuffix():
-            # need to have temp file on same volumes as documents so that move works
-            absDocPath = abspath(config.DocumentRoot)
-            if absDocPath.startswith(&quot;/Volumes/&quot;):
-                tmpDir = absDocPath
-                prefix = &quot;.directoryAddressBook-&quot;
-            else:
-                tmpDir = gettempdir()
-                prefix = &quot;directoryAddressBook-&quot;
-
-            return (tmpDir, prefix, &quot;.tmp&quot;)
-
-        def makeTmpFilename():
-            tmpDir, prefix, suffix = getTmpDirAndTmpFilePrefixSuffix()
-            fd, fname = mkstemp(suffix=suffix, prefix=prefix, dir=tmpDir)
-            os.close(fd)
-            os.remove(fname)
-            return fname
-
-        @inlineCallbacks
-        def removeTmpAddressBooks():
-            self.log.info(&quot;Checking for temporary directory address books&quot;)
-            tmpDir, prefix, suffix = getTmpDirAndTmpFilePrefixSuffix()
-
-            tmpDirLock = self._tmpDirAddressBookLock
-            self.log.debug(&quot;blocking on lock of: \&quot;%s\&quot;)&quot; % self._tmpDirAddressBookLockPath)
-            yield tmpDirLock.acquire()
-
-            try:
-                for name in listdir(tmpDir):
-                    if name.startswith(prefix) and name.endswith(suffix):
-                        try:
-                            path = join(tmpDir, name)
-                            self.log.info(&quot;Deleting temporary directory address book at: %s&quot; % path)
-                            FilePath(path).remove()
-                            self.log.debug(&quot;Done deleting&quot;)
-                        except:
-                            self.log.info(&quot;Deletion failed&quot;)
-            finally:
-                self.log.debug(&quot;unlocking: \&quot;%s\&quot;)&quot; % self._tmpDirAddressBookLockPath)
-                yield tmpDirLock.release()
-
-            self._cleanupTime = time.time()
-
-        updateLock = None
-        limited = False
-        try:
-
-            try:
-                # get the records
-                if clear:
-                    records = {}
-                else:
-                    records, limited = (yield self._getDirectoryRecords(query, attributes, maxRecords))
-
-                # calculate the hash
-                # simple for now, could use MD5 digest if too many collisions
-                newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + &quot;:&quot; + self.realmName + &quot;:&quot; + &quot;&quot;.join(str(hash(records[key])) for key in records.keys()))))
-
-                # get the old hash
-                oldAddressBookCTag = &quot;&quot;
-                updateLock = self.updateLock()
-                self.log.debug(&quot;blocking on lock of: \&quot;%s\&quot;)&quot; % self._updateLockPath)
-                yield updateLock.acquire()
-
-                if not flushCache:
-                    # get update lock
-                    try:
-                        oldAddressBookCTag = self.directoryBackedAddressBook.readDeadProperty((calendarserver_namespace, &quot;getctag&quot;))
-                    except:
-                        oldAddressBookCTag = &quot;&quot;
-
-                self.log.debug(&quot;Comparing {http://calendarserver.org/ns/}getctag: new = %s, old = %s&quot; % (newAddressBookCTag, oldAddressBookCTag))
-                if str(newAddressBookCTag) != str(oldAddressBookCTag):
-
-                    self.log.debug(&quot;unlocking: \&quot;%s\&quot;)&quot; % self._updateLockPath)
-                    yield updateLock.release()
-                    updateLock = None
-
-                if not keepLock:
-                    self.log.debug(&quot;unlocking: \&quot;%s\&quot;)&quot; % self._updateLockPath)
-                    yield updateLock.release()
-                    updateLock = None
-
-            except:
-                cleanupLater()
-                if reschedule:
-                    refreshLater()
-                raise
-
-            if creating:
-                createdLock = MemcacheLock(&quot;OpenDirectoryBacker&quot;, self._createdLockPath)
-                self.log.debug(&quot;blocking on lock of: \&quot;%s\&quot;)&quot; % self._createdLockPath)
-                yield createdLock.acquire()
-
-            cleanupLater()
-            if reschedule:
-                refreshLater()
-
-        except:
-            if updateLock:
-                yield updateLock.release()
-            raise
-
-        returnValue((updateLock, limited))
-
-
-    def _getDSLocalRecords(self):
-
-        def generateDSLocalRecords():
-
-            records = {}
-
-            recordTypes = [dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers, ]
-            try:
-                localNodeDirectory = opendirectory.odInit(&quot;/Local/Default&quot;)
-                self.log.debug(&quot;opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)&quot; % (
-                        &quot;/DSLocal&quot;,
-                        recordTypes,
-                        self.returnedAttributes,
-                    ))
-                results = list(opendirectory.listAllRecordsWithAttributes_list(
-                        localNodeDirectory,
-                        recordTypes,
-                        self.returnedAttributes,
-                    ))
-            except opendirectory.ODError, ex:
-                self.log.error(&quot;Open Directory (node=%s) error: %s&quot; % (&quot;/Local/Default&quot;, str(ex)))
-                raise
-
-            self._dsLocalRecords = []
-            for (recordShortName, value) in results: #@UnusedVariable
-
-                record = VCardRecord(self, value, &quot;/Local/Default&quot;)
-
-                if self.ignoreSystemRecords:
-                    # remove system users and people
-                    if record.guid.startswith(&quot;FFFFEEEE-DDDD-CCCC-BBBB-AAAA&quot;):
-                        self.log.info(&quot;Ignoring vcard for system record %s&quot; % (record,))
-                        continue
-
-                if record.guid in records:
-                    self.log.info(&quot;Record skipped due to conflict (duplicate uuid): %s&quot; % (record,))
-                else:
-                    try:
-                        vCardText = record.vCardText()
-                    except:
-                        traceback.print_exc()
-                        self.log.info(&quot;Could not get vcard for record %s&quot; % (record,))
-                    else:
-                        self.log.debug(&quot;VCard text =\n%s&quot; % (vCardText,))
-                        records[record.guid] = record
-
-            return records
-
-        if not self.liveQuery or not self.queryDSLocal:
-            return {}
-
-        if time.time() &gt; self._nextDSLocalQueryTime:
-            self._dsLocalRecords = generateDSLocalRecords()
-            # Add jitter/fuzz factor
-            self._nextDSLocalQueryTime = time.time() + self.dsLocalCacheTimeout * (random() + 0.5) * 60
-
-        return self._dsLocalRecords
-
-
-    @inlineCallbacks
-    def _getDirectoryRecords(self, query=None, attributes=None, maxRecords=0):
-        &quot;&quot;&quot;
-        Get a list of filtered VCardRecord for the given query with the given attributes.
-        query == None gets all records. attribute == None gets VCardRecord.allDSQueryAttributes
-        &quot;&quot;&quot;
-        limited = False
-        queryResults = (yield self._queryDirectory(query, attributes, maxRecords))
-        if maxRecords and len(queryResults) &gt;= maxRecords:
-            limited = True
-            self.log.debug(&quot;Directory address book record limit (= %d) reached.&quot; % (maxRecords,))
-
-        self.log.debug(&quot;Query done. Inspecting %s results&quot; % len(queryResults))
-
-        records = self._getDSLocalRecords().copy()
-        self.log.debug(&quot;Adding %s DSLocal results&quot; % len(records.keys()))
-
-        for (recordShortName, value) in queryResults: #@UnusedVariable
-
-            record = VCardRecord(self, value, self.defaultNodeName)
-
-            if self.ignoreSystemRecords:
-                # remove system users and people
-                if record.guid.startswith(&quot;FFFFEEEE-DDDD-CCCC-BBBB-AAAA&quot;):
-                    self.log.info(&quot;Ignoring vcard for system record %s&quot; % (record,))
-                    continue
-
-            if record.guid in records:
-                self.log.info(&quot;Ignoring vcard for record due to conflict (duplicate uuid): %s&quot; % (record,))
-            else:
-                records[record.guid] = record
-
-        self.log.debug(&quot;After filtering, %s records (limited=%s).&quot; % (len(records), limited))
-        returnValue((records, limited,))
-
-
-    def _queryDirectory(self, query=None, attributes=None, maxRecords=0):
-
-        startTime = time.time()
-
-        if not attributes:
-            attributes = self.returnedAttributes
-
-        attributes = list(set(attributes + self.additionalAttributes)) # remove duplicates
-
-        directoryAndRecordTypes = []
-        if self.peopleDirectory == self.userDirectory:
-            # use single ds query if possible for best performance
-            directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, (dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers)))
-        else:
-            if self.peopleDirectory:
-                directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, dsattributes.kDSStdRecordTypePeople))
-            if self.userDirectory:
-                directoryAndRecordTypes.append((self.userDirectory, self.userNode, dsattributes.kDSStdRecordTypeUsers))
-
-        allResults = []
-        for directory, node, recordType in directoryAndRecordTypes:
-            try:
-                if query:
-                    if isinstance(query, dsquery.match) and query.value is not &quot;&quot;:
-                        self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r,%r)&quot; % (
-                            node,
-                            query.attribute,
-                            query.value,
-                            query.matchType,
-                            False,
-                            recordType,
-                            attributes,
-                            maxRecords,
-                        ))
-                        results = list(
-                            opendirectory.queryRecordsWithAttribute_list(
-                                directory,
-                                query.attribute,
-                                query.value,
-                                query.matchType,
-                                False,
-                                recordType,
-                                attributes,
-                                maxRecords,
-                            ))
-                    else:
-                        self.log.debug(&quot;opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r)&quot; % (
-                            node,
-                            query.generate(),
-                            False,
-                            recordType,
-                            attributes,
-                            maxRecords,
-                        ))
-                        results = list(
-                            opendirectory.queryRecordsWithAttributes_list(
-                                directory,
-                                query.generate(),
-                                False,
-                                recordType,
-                                attributes,
-                                maxRecords,
-                            ))
-                else:
-                    self.log.debug(&quot;opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r,%r)&quot; % (
-                        node,
-                        recordType,
-                        attributes,
-                        maxRecords,
-                    ))
-                    results = list(
-                        opendirectory.listAllRecordsWithAttributes_list(
-                            directory,
-                            recordType,
-                            attributes,
-                            maxRecords,
-                        ))
-            except opendirectory.ODError, ex:
-                self.log.error(&quot;Open Directory (node=%s) error: %s&quot; % (self.realmName, str(ex)))
-                raise
-
-            allResults.extend(results)
-
-            if maxRecords:
-                maxRecords -= len(results)
-                if maxRecords &lt;= 0:
-                    break
-
-        elaspedTime = time.time() - startTime
-        self.log.info(&quot;Timing: Directory query: %.1f ms (%d records, %.2f records/sec)&quot; % (elaspedTime * 1000, len(allResults), len(allResults) / elaspedTime))
-        return succeed(allResults)
-
-
-    def _getDSFilter(self, addressBookFilter):
-        &quot;&quot;&quot;
-        Convert the supplied addressbook-query into an expression tree.
-
-        @param filter: the L{Filter} for the addressbook-query to convert.
-        @return: (needsAllRecords, espressionAttributes, expression) tuple
-        &quot;&quot;&quot;
-        def propFilterListQuery(filterAllOf, propFilters):
-
-            def propFilterExpression(filterAllOf, propFilter):
-                #print(&quot;propFilterExpression&quot;)
-                &quot;&quot;&quot;
-                Create an expression for a single prop-filter element.
-
-                @param propFilter: the L{PropertyFilter} element.
-                @return: (needsAllRecords, espressionAttributes, expressions) tuple
-                &quot;&quot;&quot;
-
-                def definedExpression(defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
-                    if constant or filterName in (&quot;N&quot; , &quot;FN&quot;, &quot;UID&quot;,):
-                        return (defined, [], [])     # all records have this property so no records do not have it
-                    else:
-                        matchList = list(set([dsquery.match(attrName, &quot;&quot;, dsattributes.eDSStartsWith) for attrName in allAttrStrings]))
-                        if defined:
-                            return andOrExpression(allOf, queryAttributes, matchList)
-                        else:
-                            if len(matchList) &gt; 1:
-                                expr = dsquery.expression(dsquery.expression.OR, matchList)
-                            else:
-                                expr = matchList
-                            return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
-                    #end isNotDefinedExpression()
-
-
-                def andOrExpression(propFilterAllOf, queryAttributes, matchList):
-                    #print(&quot;andOrExpression(propFilterAllOf=%r, queryAttributes%r, matchList%r)&quot; % (propFilterAllOf, queryAttributes, matchList))
-                    if propFilterAllOf and len(matchList):
-                        # add OR expression because parent will AND
-                        return (False, queryAttributes, [dsquery.expression(dsquery.expression.OR, matchList), ])
-                    else:
-                        return (False, queryAttributes, matchList)
-                    #end andOrExpression()
-
-
-                # short circuit parameter filters
-                def supportedParamter(filterName, paramFilters, propFilterAllOf):
-
-                    def supported(paramFilterName, paramFilterDefined, params):
-                        paramFilterName = paramFilterName.upper()
-                        if len(params.keys()) and ((paramFilterName in params.keys()) != paramFilterDefined):
-                            return False
-                        if len(params[paramFilterName]) and str(paramFilter.qualifier).upper() not in params[paramFilterName]:
-                            return False
-                        return True
-                        #end supported()
-
-                    oneSupported = False
-                    for paramFilter in paramFilters:
-                        if filterName == &quot;PHOTO&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;ENCODING&quot;: [&quot;B&quot;, ], &quot;TYPE&quot;: [&quot;JPEG&quot;, ], }):
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;ADR&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;POSTAL&quot;, &quot;PARCEL&quot;, ], }):
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;LABEL&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;TYPE&quot;: [&quot;POSTAL&quot;, &quot;PARCEL&quot;, ]}):
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;TEL&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;TYPE&quot;: [], }): # has params derived from ds attributes
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;EMAIL&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;TYPE&quot;: [], }): # has params derived from ds attributes
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;URL&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {}):
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif filterName == &quot;KEY&quot;:
-                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {&quot;ENCODING&quot;: [&quot;B&quot;, ], &quot;TYPE&quot;: [&quot;PGPPUBILICKEY&quot;, &quot;USERCERTIFICATE&quot;, &quot;USERPKCS12DATA&quot;, &quot;USERSMIMECERTIFICATE&quot;, ]}):
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-                        elif not filterName.startswith(&quot;X-&quot;): #X- IMHandles X-ABRELATEDNAMES excepted, no other params are used
-                            if propFilterAllOf == paramFilter.defined:
-                                return not propFilterAllOf
-                            oneSupported |= propFilterAllOf
-
-                    if propFilterAllOf:
-                        return True
-                    else:
-                        return oneSupported
-                    #end supportedParamter()
-
-
-                def textMatchElementExpression(propFilterAllOf, textMatchElement):
-
-                    # pre process text match strings for ds query
-                    def getMatchStrings(propFilter, matchString):
-
-                        if propFilter.filter_name in (&quot;REV&quot; , &quot;BDAY&quot;,):
-                            rawString = matchString
-                            matchString = &quot;&quot;
-                            for c in rawString:
-                                if not c in &quot;TZ-:&quot;:
-                                    matchString += c
-                        elif propFilter.filter_name == &quot;GEO&quot;:
-                            matchString = &quot;,&quot;.join(matchString.split(&quot;;&quot;))
-
-                        if propFilter.filter_name in (&quot;N&quot; , &quot;ADR&quot;, &quot;ORG&quot;,):
-                            # for structured properties, change into multiple strings for ds query
-                            if propFilter.filter_name == &quot;ADR&quot;:
-                                #split by newline and comma
-                                rawStrings = &quot;,&quot;.join(matchString.split(&quot;\n&quot;)).split(&quot;,&quot;)
-                            else:
-                                #split by space
-                                rawStrings = matchString.split(&quot; &quot;)
-
-                            # remove empty strings
-                            matchStrings = []
-                            for oneString in rawStrings:
-                                if len(oneString):
-                                    matchStrings += [oneString, ]
-                            return matchStrings
-
-                        elif len(matchString):
-                            return [matchString, ]
-                        else:
-                            return []
-                        # end getMatchStrings
-
-                    if constant:
-                        # do the match right now!  Return either all or none.
-                        return(textMatchElement.test([constant, ]), [], [])
-                    else:
-
-                        matchStrings = getMatchStrings(propFilter, textMatchElement.text)
-
-                        if not len(matchStrings) or binaryAttrStrs:
-                            # no searching text in binary ds attributes, so change to defined/not defined case
-                            if textMatchElement.negate:
-                                return definedExpression(False, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-                            # else fall through to attribute exists case below
-                        else:
-
-                            # special case UID's formed from node and record name
-                            if propFilter.filter_name == &quot;UID&quot;:
-                                matchString = matchStrings[0]
-                                seperatorIndex = matchString.find(VCardRecord.peopleUIDSeparator)
-                                if seperatorIndex &gt; 1:
-                                    recordNameStart = seperatorIndex + len(VCardRecord.peopleUIDSeparator)
-                                else:
-                                    seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)
-                                    if seperatorIndex &gt; 1:
-                                        recordNameStart = seperatorIndex + len(VCardRecord.userUIDSeparator)
-                                    else:
-                                        recordNameStart = sys.maxint
-
-                                if recordNameStart &lt; len(matchString) - 1:
-                                    try:
-                                        recordNameQualifier = matchString[recordNameStart:].decode(&quot;base64&quot;).decode(&quot;utf8&quot;)
-                                    except Exception, e:
-                                        self.log.debug(&quot;Could not decode UID string %r in %r: %r&quot; % (matchString[recordNameStart:], matchString, e,))
-                                    else:
-                                        if textMatchElement.negate:
-                                            return (False, queryAttributes,
-                                                    [dsquery.expression(dsquery.expression.NOT, dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact)), ]
-                                                    )
-                                        else:
-                                            return (False, queryAttributes,
-                                                    [dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact), ]
-                                                    )
-
-                            # use match_type where possible depending on property/attribute mapping
-                            # Note that case sensitive negate will not work
-                            #        Should return all records in that case
-                            matchType = dsattributes.eDSContains
-                            if propFilter.filter_name in (&quot;NICKNAME&quot; , &quot;TITLE&quot; , &quot;NOTE&quot; , &quot;UID&quot;, &quot;URL&quot;, &quot;N&quot;, &quot;ADR&quot;, &quot;ORG&quot;, &quot;REV&quot;, &quot;LABEL&quot;,):
-                                if textMatchElement.match_type == &quot;equals&quot;:
-                                        matchType = dsattributes.eDSExact
-                                elif textMatchElement.match_type == &quot;starts-with&quot;:
-                                        matchType = dsattributes.eDSStartsWith
-                                elif textMatchElement.match_type == &quot;ends-with&quot;:
-                                        matchType = dsattributes.eDSEndsWith
-
-                            matchList = []
-                            for matchString in matchStrings:
-                                matchList += [dsquery.match(attrName, matchString, matchType) for attrName in stringAttrStrs]
-
-                            matchList = list(set(matchList))
-
-                            if textMatchElement.negate:
-                                if len(matchList) &gt; 1:
-                                    expr = dsquery.expression(dsquery.expression.OR, matchList)
-                                else:
-                                    expr = matchList
-                                return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
-                            else:
-                                return andOrExpression(propFilterAllOf, queryAttributes, matchList)
-
-                    # attribute exists search
-                    return definedExpression(True, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-                    #end textMatchElementExpression()
-
-                # get attribute strings from dsqueryAttributesForProperty list
-                queryAttributes = list(set(VCardRecord.dsqueryAttributesForProperty.get(propFilter.filter_name, [])).intersection(set(self.allowedDSQueryAttributes)))
-
-                binaryAttrStrs = []
-                stringAttrStrs = []
-                for attr in queryAttributes:
-                    if isinstance(attr, tuple):
-                        binaryAttrStrs.append(attr[0])
-                    else:
-                        stringAttrStrs.append(attr)
-                allAttrStrings = stringAttrStrs + binaryAttrStrs
-
-                constant = VCardRecord.constantProperties.get(propFilter.filter_name)
-                if not constant and not allAttrStrings:
-                    return (False, [], [])
-
-                if propFilter.qualifier and isinstance(propFilter.qualifier, IsNotDefined):
-                    return definedExpression(False, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-
-                paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, ParameterFilter)]
-                textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, TextMatch)]
-                propFilterAllOf = propFilter.propfilter_test == &quot;allof&quot;
-
-                # handle parameter filter elements
-                if len(paramFilterElements) &gt; 0:
-                    if supportedParamter(propFilter.filter_name, paramFilterElements, propFilterAllOf):
-                        if len(textMatchElements) == 0:
-                            return definedExpression(True, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-                    else:
-                        if propFilterAllOf:
-                            return (False, [], [])
-
-                # handle text match elements
-                propFilterNeedsAllRecords = propFilterAllOf
-                propFilterAttributes = []
-                propFilterExpressionList = []
-                for textMatchElement in textMatchElements:
-
-                    textMatchNeedsAllRecords, textMatchExpressionAttributes, textMatchExpression = textMatchElementExpression(propFilterAllOf, textMatchElement)
-                    if propFilterAllOf:
-                        propFilterNeedsAllRecords &amp;= textMatchNeedsAllRecords
-                    else:
-                        propFilterNeedsAllRecords |= textMatchNeedsAllRecords
-                    propFilterAttributes += textMatchExpressionAttributes
-                    propFilterExpressionList += textMatchExpression
-
-                if (len(propFilterExpressionList) &gt; 1) and (filterAllOf != propFilterAllOf):
-                    propFilterExpressions = [dsquery.expression(dsquery.expression.AND if propFilterAllOf else dsquery.expression.OR , list(set(propFilterExpressionList)))] # remove duplicates
-                else:
-                    propFilterExpressions = list(set(propFilterExpressionList))
-
-                return (propFilterNeedsAllRecords, propFilterAttributes, propFilterExpressions)
-                #end propFilterExpression
-
-            #print(&quot;propFilterListQuery: filterAllOf=%r, propFilters=%r&quot; % (filterAllOf, propFilters,))
-            &quot;&quot;&quot;
-            Create an expression for a list of prop-filter elements.
-
-            @param filterAllOf: the C{True} if parent filter test is &quot;allof&quot;
-            @param propFilters: the C{list} of L{ComponentFilter} elements.
-            @return: (needsAllRecords, espressionAttributes, expression) tuple
-            &quot;&quot;&quot;
-            needsAllRecords = filterAllOf
-            attributes = []
-            expressions = []
-            for propFilter in propFilters:
-
-                propNeedsAllRecords, propExpressionAttributes, propExpression = propFilterExpression(filterAllOf, propFilter)
-                if filterAllOf:
-                    needsAllRecords &amp;= propNeedsAllRecords
-                else:
-                    needsAllRecords |= propNeedsAllRecords
-                attributes += propExpressionAttributes
-                expressions += propExpression
-
-            if len(expressions) &gt; 1:
-                expr = dsquery.expression(dsquery.expression.AND if filterAllOf else dsquery.expression.OR , list(set(expressions))) # remove duplicates
-            elif len(expressions):
-                expr = expressions[0]
-            else:
-                expr = None
-
-            return (needsAllRecords, attributes, expr)
-
-        #print(&quot;_getDSFilter&quot;)
-        # Lets assume we have a valid filter from the outset
-
-        # Top-level filter contains zero or more prop-filters
-        if addressBookFilter:
-            filterAllOf = addressBookFilter.filter_test == &quot;allof&quot;
-            if len(addressBookFilter.children) &gt; 0:
-                return propFilterListQuery(filterAllOf, addressBookFilter.children)
-            else:
-                return (filterAllOf, [], [])
-        else:
-            return (False, [], [])
-
-
-    def _attributesForAddressBookQuery(self, addressBookQuery):
-
-        propertyNames = []
-        #print( &quot;addressBookQuery.qname=%r&quot; % addressBookQuery.qname)
-        if addressBookQuery.qname() == (&quot;DAV:&quot;, &quot;prop&quot;):
-
-            for property in addressBookQuery.children:
-                #print(&quot;property = %r&quot; % property )
-                if isinstance(property, carddavxml.AddressData):
-                    for addressProperty in property.children:
-                        #print(&quot;addressProperty = %r&quot; % addressProperty )
-                        if isinstance(addressProperty, carddavxml.Property):
-                            #print(&quot;Adding property %r&quot;, addressProperty.attributes[&quot;name&quot;])
-                            propertyNames.append(addressProperty.attributes[&quot;name&quot;])
-
-                elif not self.fakeETag and property.qname() == (&quot;DAV:&quot;, &quot;getetag&quot;):
-                    # for a real etag == md5(vCard), we need all attributes
-                    propertyNames = None
-                    break
-
-        if not len(propertyNames):
-            #print(&quot;using all attributes&quot;)
-            return self.returnedAttributes
-
-        else:
-            propertyNames.append(&quot;X-INTERNAL-MINIMUM-VCARD-PROPERTIES&quot;) # these properties are required to make a vCard
-            queryAttributes = []
-            for prop in propertyNames:
-                if prop in VCardRecord.dsqueryAttributesForProperty:
-                    #print(&quot;adding attributes %r&quot; % VCardRecord.dsqueryAttributesForProperty.get(prop))
-                    queryAttributes += VCardRecord.dsqueryAttributesForProperty.get(prop)
-
-            return list(set(queryAttributes).intersection(set(self.returnedAttributes)))
-
-
-    @inlineCallbacks
-    def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
-        &quot;&quot;&quot;
-        Cache the vCards for a given addressBookFilder and addressBookQuery
-        &quot;&quot;&quot;
-        startTime = time.time()
-        #print(&quot;Timing: cacheVCardsForAddressBookQuery.starttime=%f&quot; % startTime)
-
-        allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
-        #print(&quot;allRecords = %s, query = %s&quot; % (allRecords, &quot;None&quot; if dsFilter is None else dsFilter.generate(),))
-
-        if allRecords:
-            dsFilter = None #  None expression == all Records
-        clear = not allRecords and not dsFilter
-
-        #get unique list of requested attributes
-        if clear:
-            attributes = None
-        else:
-            queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
-            attributes = filterAttributes + queryAttributes
-
-        #calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
-        maxRecords = int(maxResults * 1.2)
-        if self.maxDSQueryRecords and maxRecords &gt; self.maxDSQueryRecords:
-            maxRecords = self.maxDSQueryRecords
-
-        updateLock, limited = (yield self._refreshCache(reschedule=False, query=dsFilter, attributes=attributes, keepLock=True, clear=clear, maxRecords=maxRecords))
-
-        elaspedTime = time.time() - startTime
-        self.log.info(&quot;Timing: Cache fill: %.1f ms&quot; % (elaspedTime * 1000,))
-
-        returnValue((updateLock, limited))
-
-
-    @inlineCallbacks
-    def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
-        &quot;&quot;&quot;
-        Get vCards for a given addressBookFilder and addressBookQuery
-        &quot;&quot;&quot;
-
-        allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
-        #print(&quot;allRecords = %s, query = %s&quot; % (allRecords, &quot;None&quot; if dsFilter is None else dsFilter.generate(),))
-
-        # testing:
-        # allRecords = True
-
-        if allRecords:
-            dsFilter = None #  None expression == all Records
-        clear = not allRecords and not dsFilter
-
-        queryRecords = []
-        limited = False
-
-        if not clear:
-            queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
-            attributes = filterAttributes + queryAttributes
-
-            #calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
-            maxRecords = int(maxResults * 1.2)
-            if self.maxDSQueryRecords and maxRecords &gt; self.maxDSQueryRecords:
-                maxRecords = self.maxDSQueryRecords
-
-            records, limited = (yield self._getDirectoryRecords(dsFilter, attributes, maxRecords))
-
-            #filter out bad records --- should only happen during development
-            for record in records.values():
-                try:
-                    vCardText = record.vCardText()
-                except:
-                    traceback.print_exc()
-                    self.log.info(&quot;Could not get vcard for record %s&quot; % (record,))
-                else:
-                    if not record.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation).startswith(&quot;/Local&quot;):
-                        self.log.debug(&quot;VCard text =\n%s&quot; % (vCardText,))
-                    queryRecords.append(record)
-
-        returnValue((queryRecords, limited,))
-
-
-
-class VCardRecord(DirectoryRecord, DAVPropertyMixIn):
-    &quot;&quot;&quot;
-    Open Directory implementation of L{IDirectoryRecord}.
-    &quot;&quot;&quot;
-
-    # od attributes that may contribute to vcard properties
-    # will be used to translate vCard queries to od queries
-
-    dsqueryAttributesForProperty = {
-
-        &quot;FN&quot; : [
-               dsattributes.kDS1AttrFirstName,
-               dsattributes.kDS1AttrLastName,
-               dsattributes.kDS1AttrMiddleName,
-               dsattributes.kDSNAttrNamePrefix,
-               dsattributes.kDSNAttrNameSuffix,
-               dsattributes.kDS1AttrDistinguishedName,
-               dsattributes.kDSNAttrRecordName,
-               ],
-        &quot;N&quot; : [
-               dsattributes.kDS1AttrFirstName,
-               dsattributes.kDS1AttrLastName,
-               dsattributes.kDS1AttrMiddleName,
-               dsattributes.kDSNAttrNamePrefix,
-               dsattributes.kDSNAttrNameSuffix,
-               dsattributes.kDS1AttrDistinguishedName,
-               dsattributes.kDSNAttrRecordName,
-               ],
-        &quot;NICKNAME&quot; : [
-                dsattributes.kDSNAttrNickName,
-                ],
-        # no binary searching
-        &quot;PHOTO&quot; : [
-                (dsattributes.kDSNAttrJPEGPhoto, &quot;base64&quot;),
-                ],
-        &quot;BDAY&quot; : [
-                dsattributes.kDS1AttrBirthday,
-                ],
-        &quot;ADR&quot; : [
-                dsattributes.kDSNAttrBuilding,
-                dsattributes.kDSNAttrStreet,
-                dsattributes.kDSNAttrCity,
-                dsattributes.kDSNAttrState,
-                dsattributes.kDSNAttrPostalCode,
-                dsattributes.kDSNAttrCountry,
-                ],
-        &quot;LABEL&quot; : [
-                dsattributes.kDSNAttrPostalAddress,
-                dsattributes.kDSNAttrPostalAddressContacts,
-                dsattributes.kDSNAttrAddressLine1,
-                dsattributes.kDSNAttrAddressLine2,
-                dsattributes.kDSNAttrAddressLine3,
-                ],
-         &quot;TEL&quot; : [
-                dsattributes.kDSNAttrPhoneNumber,
-                dsattributes.kDSNAttrMobileNumber,
-                dsattributes.kDSNAttrPagerNumber,
-                dsattributes.kDSNAttrHomePhoneNumber,
-                dsattributes.kDSNAttrPhoneContacts,
-                dsattributes.kDSNAttrFaxNumber,
-                #dsattributes.kDSNAttrAreaCode,
-                ],
-         &quot;EMAIL&quot; : [
-                dsattributes.kDSNAttrEMailAddress,
-                dsattributes.kDSNAttrEMailContacts,
-                ],
-         &quot;GEO&quot; : [
-                dsattributes.kDSNAttrMapCoordinates,
-                ],
-         &quot;TITLE&quot; : [
-                dsattributes.kDSNAttrJobTitle,
-                ],
-         &quot;ORG&quot; : [
-                dsattributes.kDSNAttrCompany,
-                dsattributes.kDSNAttrOrganizationName,
-                dsattributes.kDSNAttrDepartment,
-                ],
-         &quot;NOTE&quot; : [
-                dsattributes.kDS1AttrComment,
-                dsattributes.kDS1AttrNote,
-                ],
-         &quot;REV&quot; : [
-                dsattributes.kDS1AttrModificationTimestamp,
-                ],
-         &quot;UID&quot; : [
-                dsattributes.kDS1AttrGeneratedUID,
-                # special cased
-                #dsattributes.kDSNAttrMetaNodeLocation,
-                #dsattributes.kDSNAttrRecordName,
-                #dsattributes.kDS1AttrDistinguishedName,
-                ],
-         &quot;URL&quot; : [
-                dsattributes.kDS1AttrWeblogURI,
-                dsattributes.kDSNAttrURL,
-                ],
-         &quot;KEY&quot; : [
-                # check on format, are these all binary?
-                (dsattributes.kDSNAttrPGPPublicKey, &quot;base64&quot;),
-                (dsattributes.kDS1AttrUserCertificate, &quot;base64&quot;),
-                (dsattributes.kDS1AttrUserPKCS12Data, &quot;base64&quot;),
-                (dsattributes.kDS1AttrUserSMIMECertificate, &quot;base64&quot;),
-                ],
-         # too bad this is not one X-Attribute with params.     Would make searching easier
-         &quot;X-AIM&quot; : [
-                dsattributes.kDSNAttrIMHandle,
-                ],
-         &quot;X-JABBER&quot; : [
-                dsattributes.kDSNAttrIMHandle,
-                ],
-         &quot;X-MSN&quot; : [
-                dsattributes.kDSNAttrIMHandle,
-                ],
-         &quot;X-YAHOO&quot; : [
-                dsattributes.kDSNAttrIMHandle,
-                ],
-         &quot;X-ICQ&quot; : [
-                dsattributes.kDSNAttrIMHandle,
-                ],
-         &quot;X-ABRELATEDNAMES&quot; : [
-                dsattributes.kDSNAttrRelationships,
-                ],
-          &quot;X-INTERNAL-MINIMUM-VCARD-PROPERTIES&quot; : [
-                dsattributes.kDS1AttrGeneratedUID,
-                dsattributes.kDSNAttrMetaNodeLocation,
-                dsattributes.kDS1AttrFirstName,
-                 dsattributes.kDS1AttrLastName,
-                dsattributes.kDS1AttrMiddleName,
-                   dsattributes.kDSNAttrNamePrefix,
-                  dsattributes.kDSNAttrNameSuffix,
-                 dsattributes.kDS1AttrDistinguishedName,
-                dsattributes.kDSNAttrRecordName,
-                dsattributes.kDSNAttrRecordType,
-                dsattributes.kDS1AttrModificationTimestamp,
-                dsattributes.kDS1AttrCreationTimestamp,
-                ],
-          &quot;X-INTERNAL-REQUIRED&quot; : [
-                dsattributes.kDS1AttrGeneratedUID,
-                dsattributes.kDSNAttrMetaNodeLocation,
-                 dsattributes.kDS1AttrDistinguishedName,
-                dsattributes.kDSNAttrRecordName,
-                dsattributes.kDS1AttrFirstName,
-                 dsattributes.kDS1AttrLastName,
-                dsattributes.kDSNAttrRecordType,
-                ],
-
-    }
-
-    allDSQueryAttributes = sorted(list(set([attr for lookupAttributes in dsqueryAttributesForProperty.values()
-                                      for attr in lookupAttributes])))
-
-    binaryDSAttributeStrs = [attr[0] for attr in allDSQueryAttributes
-                                if isinstance(attr, tuple)]
-
-    stringDSAttributeStrs = [attr for attr in allDSQueryAttributes
-                                if isinstance(attr, str)]
-
-    allDSAttributeStrs = stringDSAttributeStrs + binaryDSAttributeStrs
-
-    #peopleUIDSeparator = &quot;-&quot; + OpenDirectoryBackingService.baseGUID + &quot;-&quot;
-    userUIDSeparator = &quot;-bf07a1a2-&quot;
-    peopleUIDSeparator = &quot;-cf07a1a2-&quot;
-
-    constantProperties = {
-        # 3.6.3 PRODID Type Definition
-        &quot;PRODID&quot;: vCardProductID,
-        # 3.6.9 VERSION Type Definition
-        &quot;VERSION&quot;: &quot;3.0&quot;,
-        }
-
-
-    def __init__(self, service, recordAttributes, defaultNodeName=None):
-
-        self.log.debug(&quot;service=%s, attributes=%s&quot; % (service, recordAttributes))
-
-        #save off for debugging
-        if service.addDSAttrXProperties:
-            self.originalAttributes = recordAttributes.copy()
-
-        self.directoryBackedAddressBook = service.directoryBackedAddressBook
-        self._vCard = None
-        self._vCardText = None
-        self._uriName = None
-        self._hRef = None
-
-        self.attributes = {}
-        for key, values in recordAttributes.items():
-            if key in VCardRecord.stringDSAttributeStrs:
-                if isinstance(values, list):
-                    self.attributes[key] = [removeControlChars(val).decode(&quot;utf8&quot;) for val in values]
-                else:
-                    self.attributes[key] = removeControlChars(values).decode(&quot;utf8&quot;)
-            else:
-                self.attributes[key] = values
-
-        # fill in  missing essential attributes used for filtering
-        fullName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
-        if not fullName:
-            fullName = self.firstValueForAttribute(dsattributes.kDSNAttrRecordName)
-            self.attributes[dsattributes.kDS1AttrDistinguishedName] = fullName
-
-        node = self.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation)
-
-        # use a better node name -- makes better synthetic GUIDS
-        if not node or node == &quot;/LDAPv3/127.0.0.1&quot;:
-            node = defaultNodeName if defaultNodeName else service.realmName
-            self.attributes[dsattributes.kDSNAttrMetaNodeLocation] = node
-
-        guid = self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)
-        if not guid:
-            if service.standardizeSyntheticUIDs:
-                nodeUUIDStr = &quot;00000000&quot;
-            else:
-                nodeUUIDStr = &quot;%x&quot; % abs(hash(node))
-            nameUUIDStr = &quot;&quot;.join(self.firstValueForAttribute(dsattributes.kDSNAttrRecordName).encode(&quot;utf8&quot;).encode(&quot;base64&quot;).split(&quot;\n&quot;))
-            if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
-                guid = VCardRecord.userUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
-            else:
-                guid = VCardRecord.peopleUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
-
-        # since guid is used as file name, normalize so uid uniqueness == fine name uniqueness
-        #guid = &quot;/&quot;.join(guid.split(&quot;:&quot;)).upper()
-        self.attributes[dsattributes.kDS1AttrGeneratedUID] = guid
-
-        if self.firstValueForAttribute(dsattributes.kDS1AttrLastName) == &quot;99&quot;:
-            del self.attributes[dsattributes.kDS1AttrLastName]
-
-        if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
-            recordType = DirectoryService.recordType_users
-        else:
-            recordType = DirectoryService.recordType_people
-
-        super(VCardRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            guid=guid,
-            shortNames=tuple(self.valuesForAttribute(dsattributes.kDSNAttrRecordName)),
-            fullName=fullName,
-            firstName=self.firstValueForAttribute(dsattributes.kDS1AttrFirstName, None),
-            lastName=self.firstValueForAttribute(dsattributes.kDS1AttrLastName, None),
-            emailAddresses=(),
-            calendarUserAddresses=(),
-            autoSchedule=False,
-            enabledForCalendaring=False,
-        )
-
-
-    def __repr__(self):
-        return &quot;&lt;%s[%s(%s)] %s(%s) %r&gt;&quot; % (
-            self.__class__.__name__,
-            self.firstValueForAttribute(dsattributes.kDSNAttrRecordType),
-            self.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation),
-            self.guid,
-            self.shortNames,
-            self.fullName
-        )
-
-
-    def __hash__(self):
-        s = &quot;&quot;.join([
-              &quot;%s:%s&quot; % (attribute, self.valuesForAttribute(attribute),)
-              for attribute in self.attributes
-              ])
-        return hash(s)
-
-    &quot;&quot;&quot;
-    def nextFileName(self):
-        self.renameCounter += 1
-        self.fileName = self.baseFileName + &quot;-&quot; + str(self.renameCounter)
-        self.fileNameLower = self.fileName.lower()
-    &quot;&quot;&quot;
-
-    def hasAttribute(self, attributeName):
-        return self.valuesForAttribute(attributeName, None) is not None
-
-
-    def valuesForAttribute(self, attributeName, default_values=[]):
-        values = self.attributes.get(attributeName)
-        if (values is None):
-            return default_values
-        elif not isinstance(values, list):
-            values = [values, ]
-
-        # ds templates often return empty attribute values
-        #     get rid of them here
-        nonEmptyValues = [(value.encode(&quot;utf-8&quot;) if isinstance(value, unicode) else value) for value in values if len(value) &gt; 0]
-
-        if len(nonEmptyValues) &gt; 0:
-            return nonEmptyValues
-        else:
-            return default_values
-
-
-    def firstValueForAttribute(self, attributeName, default_value=&quot;&quot;):
-        values = self.attributes.get(attributeName)
-        if values is None:
-            return default_value
-        elif isinstance(values, list):
-            return values[0].encode(&quot;utf_8&quot;) if isinstance(values[0], unicode) else values[0]
-        else:
-            return values.encode(&quot;utf_8&quot;) if isinstance(values, unicode) else values
-
-
-    def joinedValuesForAttribute(self, attributeName, separator=&quot;,&quot;, default_string=&quot;&quot;):
-        values = self.valuesForAttribute(attributeName, None)
-        if not values:
-            return default_string
-        else:
-            return separator.join(values)
-
-
-    def isoDateStringForDateAttribute(self, attributeName, default_string=&quot;&quot;):
-        modDate = self.firstValueForAttribute(attributeName, default_string)
-        revDate = None
-        if modDate:
-            if len(modDate) &gt;= len(&quot;YYYYMMDD&quot;) and modDate[:8].isdigit():
-                revDate = &quot;%s-%s-%s&quot; % (modDate[:4], modDate[4:6], modDate[6:8],)
-            if len(modDate) &gt;= len(&quot;YYYYMMDDHHMMSS&quot;) and modDate[8:14].isdigit():
-                revDate += &quot;T%s:%s:%sZ&quot; % (modDate[8:10], modDate[10:12], modDate[12:14],)
-        return revDate
-
-
-    def vCard(self):
-
-
-        def generateVCard():
-
-            def isUniqueProperty(vcard, newProperty, ignoreParams=None):
-                existingProperties = vcard.properties(newProperty.name())
-                for existingProperty in existingProperties:
-                    if ignoreParams:
-                        existingProperty = existingProperty.duplicate()
-                        for paramname, paramvalue in ignoreParams:
-                            existingProperty.removeParameterValue(paramname, paramvalue)
-                    if existingProperty == newProperty:
-                        return False
-                return True
-
-            def addUniqueProperty(vcard, newProperty, ignoreParams=None, attrType=None, attrValue=None):
-                if isUniqueProperty(vcard, newProperty, ignoreParams):
-                    vcard.addProperty(newProperty)
-                else:
-                    if attrType and attrValue:
-                        self.log.info(&quot;Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists.&quot; % (attrType, attrValue, newProperty,))
-
-            def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters=None):
-                groupCount[0] += 1
-                groupPrefix = &quot;item%d&quot; % groupCount[0]
-                vcard.addProperty(Property(propertyName, propertyValue, params=parameters, group=groupPrefix))
-                vcard.addProperty(Property(&quot;X-ABLabel&quot;, label, group=groupPrefix))
-
-            # for attributes of the form  param:value
-            def addPropertiesAndLabelsForPrefixedAttribute(groupCount, propertyPrefix, propertyName, defaultLabel, nolabelParamTypes, labelMap, attrType):
-                preferred = True
-                for attrValue in self.valuesForAttribute(attrType):
-                    try:
-                        # special case for Apple
-                        if self.service.appleInternalServer and attrType == dsattributes.kDSNAttrIMHandle:
-                            splitValue = attrValue.split(&quot;|&quot;)
-                            if len(splitValue) &gt; 1:
-                                attrValue = splitValue[0]
-
-                        colonIndex = attrValue.find(&quot;:&quot;)
-                        if (colonIndex &gt; len(attrValue) - 2):
-                            raise ValueError(&quot;Nothing after colon.&quot;)
-
-                        propertyValue = attrValue[colonIndex + 1:]
-                        labelString = attrValue[:colonIndex] if colonIndex &gt; 0 else defaultLabel
-                        paramTypeString = labelString.upper()
-
-                        # add PREF to first prop's parameters
-                        paramTypeStrings = [paramTypeString, ]
-                        if preferred and &quot;PREF&quot; != paramTypeString:
-                            paramTypeStrings += [&quot;PREF&quot;, ]
-                        parameters = {&quot;TYPE&quot;: paramTypeStrings, }
-
-                        #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER
-                        if propertyPrefix:
-                            propertyName = propertyPrefix + paramTypeString
-
-                        # only add label prop if needed
-                        if paramTypeString in nolabelParamTypes:
-                            addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex + 1:], params=parameters), None, attrValue, attrType)
-                        else:
-                            # use special localizable addressbook labels where possible
-                            abLabelString = labelMap.get(labelString, labelString)
-                            addPropertyAndLabel(groupCount, abLabelString, propertyName, propertyValue, parameters)
-                        preferred = False
-
-                    except Exception, e:
-                        traceback.print_exc()
-                        self.log.debug(&quot;addPropertiesAndLabelsForPrefixedAttribute(): groupCount=%r, propertyPrefix=%r, propertyName=%r, nolabelParamTypes=%r, labelMap=%r, attrType=%r&quot; % (groupCount[0], propertyPrefix, propertyName, nolabelParamTypes, labelMap, attrType,))
-                        self.log.error(&quot;addPropertiesAndLabelsForPrefixedAttribute(): Trouble parsing attribute %s, with value \&quot;%s\&quot;.  Error = %s&quot; % (attrType, attrValue, e,))
-
-            #print(&quot;VCardRecord.vCard&quot;)
-            # create vCard
-            vcard = Component(&quot;VCARD&quot;)
-            groupCount = [0]
-
-            # add constant properties - properties that are the same regardless of the record attributes
-            for key, value in VCardRecord.constantProperties.items():
-                vcard.addProperty(Property(key, value))
-
-            # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
-            # 3.1.1 FN Type Definition
-            # dsattributes.kDS1AttrDistinguishedName,      # Users distinguished or real name
-            #
-            # full name is required but this is set in OpenDiretoryBackingRecord.__init__
-            #vcard.addProperty(Property(&quot;FN&quot;, self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)))
-
-            # 3.1.2 N Type Definition
-            # dsattributes.kDS1AttrFirstName,            # Used for first name of user or person record.
-            # dsattributes.kDS1AttrLastName,            # Used for the last name of user or person record.
-            # dsattributes.kDS1AttrMiddleName,            # Used for the middle name of user or person record.
-            # dsattributes.kDSNAttrNameSuffix,            # Represents the name suffix of a user or person.
-                                                        #      ie. Jr., Sr., etc.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        #      dsattributes.kDSStdRecordTypePeople).
-            # dsattributes.kDSNAttrNamePrefix,            # Represents the title prefix of a user or person.
-                                                        #      ie. Mr., Ms., Mrs., Dr., etc.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        #      dsattributes.kDSStdRecordTypePeople).
-
-            # name is required, so make sure we have one
-            # vcard says: Each name attribute can be a string or a list of strings.
-            if not self.hasAttribute(dsattributes.kDS1AttrFirstName) and not self.hasAttribute(dsattributes.kDS1AttrLastName):
-                familyName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
-            else:
-                familyName = self.valuesForAttribute(dsattributes.kDS1AttrLastName, &quot;&quot;)
-
-            nameObject = N(
-                first=self.valuesForAttribute(dsattributes.kDS1AttrFirstName, &quot;&quot;),
-                last=familyName,
-                middle=self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, &quot;&quot;),
-                prefix=self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, &quot;&quot;),
-                suffix=self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, &quot;&quot;),
-            )
-            vcard.addProperty(Property(&quot;N&quot;, nameObject))
-
-            # set full name to Name with contiguous spaces stripped
-            # it turns out that Address Book.app ignores FN and creates it fresh from N in ABRecord
-            # so no reason to have FN distinct from N
-            vcard.addProperty(Property(&quot;FN&quot;, nameObject.getFullName()))
-
-            # 3.1.3 NICKNAME Type Definition
-            # dsattributes.kDSNAttrNickName,            # Represents the nickname of a user or person.
-                                                        #    Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        #    dsattributes.kDSStdRecordTypePeople).
-            for nickname in self.valuesForAttribute(dsattributes.kDSNAttrNickName):
-                addUniqueProperty(vcard, Property(&quot;NICKNAME&quot;, nickname), None, dsattributes.kDSNAttrNickName, nickname)
-
-            # 3.1.4 PHOTO Type Definition
-            # dsattributes.kDSNAttrJPEGPhoto,            # Used to store binary picture data in JPEG format.
-                                                        #      Usually found in user, people or group records (kDSStdRecordTypeUsers,
-                                                        #      dsattributes.kDSStdRecordTypePeople,dsattributes.kDSStdRecordTypeGroups).
-            # pyOpenDirectory always returns binary-encoded string
-
-            for photo in self.valuesForAttribute(dsattributes.kDSNAttrJPEGPhoto):
-                addUniqueProperty(vcard, Property(&quot;PHOTO&quot;, photo, params={&quot;ENCODING&quot;: [&quot;b&quot;, ], &quot;TYPE&quot;: [&quot;JPEG&quot;, ], }), None, dsattributes.kDSNAttrJPEGPhoto, photo)
-
-            # 3.1.5 BDAY Type Definition
-            # dsattributes.kDS1AttrBirthday,            # Single-valued attribute that defines the user's birthday.
-                                                        #      Format is x.208 standard YYYYMMDDHHMMSSZ which we will require as GMT time.
-                                                        #                               012345678901234
-
-            birthdate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrBirthday)
-            if birthdate:
-                vcard.addProperty(Property(&quot;BDAY&quot;, DateTime.parseText(birthdate, fullISO=True)))
-
-            # 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
-            #
-            # 3.2.1 ADR Type Definition
-
-            #address
-            # vcard says: Each address attribute can be a string or a list of strings.
-            extended = self.valuesForAttribute(dsattributes.kDSNAttrBuilding, &quot;&quot;)
-            street = self.valuesForAttribute(dsattributes.kDSNAttrStreet, &quot;&quot;)
-            city = self.valuesForAttribute(dsattributes.kDSNAttrCity, &quot;&quot;)
-            region = self.valuesForAttribute(dsattributes.kDSNAttrState, &quot;&quot;)
-            code = self.valuesForAttribute(dsattributes.kDSNAttrPostalCode, &quot;&quot;)
-            country = self.valuesForAttribute(dsattributes.kDSNAttrCountry, &quot;&quot;)
-
-            if len(extended) &gt; 0 or len(street) &gt; 0 or len(city) &gt; 0 or len(region) &gt; 0 or len(code) &gt; 0 or len(country) &gt; 0:
-                vcard.addProperty(Property(&quot;ADR&quot;,
-                    Adr(
-                        #pobox = box,
-                        extended=extended,
-                        street=street,
-                        locality=city,
-                        region=region,
-                        postalcode=code,
-                        country=country,
-                    ),
-                    params={&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;POSTAL&quot;, &quot;PARCEL&quot;, ], }
-                ))
-
-            # 3.2.2 LABEL Type Definition
-
-            # dsattributes.kDSNAttrPostalAddress,            # The postal address usually excluding postal code.
-            # dsattributes.kDSNAttrPostalAddressContacts,    # multi-valued attribute that defines a record's alternate postal addresses .
-                                                            #      found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
-            # dsattributes.kDSNAttrAddressLine1,            # Line one of multiple lines of address data for a user.
-            # dsattributes.kDSNAttrAddressLine2,            # Line two of multiple lines of address data for a user.
-            # dsattributes.kDSNAttrAddressLine3,            # Line three of multiple lines of address data for a user.
-
-            for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddress):
-                addUniqueProperty(vcard, Property(&quot;LABEL&quot;, label, params={&quot;TYPE&quot;: [&quot;POSTAL&quot;, &quot;PARCEL&quot;, ]}), None, dsattributes.kDSNAttrPostalAddress, label)
-
-            for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddressContacts):
-                addUniqueProperty(vcard, Property(&quot;LABEL&quot;, label, params={&quot;TYPE&quot;: [&quot;POSTAL&quot;, &quot;PARCEL&quot;, ]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
-
-            address = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine1)
-            addressLine2 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine2)
-            if len(addressLine2) &gt; 0:
-                address += &quot;\n&quot; + addressLine2
-            addressLine3 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine3)
-            if len(addressLine3) &gt; 0:
-                address += &quot;\n&quot; + addressLine3
-
-            if len(address) &gt; 0:
-                vcard.addProperty(Property(&quot;LABEL&quot;, address, params={&quot;TYPE&quot;: [&quot;POSTAL&quot;, &quot;PARCEL&quot;, ]}))
-
-            # 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
-            # 3.3.1 TEL Type Definition
-            #          TEL;TYPE=work,voice,pref,msg:+1-213-555-1234
-
-            # dsattributes.kDSNAttrPhoneNumber,            # Telephone number of a user.
-            # dsattributes.kDSNAttrMobileNumber,        # Represents the mobile numbers of a user or person.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        #      dsattributes.kDSStdRecordTypePeople).
-            # dsattributes.kDSNAttrFaxNumber,            # Represents the FAX numbers of a user or person.
-                                                        # Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        # kDSStdRecordTypePeople).
-            # dsattributes.kDSNAttrPagerNumber,            # Represents the pager numbers of a user or person.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
-                                                        #      dsattributes.kDSStdRecordTypePeople).
-            # dsattributes.kDSNAttrHomePhoneNumber,        # Home telephone number of a user or person.
-            # dsattributes.kDSNAttrPhoneContacts,        # multi-valued attribute that defines a record's custom phone numbers .
-                                                        #      found in user records (kDSStdRecordTypeUsers).
-                                                        #      Example: home fax:408-555-4444
-
-            params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;VOICE&quot;, ], }
-            for phone in self.valuesForAttribute(dsattributes.kDSNAttrPhoneNumber):
-                addUniqueProperty(vcard, Property(&quot;TEL&quot;, phone, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), phone, dsattributes.kDSNAttrPhoneNumber)
-                params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;VOICE&quot;, ], }
-
-            params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;CELL&quot;, ], }
-            for phone in self.valuesForAttribute(dsattributes.kDSNAttrMobileNumber):
-                addUniqueProperty(vcard, Property(&quot;TEL&quot;, phone, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), phone, dsattributes.kDSNAttrMobileNumber)
-                params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;CELL&quot;, ], }
-
-            params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;FAX&quot;, ], }
-            for phone in self.valuesForAttribute(dsattributes.kDSNAttrFaxNumber):
-                addUniqueProperty(vcard, Property(&quot;TEL&quot;, phone, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), phone, dsattributes.kDSNAttrFaxNumber)
-                params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;FAX&quot;, ], }
-
-            params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;PAGER&quot;, ], }
-            for phone in self.valuesForAttribute(dsattributes.kDSNAttrPagerNumber):
-                addUniqueProperty(vcard, Property(&quot;TEL&quot;, phone, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), phone, dsattributes.kDSNAttrPagerNumber)
-                params = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PAGER&quot;, ], }
-
-            params = {&quot;TYPE&quot;: [&quot;HOME&quot;, &quot;PREF&quot;, &quot;VOICE&quot;, ], }
-            for phone in self.valuesForAttribute(dsattributes.kDSNAttrHomePhoneNumber):
-                addUniqueProperty(vcard, Property(&quot;TEL&quot;, phone, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), phone, dsattributes.kDSNAttrHomePhoneNumber)
-                params = {&quot;TYPE&quot;: [&quot;HOME&quot;, &quot;VOICE&quot;, ], }
-
-            addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, &quot;TEL&quot;, &quot;work&quot;,
-                                                        [&quot;VOICE&quot;, &quot;CELL&quot;, &quot;FAX&quot;, &quot;PAGER&quot;, ], {},
-                                                        dsattributes.kDSNAttrPhoneContacts,)
-
-            &quot;&quot;&quot;
-            # EXTEND:  Use this attribute
-            # dsattributes.kDSNAttrAreaCode,            # Area code of a user's phone number.
-            &quot;&quot;&quot;
-
-            # 3.3.2 EMAIL Type Definition
-            # dsattributes.kDSNAttrEMailAddress,        # Email address of usually a user record.
-
-            # setup some params
-            preferredWorkParams = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;PREF&quot;, &quot;INTERNET&quot;, ], }
-            workParams = {&quot;TYPE&quot;: [&quot;WORK&quot;, &quot;INTERNET&quot;, ], }
-            params = preferredWorkParams
-            for emailAddress in self.valuesForAttribute(dsattributes.kDSNAttrEMailAddress):
-                addUniqueProperty(vcard, Property(&quot;EMAIL&quot;, emailAddress, params=params), ((&quot;TYPE&quot;, &quot;PREF&quot;),), emailAddress, dsattributes.kDSNAttrEMailAddress)
-                params = workParams
-
-            # dsattributes.kDSNAttrEMailContacts,        # multi-valued attribute that defines a record's custom email addresses .
-                                                        #    found in user records (kDSStdRecordTypeUsers).
-                                                        #      Example: home:johndoe@mymail.com
-
-            # check to see if parameters type are open ended. Could be any string
-            addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, &quot;EMAIL&quot;, &quot;work&quot;,
-                                                        [&quot;WORK&quot;, &quot;HOME&quot;, ], {},
-                                                        dsattributes.kDSNAttrEMailContacts,)
-
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED:
-            # 3.3.3 MAILER Type Definition
-            &quot;&quot;&quot;
-            # 3.4 GEOGRAPHICAL TYPES http://tools.ietf.org/html/rfc2426#section-3.4
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED:
-            # 3.4.1 TZ Type Definition
-            &quot;&quot;&quot;
-            # 3.4.2 GEO Type Definition
-            #dsattributes.kDSNAttrMapCoordinates,        # attribute that defines coordinates for a user's location .
-                                                        #      Found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
-                                                        #      Example: 7.7,10.6
-            for coordinate in self.valuesForAttribute(dsattributes.kDSNAttrMapCoordinates):
-                parts = coordinate.split(&quot;,&quot;)
-                if (len(parts) == 2):
-                    vcard.addProperty(Property(&quot;GEO&quot;, parts))
-                else:
-                    self.log.info(&quot;Ignoring malformed attribute %r with value %r. Well-formed example: 7.7,10.6.&quot; % (dsattributes.kDSNAttrMapCoordinates, coordinate))
-            #
-            # 3.5 ORGANIZATIONAL TYPES http://tools.ietf.org/html/rfc2426#section-3.5
-            #
-            # 3.5.1 TITLE Type Definition
-            for jobTitle in self.valuesForAttribute(dsattributes.kDSNAttrJobTitle):
-                addUniqueProperty(vcard, Property(&quot;TITLE&quot;, jobTitle), None, dsattributes.kDSNAttrJobTitle, jobTitle)
-
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED:
-            # 3.5.2 ROLE Type Definition
-            # 3.5.3 LOGO Type Definition
-            # 3.5.4 AGENT Type Definition
-            &quot;&quot;&quot;
-            # 3.5.5 ORG Type Definition
-            company = self.joinedValuesForAttribute(dsattributes.kDSNAttrCompany)
-            if len(company) == 0:
-                company = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationName)
-            department = self.joinedValuesForAttribute(dsattributes.kDSNAttrDepartment)
-            extra = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationInfo)
-            if len(company) &gt; 0 or len(department) &gt; 0:
-                vcard.addProperty(Property(&quot;ORG&quot;, (company, department, extra,),))
-
-            # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED:
-            # 3.6.1 CATEGORIES Type Definition
-            &quot;&quot;&quot;
-            # 3.6.2 NOTE Type Definition
-            # dsattributes.kDS1AttrComment,                  # Attribute used for unformatted comment.
-            # dsattributes.kDS1AttrNote,                  # Note attribute. Commonly used in printer records.
-            for comment in self.valuesForAttribute(dsattributes.kDS1AttrComment):
-                addUniqueProperty(vcard, Property(&quot;NOTE&quot;, comment), None, dsattributes.kDS1AttrComment, comment)
-
-            for note in self.valuesForAttribute(dsattributes.kDS1AttrNote):
-                addUniqueProperty(vcard, Property(&quot;NOTE&quot;, note), None, dsattributes.kDS1AttrNote, note)
-
-            # 3.6.3 PRODID Type Definition
-            #vcard.addProperty(Property(&quot;PRODID&quot;, vCardProductID + &quot;//BUILD %s&quot; % twistedcaldav.__version__))
-            #vcard.addProperty(Property(&quot;PRODID&quot;, vCardProductID))
-            # ADDED WITH CONTSTANT PROPERTIES
-
-            # 3.6.4 REV Type Definition
-            revDate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrModificationTimestamp)
-            if revDate:
-                vcard.addProperty(Property(&quot;REV&quot;, DateTime.parseText(revDate, fullISO=True)))
-
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED:
-            # 3.6.5 SORT-STRING Type Definition
-            # 3.6.6 SOUND Type Definition
-            &quot;&quot;&quot;
-            # 3.6.7 UID Type Definition
-            # dsattributes.kDS1AttrGeneratedUID,        # Used for 36 character (128 bit) unique ID. Usually found in user,
-                                                        #      group, and computer records. An example value is &quot;A579E95E-CDFE-4EBC-B7E7-F2158562170F&quot;.
-                                                        #      The standard format contains 32 hex characters and four hyphen characters.
-            # !! don't use self.guid which is URL encoded
-            vcard.addProperty(Property(&quot;UID&quot;, self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)))
-
-            # 3.6.8 URL Type Definition
-            # dsattributes.kDSNAttrURL,                    # List of URLs.
-            # dsattributes.kDS1AttrWeblogURI,            # Single-valued attribute that defines the URI of a user's weblog.
-                                                        #     Usually found in user records (kDSStdRecordTypeUsers).
-                                                        #      Example: http://example.com/blog/jsmith
-            for url in self.valuesForAttribute(dsattributes.kDS1AttrWeblogURI):
-                addPropertyAndLabel(groupCount, &quot;weblog&quot;, &quot;URL&quot;, url, parameters={&quot;TYPE&quot;: [&quot;Weblog&quot;, ]})
-
-            for url in self.valuesForAttribute(dsattributes.kDSNAttrURL):
-                addPropertyAndLabel(groupCount, &quot;_$!&lt;HomePage&gt;!$_&quot;, &quot;URL&quot;, url, parameters={&quot;TYPE&quot;: [&quot;Homepage&quot;, ]})
-
-            # 3.6.9 VERSION Type Definition
-            # ALREADY ADDED
-
-            # 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
-            # 3.7.1 CLASS Type Definition
-            # ALREADY ADDED
-
-            # 3.7.2 KEY Type Definition
-
-            # dsattributes.kDSNAttrPGPPublicKey,        # Pretty Good Privacy public encryption key.
-            # dsattributes.kDS1AttrUserCertificate,        # Attribute containing the binary of the user's certificate.
-                                                        #       Usually found in user records. The certificate is data which identifies a user.
-                                                        #       This data is attested to by a known party, and can be independently verified
-                                                        #       by a third party.
-            # dsattributes.kDS1AttrUserPKCS12Data,        # Attribute containing binary data in PKCS #12 format.
-                                                        #       Usually found in user records. The value can contain keys, certificates,
-                                                        #      and other related information and is encrypted with a passphrase.
-            # dsattributes.kDS1AttrUserSMIMECertificate,# Attribute containing the binary of the user's SMIME certificate.
-                                                        #       Usually found in user records. The certificate is data which identifies a user.
-                                                        #       This data is attested to by a known party, and can be independently verified
-                                                        #       by a third party. SMIME certificates are often used for signed or encrypted
-                                                        #       emails.
-
-            for key in self.valuesForAttribute(dsattributes.kDSNAttrPGPPublicKey):
-                addUniqueProperty(vcard, Property(&quot;KEY&quot;, key, params={&quot;ENCODING&quot;: [&quot;b&quot;, ], &quot;TYPE&quot;: [&quot;PGPPublicKey&quot;, ]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
-
-            for key in self.valuesForAttribute(dsattributes.kDS1AttrUserCertificate):
-                addUniqueProperty(vcard, Property(&quot;KEY&quot;, key, params={&quot;ENCODING&quot;: [&quot;b&quot;, ], &quot;TYPE&quot;: [&quot;UserCertificate&quot;, ]}), None, dsattributes.kDS1AttrUserCertificate, key)
-
-            for key in self.valuesForAttribute(dsattributes.kDS1AttrUserPKCS12Data):
-                addUniqueProperty(vcard, Property(&quot;KEY&quot;, key, params={&quot;ENCODING&quot;: [&quot;b&quot;, ], &quot;TYPE&quot;: [&quot;UserPKCS12Data&quot;, ]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
-
-            for key in self.valuesForAttribute(dsattributes.kDS1AttrUserSMIMECertificate):
-                addUniqueProperty(vcard, Property(&quot;KEY&quot;, key, params={&quot;ENCODING&quot;: [&quot;b&quot;, ], &quot;TYPE&quot;: [&quot;UserSMIMECertificate&quot;, ]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
-
-            &quot;&quot;&quot;
-            X- attributes, Address Book support
-            &quot;&quot;&quot;
-            # X-AIM, X-JABBER, X-MSN, X-YAHOO, X-ICQ
-            # instant messaging
-            # dsattributes.kDSNAttrIMHandle,            # Represents the Instant Messaging handles of a user.
-                                                        #      Values should be prefixed with the appropriate IM type
-                                                        #       ie. AIM:, Jabber:, MSN:, Yahoo:, or ICQ:
-                                                        #       Usually found in user records (kDSStdRecordTypeUsers).
-
-            addPropertiesAndLabelsForPrefixedAttribute(groupCount, &quot;X-&quot;, None, &quot;aim&quot;,
-                                                        [&quot;AIM&quot;, &quot;JABBER&quot;, &quot;MSN&quot;, &quot;YAHOO&quot;, &quot;ICQ&quot;],
-                                                        {},
-                                                        dsattributes.kDSNAttrIMHandle,)
-
-            # X-ABRELATEDNAMES
-            # dsattributes.kDSNAttrRelationships,        #      multi-valued attribute that defines the relationship to the record type .
-                                                        #      found in user records (kDSStdRecordTypeUsers).
-                                                        #      Example: brother:John
-            addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, &quot;X-ABRELATEDNAMES&quot;, &quot;friend&quot;,
-                                                        [],
-                                                        {&quot;FATHER&quot;: &quot;_$!&lt;Father&gt;!$_&quot;,
-                                                         &quot;MOTHER&quot;: &quot;_$!&lt;Mother&gt;!$_&quot;,
-                                                         &quot;PARENT&quot;: &quot;_$!&lt;Parent&gt;!$_&quot;,
-                                                         &quot;BROTHER&quot;: &quot;_$!&lt;Brother&gt;!$_&quot;,
-                                                         &quot;SISTER&quot;: &quot;_$!&lt;Sister&gt;!$_&quot;,
-                                                         &quot;CHILD&quot;: &quot;_$!&lt;Child&gt;!$_&quot;,
-                                                         &quot;FRIEND&quot;: &quot;_$!&lt;Friend&gt;!$_&quot;,
-                                                         &quot;SPOUSE&quot;: &quot;_$!&lt;Spouse&gt;!$_&quot;,
-                                                         &quot;PARTNER&quot;: &quot;_$!&lt;Partner&gt;!$_&quot;,
-                                                         &quot;ASSISTANT&quot;: &quot;_$!&lt;Assistant&gt;!$_&quot;,
-                                                         &quot;MANAGER&quot;: &quot;_$!&lt;Manager&gt;!$_&quot;, },
-                                                        dsattributes.kDSNAttrRelationships,)
-
-            # special case for Apple
-            if self.service.appleInternalServer:
-                for manager in self.valuesForAttribute(&quot;dsAttrTypeNative:appleManager&quot;):
-                    splitManager = manager.split(&quot;|&quot;)
-                    if len(splitManager) &gt;= 4:
-                        managerValue = &quot;%s %s, %s&quot; % (splitManager[0], splitManager[1], splitManager[3],)
-                    elif len(splitManager) &gt;= 2:
-                        managerValue = &quot;%s %s&quot; % (splitManager[0], splitManager[1])
-                    else:
-                        managerValue = manager
-                    addPropertyAndLabel(groupCount, &quot;_$!&lt;Manager&gt;!$_&quot;, &quot;X-ABRELATEDNAMES&quot;, managerValue, parameters={&quot;TYPE&quot;: [&quot;Manager&quot;, ]})
-
-            &quot;&quot;&quot;
-            # UNIMPLEMENTED: X- attributes
-
-            X-MAIDENNAME
-            X-PHONETIC-FIRST-NAME
-            X-PHONETIC-MIDDLE-NAME
-            X-PHONETIC-LAST-NAME
-
-            sattributes.kDS1AttrPicture,                # Represents the path of the picture for each user displayed in the login window.
-                                                        #      Found in user records (kDSStdRecordTypeUsers).
-
-            dsattributes.kDS1AttrMapGUID,                # Represents the GUID for a record's map.
-            dsattributes.kDSNAttrMapURI,                # attribute that defines the URI of a user's location.
-
-            dsattributes.kDSNAttrOrganizationInfo,        # Usually the organization info of a user.
-            dsattributes.kDSNAttrAreaCode,                # Area code of a user's phone number.
-
-            dsattributes.kDSNAttrMIME,                    # Data contained in this attribute type is a fully qualified MIME Type.
-
-            &quot;&quot;&quot;
-
-            # debug, create x attributes for all ds attributes
-            if self.service.addDSAttrXProperties:
-                for attribute in self.originalAttributes:
-                    for value in self.valuesForAttribute(attribute):
-                        vcard.addProperty(Property(&quot;X-&quot; + &quot;-&quot;.join(attribute.split(&quot;:&quot;)), removeControlChars(value)))
-
-            return vcard
-
-        if not self._vCard:
-            self._vCard = generateVCard()
-
-        return self._vCard
-
-
-    def vCardText(self):
-        if not self._vCardText:
-            self._vCardText = str(self.vCard())
-
-        return self._vCardText
-
-
-    def uriName(self):
-        if not self._uriName:
-            self._uriName = self.vCard().getProperty(&quot;UID&quot;).value() + &quot;.vcf&quot;
-        #print(&quot;uriName():self._uriName=%s&quot; % self._uriName)
-        return self._uriName
-
-
-    def hRef(self, parentURI=&quot;/directory/&quot;):
-        if not self._hRef:
-            self._hRef = davxml.HRef.fromString(joinURL(parentURI, self.uriName()))
-
-        return self._hRef
-
-
-    def readProperty(self, property, request):
-
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
-
-        namespace, name = qname
-
-        #print(&quot;VCardResource.readProperty: qname = %s&quot; % (qname, ))
-
-        if namespace == dav_namespace:
-            if name == &quot;resourcetype&quot;:
-                result = davxml.ResourceType.empty #@UndefinedVariable
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;getetag&quot;:
-                result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;getcontenttype&quot;:
-                mimeType = MimeType('text', 'vcard', {})
-                result = davxml.GETContentType(generateContentType(mimeType))
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;getcontentlength&quot;:
-                result = davxml.GETContentLength.fromString(str(len(self.vCardText())))
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;getlastmodified&quot;:
-                if self.vCard().hasProperty(&quot;REV&quot;):
-                    modDatetime = parse_date(self.vCard().propertyValue(&quot;REV&quot;))
-                else:
-                    # use creation date attribute if it exists
-                    creationDateString = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrCreationTimestamp)
-                    if creationDateString:
-                        modDatetime = parse_date(creationDateString)
-                    else:
-                        modDatetime = datetime.datetime.utcnow()
-
-                #strip time zone because time zones are unimplemented in davxml.GETLastModified.fromDate
-                d = modDatetime.date()
-                t = modDatetime.time()
-                modDatetimeNoTZ = datetime.datetime(d.year, d.month, d.day, t.hour, t.minute, t.second, t.microsecond, None)
-                result = davxml.GETLastModified.fromDate(modDatetimeNoTZ)
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;creationdate&quot;:
-                creationDateString = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrCreationTimestamp)
-                if creationDateString:
-                    creationDatetime = parse_date(creationDateString)
-                elif self.vCard().hasProperty(&quot;REV&quot;):    # use modification date property if it exists
-                    creationDatetime = parse_date(self.vCard().propertyValue(&quot;REV&quot;))
-                else:
-                    creationDatetime = datetime.datetime.utcnow()
-                result = davxml.CreationDate.fromDate(creationDatetime)
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-            elif name == &quot;displayname&quot;:
-                # AddressBook.app uses N. Use FN or UID instead?
-                result = davxml.DisplayName.fromString(self.vCard().propertyValue(&quot;N&quot;))
-                #print(&quot;VCardResource.readProperty: qname = %s, result = %s&quot; % (qname, result))
-                return result
-
-        elif namespace == twisted_dav_namespace:
-            return super(VCardRecord, self).readProperty(property, request)
-            #return DAVPropertyMixIn.readProperty(self, property, request)
-
-        return self.directoryBackedAddressBook.readProperty(property, request)
-
-
-    def listProperties(self, request):
-        #print(&quot;VCardResource.listProperties()&quot;)
-        qnames = set(self.liveProperties())
-
-        # Add dynamic live properties that exist
-        dynamicLiveProperties = (
-            (dav_namespace, &quot;quota-available-bytes&quot;),
-            (dav_namespace, &quot;quota-used-bytes&quot;),
-        )
-        for dqname in dynamicLiveProperties:
-            #print(&quot;VCardResource.listProperties: removing dqname=%s&quot; % (dqname,))
-            qnames.remove(dqname)
-
-        for qname in self.deadProperties().list():
-            if (qname not in qnames) and (qname[0] != twisted_private_namespace):
-                #print(&quot;listProperties: adding qname=%s&quot; % (qname,))
-                qnames.add(qname)
-
-        #for qn in qnames: print(&quot;VCardResource.listProperties: qn=%s&quot; % (qn,))
-
-        yield qnames
-
-    listProperties = deferredGenerator(listProperties)
-
-
-
-# utility
-#remove control characters because vCard does not support them
-def removeControlChars(utf8String):
-    result = utf8String
-    for a in utf8String:
-        if '\x00' &lt;= a &lt;= '\x1F':
-            result = &quot;&quot;
-            for c in utf8String:
-                if '\x00' &lt;= c &lt;= '\x1F':
-                    pass
-                else:
-                    result += c
-    #if utf8String != result: print (&quot;changed %r to %r&quot; % (utf8String, result))
-    return result
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryprincipalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/principal.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/principal.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/principal.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -28,55 +28,55 @@
</span><span class="cx">     &quot;DirectoryCalendarPrincipalResource&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-from urllib import unquote
</del><ins>+from urllib import quote, unquote
</ins><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
-from twistedcaldav.directory.wiki import getWikiACL
</del><ins>+from twistedcaldav.directory.util import NotFoundResource
+from twistedcaldav.directory.util import (
+    formatLink, formatLinks, formatPrincipals, formatList
+)
+from txdav.who.wiki import getWikiACL
+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.delegates import RecordType as DelegateRecordType
+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="cx"> 
</span><span class="cx"> class PermissionsMixIn (ReadOnlyResourceMixIn):
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return authReadACL
</del><ins>+        return succeed(authReadACL)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -94,7 +94,7 @@
</span><span class="cx">         else:
</span><span class="cx">             # ...otherwise permissions are fixed, and are not subject to
</span><span class="cx">             # inheritance rules, etc.
</span><del>-            returnValue(self.defaultAccessControlList())
</del><ins>+            returnValue((yield self.defaultAccessControlList()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -108,7 +108,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">@@ -118,7 +118,7 @@
</span><span class="cx">     cua = normalizeCUAddr(origCUAddr)
</span><span class="cx"> 
</span><span class="cx">     if cua.startswith(&quot;urn:uuid:&quot;):
</span><del>-        return &quot;guid&quot;, cua[9:]
</del><ins>+        return &quot;guid&quot;, uuid.UUID(cua[9:])
</ins><span class="cx"> 
</span><span class="cx">     elif cua.startswith(&quot;mailto:&quot;):
</span><span class="cx">         return &quot;emailAddresses&quot;, cua[7:]
</span><span class="lines">@@ -126,7 +126,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">@@ -150,18 +150,21 @@
</span><span class="cx">         CalendarPrincipalCollectionResource.__init__(self, url)
</span><span class="cx">         DAVResourceWithChildrenMixin.__init__(self)
</span><span class="cx"> 
</span><del>-        self.directory = IDirectoryService(directory)
</del><ins>+        # MOVE2WHO
+        # self.directory = IDirectoryService(directory)
+        self.directory = directory
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><span class="cx">         return &quot;&lt;%s: %s %s&gt;&quot; % (self.__class__.__name__, self.directory, self._url)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def locateChild(self, req, segments):
</span><del>-        child = self.getChild(segments[0])
</del><ins>+        child = (yield self.getChild(segments[0]))
</ins><span class="cx">         if child is not None:
</span><del>-            return (child, segments[1:])
-        return (NotFoundResource(principalCollections=self.principalCollections()), ())
</del><ins>+            returnValue((child, segments[1:]))
+        returnValue((NotFoundResource(principalCollections=self.principalCollections()), ()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def deadProperties(self):
</span><span class="lines">@@ -174,27 +177,30 @@
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForShortName(self, recordType, name):
</span><del>-        return self.principalForRecord(self.directory.recordWithShortName(recordType, name))
</del><ins>+        record = (yield self.directory.recordWithShortName(recordType, name))
+        returnValue((yield self.principalForRecord(record)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalForUser(self, user):
</span><del>-        return self.principalForShortName(DirectoryService.recordType_users, user)
</del><ins>+        return self.principalForShortName(self.directory.recordType.lookupByName(&quot;user&quot;), user)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForAuthID(self, user):
</span><span class="cx">         # Basic/Digest creds -&gt; just lookup user name
</span><span class="cx">         if isinstance(user, UsernamePassword) or isinstance(user, DigestedCredentials):
</span><del>-            return self.principalForUser(user.username)
</del><ins>+            returnValue((yield self.principalForUser(user.username)))
</ins><span class="cx">         elif NegotiateCredentials is not None and isinstance(user, NegotiateCredentials):
</span><span class="cx">             authID = &quot;Kerberos:%s&quot; % (user.principal,)
</span><del>-            principal = self.principalForRecord(self.directory.recordWithAuthID(authID))
</del><ins>+            principal = yield self.principalForRecord((yield self.directory.recordWithAuthID(authID)))
</ins><span class="cx">             if principal:
</span><del>-                return principal
</del><ins>+                returnValue(principal)
</ins><span class="cx">             elif user.username:
</span><del>-                return self.principalForUser(user.username)
</del><ins>+                returnValue((yield self.principalForUser(user.username)))
</ins><span class="cx"> 
</span><del>-        return None
</del><ins>+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalForUID(self, uid):
</span><span class="lines">@@ -207,7 +213,7 @@
</span><span class="cx"> 
</span><span class="cx">     def principalForRecord(self, record):
</span><span class="cx">         if record is None or not record.enabled:
</span><del>-            return None
</del><ins>+            return succeed(None)
</ins><span class="cx">         return self.principalForUID(record.uid)
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -217,7 +223,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">@@ -281,16 +287,36 @@
</span><span class="cx">         #
</span><span class="cx">         # Create children
</span><span class="cx">         #
</span><del>-        for recordType in self.directory.recordTypes():
-            self.putChild(recordType, DirectoryPrincipalTypeProvisioningResource(self, recordType))
</del><span class="cx"> 
</span><ins>+        self.supportedChildTypes = (
+            self.directory.recordType.user,
+            self.directory.recordType.group,
+            self.directory.recordType.location,
+            self.directory.recordType.resource,
+            self.directory.recordType.address,
+        )
+
+        for name, recordType in [
+            (self.directory.recordTypeToOldName(r), r)
+            for r in self.supportedChildTypes
+        ]:
+            self.putChild(
+                name,
+                DirectoryPrincipalTypeProvisioningResource(
+                    self, name, recordType
+                )
+            )
+
</ins><span class="cx">         self.putChild(uidsResourceName, DirectoryPrincipalUIDProvisioningResource(self))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForUID(self, uid):
</span><del>-        return self.getChild(uidsResourceName).getChild(uid)
</del><ins>+        child = (yield self.getChild(uidsResourceName))
+        returnValue((yield child.getChild(uid)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _principalForURI(self, uri):
</span><span class="cx">         scheme, netloc, path, _ignore_params, _ignore_query, _ignore_fragment = urlparse(uri)
</span><span class="cx"> 
</span><span class="lines">@@ -312,56 +338,62 @@
</span><span class="cx"> 
</span><span class="cx">             if (host != config.ServerHostName and
</span><span class="cx">                 host not in config.Scheduling.Options.PrincipalHostAliases):
</span><del>-                return None
</del><ins>+                returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">             if port != {
</span><span class="cx">                 &quot;http&quot; : config.HTTPPort,
</span><span class="cx">                 &quot;https&quot;: config.SSLPort,
</span><span class="cx">             }[scheme]:
</span><del>-                return None
</del><ins>+                returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         elif scheme == &quot;urn&quot;:
</span><span class="cx">             if path.startswith(&quot;uuid:&quot;):
</span><del>-                return self.principalForUID(path[5:])
</del><ins>+                returnValue((yield self.principalForUID(path[5:])))
</ins><span class="cx">             else:
</span><del>-                return None
</del><ins>+                returnValue(None)
</ins><span class="cx">         else:
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         if not path.startswith(self._url):
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         path = path[len(self._url) - 1:]
</span><span class="cx"> 
</span><span class="cx">         segments = [unquote(s) for s in path.rstrip(&quot;/&quot;).split(&quot;/&quot;)]
</span><span class="cx">         if segments[0] == &quot;&quot; and len(segments) == 3:
</span><del>-            typeResource = self.getChild(segments[1])
</del><ins>+            typeResource = yield self.getChild(segments[1])
</ins><span class="cx">             if typeResource is not None:
</span><del>-                principalResource = typeResource.getChild(segments[2])
</del><ins>+                principalResource = yield typeResource.getChild(segments[2])
</ins><span class="cx">                 if principalResource:
</span><del>-                    return principalResource
</del><ins>+                    returnValue(principalResource)
</ins><span class="cx"> 
</span><del>-        return None
</del><ins>+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForCalendarUserAddress(self, address):
</span><span class="cx">         # First see if the address is a principal URI
</span><del>-        principal = self._principalForURI(address)
</del><ins>+        principal = yield self._principalForURI(address)
</ins><span class="cx">         if principal:
</span><del>-            if isinstance(principal, DirectoryCalendarPrincipalResource) and principal.record.enabledForCalendaring:
-                return principal
</del><ins>+            if (
+                isinstance(principal, DirectoryCalendarPrincipalResource) and
+                principal.record.hasCalendars
+            ):
+                returnValue(principal)
</ins><span class="cx">         else:
</span><span class="cx">             # Next try looking it up in the directory
</span><del>-            record = self.directory.recordWithCalendarUserAddress(address)
-            if record is not None and record.enabled and record.enabledForCalendaring:
-                return self.principalForRecord(record)
</del><ins>+            record = yield self.directory.recordWithCalendarUserAddress(address)
+            if record is not None and record.hasCalendars:
+                returnValue((yield self.principalForRecord(record)))
</ins><span class="cx"> 
</span><span class="cx">         log.debug(&quot;No principal for calendar user address: %r&quot; % (address,))
</span><del>-        return None
</del><ins>+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForRecord(self, record):
</span><del>-        return self.getChild(uidsResourceName).principalForRecord(record)
</del><ins>+        child = (yield self.getChild(uidsResourceName))
+        returnValue((yield child.principalForRecord(record)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -375,13 +407,16 @@
</span><span class="cx"> 
</span><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            return succeed(self)
</ins><span class="cx">         else:
</span><del>-            return self.putChildren.get(name, None)
</del><ins>+            return succeed(self.putChildren.get(name, None))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><del>-        return self.directory.recordTypes()
</del><ins>+        return [
+            self.directory.recordTypeToOldName(r) for r in
+            self.supportedChildTypes
+        ]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     ##
</span><span class="lines">@@ -392,43 +427,20 @@
</span><span class="cx">         return (self,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    ##
-    # Proxy callback from directory service
-    ##
</del><span class="cx"> 
</span><del>-    def isProxyFor(self, record1, record2):
-        &quot;&quot;&quot;
-        Test whether the principal identified by directory record1 is a proxy for the principal identified by
-        record2.
-
-        @param record1: directory record for a user
-        @type record1: L{DirectoryRecord}
-        @param record2: directory record to test with
-        @type record2: L{DirectoryRercord}
-
-        @return: C{True} if record1 is a proxy for record2, otherwise C{False}
-        @rtype: C{bool}
-        &quot;&quot;&quot;
-
-        principal1 = self.principalForUID(record1.uid)
-        principal2 = self.principalForUID(record2.uid)
-        return principal1.isProxyFor(principal2)
-
-
-
</del><span class="cx"> class DirectoryPrincipalTypeProvisioningResource (DirectoryProvisioningResource):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Collection resource which provisions directory principals of a
</span><span class="cx">     specific type as its children, indexed by short name.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, parent, recordType):
</del><ins>+    def __init__(self, parent, name, recordType):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param parent: the parent L{DirectoryPrincipalProvisioningResource}.
</span><span class="cx">         @param recordType: the directory record type to provision.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryProvisioningResource.__init__(
</span><span class="cx">             self,
</span><del>-            joinURL(parent.principalCollectionURL(), recordType) + &quot;/&quot;,
</del><ins>+            joinURL(parent.principalCollectionURL(), name) + &quot;/&quot;,
</ins><span class="cx">             parent.directory
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -459,22 +471,26 @@
</span><span class="cx"> 
</span><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            return succeed(self)
</ins><span class="cx">         else:
</span><span class="cx">             return self.principalForShortName(self.recordType, name)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def listChildren(self):
</span><ins>+        children = []
</ins><span class="cx">         if config.EnablePrincipalListings:
</span><ins>+            try:
+                for record in (
+                    yield self.directory.recordsWithRecordType(self.recordType)
+                ):
+                    for shortName in record.shortNames:
+                        children.append(shortName)
+            except AttributeError:
+                log.warn(&quot;Cannot list children of record type {rt}&quot;,
+                         rt=self.recordType.name)
+            returnValue(children)
</ins><span class="cx"> 
</span><del>-
-            def _recordShortnameExpand():
-                for record in self.directory.listRecords(self.recordType):
-                    if record.enabled:
-                        for shortName in record.shortNames:
-                            yield shortName
-
-            return _recordShortnameExpand()
</del><span class="cx">         else:
</span><span class="cx">             # Not a listable collection
</span><span class="cx">             raise HTTPError(responsecode.FORBIDDEN)
</span><span class="lines">@@ -517,16 +533,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalForRecord(self, record):
</span><del>-        if record is None or not record.enabled:
-            return None
</del><ins>+        if record is None:
+            return succeed(None)
</ins><span class="cx"> 
</span><del>-        if record.enabledForCalendaring or record.enabledForAddressBooks:
</del><ins>+        if record.hasCalendars or record.hasContacts:
</ins><span class="cx">             # XXX these are different features and one should not automatically
</span><span class="cx">             # imply the other...
</span><span class="cx">             principal = DirectoryCalendarPrincipalResource(self, record)
</span><span class="cx">         else:
</span><span class="cx">             principal = DirectoryPrincipalResource(self, record)
</span><del>-        return principal
</del><ins>+        return succeed(principal)
</ins><span class="cx"> 
</span><span class="cx">     ##
</span><span class="cx">     # Static
</span><span class="lines">@@ -538,9 +554,10 @@
</span><span class="cx">         raise HTTPError(responsecode.NOT_FOUND)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            returnValue(self)
</ins><span class="cx"> 
</span><span class="cx">         if &quot;#&quot; in name:
</span><span class="cx">             # This UID belongs to a sub-principal
</span><span class="lines">@@ -549,16 +566,16 @@
</span><span class="cx">             primaryUID = name
</span><span class="cx">             subType = None
</span><span class="cx"> 
</span><del>-        record = self.directory.recordWithUID(primaryUID)
-        primaryPrincipal = self.principalForRecord(record)
</del><ins>+        record = (yield self.directory.recordWithUID(primaryUID))
+        primaryPrincipal = (yield self.principalForRecord(record))
</ins><span class="cx">         if primaryPrincipal is None:
</span><span class="cx">             log.info(&quot;No principal found for UID: %s&quot; % (name,))
</span><del>-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         if subType is None:
</span><del>-            return primaryPrincipal
</del><ins>+            returnValue(primaryPrincipal)
</ins><span class="cx">         else:
</span><del>-            return primaryPrincipal.getChild(subType)
</del><ins>+            returnValue((yield primaryPrincipal.getChild(subType)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><span class="lines">@@ -610,17 +627,31 @@
</span><span class="cx">         Top-level renderer in the template.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         record = self.resource.record
</span><ins>+        try:
+            if isinstance(record.guid, uuid.UUID):
+                guid = str(record.guid).upper()
+            else:
+                guid = record.guid
+        except AttributeError:
+            guid = &quot;&quot;
+        try:
+            emailAddresses = record.emailAddresses
+        except AttributeError:
+            emailAddresses = []
</ins><span class="cx">         return tag.fillSlots(
</span><span class="cx">             directoryGUID=str(record.service.guid),
</span><del>-            realm=str(record.service.realmName),
-            principalGUID=str(record.guid),
-            recordType=str(record.recordType),
-            shortNames=&quot;,&quot;.join(record.shortNames),
-            securityIDs=&quot;,&quot;.join(record.authIDs),
-            fullName=str(record.fullName),
-            firstName=str(record.firstName),
-            lastName=str(record.lastName),
-            emailAddresses=formatList(record.emailAddresses),
</del><ins>+            realm=record.service.realmName.encode(&quot;utf-8&quot;),
+            principalGUID=guid,
+            recordType=record.service.recordTypeToOldName(record.recordType),
+            shortNames=&quot;,&quot;.join([n.encode(&quot;utf-8&quot;) for n in record.shortNames]),
+            # MOVE2WHO: need this?
+            # securityIDs=&quot;,&quot;.join(record.authIDs),
+            fullName=record.displayName.encode(&quot;utf-8&quot;),
+            # MOVE2WHO: need this?
+            # firstName=str(record.firstName),
+            # MOVE2WHO: need this?
+            # lastName=str(record.lastName),
+            emailAddresses=formatList(emailAddresses),
</ins><span class="cx">             principalUID=str(self.resource.principalUID()),
</span><span class="cx">             principalURL=formatLink(self.resource.principalURL()),
</span><span class="cx">             alternateURIs=formatLinks(self.resource.alternateURIs()),
</span><span class="lines">@@ -697,7 +728,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         resource = self.resource
</span><span class="cx">         record = resource.record
</span><del>-        if record.enabledForCalendaring:
</del><ins>+        if record.hasCalendars:
</ins><span class="cx">             return tag.fillSlots(
</span><span class="cx">                 calendarUserAddresses=formatLinks(
</span><span class="cx">                     sorted(resource.calendarUserAddresses())
</span><span class="lines">@@ -715,7 +746,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         resource = self.resource
</span><span class="cx">         record = resource.record
</span><del>-        if record.enabledForAddressBooks:
</del><ins>+        if record.hasContacts:
</ins><span class="cx">             return tag.fillSlots(
</span><span class="cx">                 addressBookHomes=formatLinks(resource.addressBookHomeURLs())
</span><span class="cx">             )
</span><span class="lines">@@ -779,7 +810,12 @@
</span><span class="cx">         self._url = url
</span><span class="cx"> 
</span><span class="cx">         self._alternate_urls = tuple([
</span><del>-            joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames
</del><ins>+            joinURL(
+                parent.parent.principalCollectionURL(),
+                record.service.recordTypeToOldName(record.recordType),
+                quote(shortName.encode(&quot;utf-8&quot;))
+            ) + slash
+            for shortName in record.shortNames
</ins><span class="cx">         ])
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -791,7 +827,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Principals are the same if their principalURLs are the same.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return (self.principalURL() == other.principalURL()) if isinstance(other, DirectoryPrincipalResource) else False
</del><ins>+        if isinstance(other, DirectoryPrincipalResource):
+            return (self.principalURL() == other.principalURL())
+        else:
+            return False
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __ne__(self, other):
</span><span class="lines">@@ -812,25 +851,34 @@
</span><span class="cx">         namespace, name = qname
</span><span class="cx"> 
</span><span class="cx">         if qname == davxml.ResourceID.qname():
</span><del>-            returnValue(davxml.ResourceID(davxml.HRef.fromString(&quot;urn:uuid:%s&quot; % (self.record.guid,))))
</del><ins>+            # FIXME: should this return a different CUA flavor if guid is not set on this record?
+            if hasattr(self.record, &quot;guid&quot;):
+                returnValue(davxml.ResourceID(davxml.HRef.fromString(&quot;urn:uuid:%s&quot; % (self.record.guid,))))
+
</ins><span class="cx">         elif namespace == calendarserver_namespace:
</span><del>-            if name == &quot;first-name&quot;:
-                firstName = self.record.firstName
-                if firstName is not None:
-                    returnValue(customxml.FirstNameProperty(firstName))
-                else:
-                    returnValue(None)
</del><span class="cx"> 
</span><del>-            elif name == &quot;last-name&quot;:
-                lastName = self.record.lastName
-                if lastName is not None:
-                    returnValue(customxml.LastNameProperty(lastName))
-                else:
-                    returnValue(None)
</del><ins>+            # MOVE2WHO
+            # if name == &quot;first-name&quot;:
+            #     firstName = self.record.firstName
+            #     if firstName is not None:
+            #         returnValue(customxml.FirstNameProperty(firstName))
+            #     else:
+            #         returnValue(None)
</ins><span class="cx"> 
</span><del>-            elif name == &quot;email-address-set&quot;:
</del><ins>+            # elif name == &quot;last-name&quot;:
+            #     lastName = self.record.lastName
+            #     if lastName is not None:
+            #         returnValue(customxml.LastNameProperty(lastName))
+            #     else:
+            #         returnValue(None)
+
+            if name == &quot;email-address-set&quot;:
+                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">@@ -867,7 +915,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def displayName(self):
</span><del>-        return self.record.displayName()
</del><ins>+        return self.record.displayName
</ins><span class="cx"> 
</span><span class="cx">     ##
</span><span class="cx">     # ACL
</span><span class="lines">@@ -939,51 +987,48 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def proxyFor(self, read_write, resolve_memberships=True):
</del><ins>+    def proxyFor(self, readWrite):
+        &quot;&quot;&quot;
+        Returns the set of principals currently delegating to this principal
+        with the access indicated by the readWrite argument.  If readWrite is
+        True, then write-access delegators are returned, otherwise the read-
+        only-access delegators are returned.
</ins><span class="cx"> 
</span><ins>+        @param readWrite: Whether to look up read-write delegators, or
+            read-only delegators
+        @type readWrite: C{bool}
+
+        @return: A Deferred firing with a set of principals
+        &quot;&quot;&quot;
</ins><span class="cx">         proxyFors = set()
</span><span class="cx"> 
</span><del>-        if resolve_memberships:
-            cache = getattr(self.record.service, &quot;groupMembershipCache&quot;, None)
-            if cache:
-                log.debug(&quot;proxyFor is using groupMembershipCache&quot;)
-                guids = (yield self.record.cachedGroups())
-                memberships = set()
-                for guid in guids:
-                    principal = self.parent.principalForUID(guid)
-                    if principal:
-                        memberships.add(principal)
-            else:
-                memberships = self._getRelatives(&quot;groups&quot;, infinity=True)
-
-            for membership in memberships:
-                results = (yield membership.proxyFor(read_write, False))
-                proxyFors.update(results)
-
</del><span class="cx">         if config.EnableProxyPrincipals:
</span><del>-            # Get proxy group UIDs and map to principal resources
-            proxies = []
-            memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
-            for uid in memberships:
-                subprincipal = self.parent.principalForUID(uid)
-                if subprincipal:
-                    if subprincipal.isProxyType(read_write):
-                        proxies.append(subprincipal.parent)
-                else:
-                    yield self._calendar_user_proxy_index().removeGroup(uid)
</del><ins>+            proxyRecordType = (
+                DelegateRecordType.writeDelegatorGroup if readWrite else
+                DelegateRecordType.readDelegatorGroup
+            )
+            proxyGroupRecord = yield self.record.service.recordWithShortName(
+                proxyRecordType, self.record.uid
+            )
+            if proxyGroupRecord is not None:
+                proxyForRecords = yield proxyGroupRecord.members()
</ins><span class="cx"> 
</span><del>-            proxyFors.update(proxies)
</del><ins>+                uids = set()
+                for record in tuple(proxyForRecords):
+                    if record.uid in uids:
+                        proxyForRecords.remove(record)
+                    else:
+                        uids.add(record.uid)
</ins><span class="cx"> 
</span><del>-        uids = set()
-        for principal in tuple(proxyFors):
-            if principal.principalUID() in uids:
-                proxyFors.remove(principal)
-            else:
-                uids.add(principal.principalUID())
</del><ins>+                for record in proxyForRecords:
+                    principal = yield self.parent.principalForRecord(record)
+                    if principal is not None:
+                        proxyFors.add(principal)
</ins><span class="cx"> 
</span><span class="cx">         returnValue(proxyFors)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _getRelatives(self, method, record=None, relatives=None, records=None, proxy=None, infinity=False):
</span><span class="cx">         if record is None:
</span><span class="cx">             record = self.record
</span><span class="lines">@@ -994,62 +1039,51 @@
</span><span class="cx"> 
</span><span class="cx">         if record not in records:
</span><span class="cx">             records.add(record)
</span><del>-            for relative in getattr(record, method)():
</del><ins>+            for relative in (yield getattr(record, method)()):
</ins><span class="cx">                 if relative not in records:
</span><del>-                    found = self.parent.principalForRecord(relative)
</del><ins>+                    found = (yield self.parent.principalForRecord(relative))
</ins><span class="cx">                     if found is None:
</span><span class="cx">                         log.error(&quot;No principal found for directory record: %r&quot; % (relative,))
</span><span class="cx">                     else:
</span><span class="cx">                         if proxy:
</span><span class="cx">                             if proxy == &quot;read-write&quot;:
</span><del>-                                found = found.getChild(&quot;calendar-proxy-write&quot;)
</del><ins>+                                found = (yield found.getChild(&quot;calendar-proxy-write&quot;))
</ins><span class="cx">                             else:
</span><del>-                                found = found.getChild(&quot;calendar-proxy-read&quot;)
</del><ins>+                                found = (yield found.getChild(&quot;calendar-proxy-read&quot;))
</ins><span class="cx">                         relatives.add(found)
</span><span class="cx"> 
</span><span class="cx">                     if infinity:
</span><del>-                        self._getRelatives(method, relative, relatives, records,
</del><ins>+                        yield self._getRelatives(method, relative, relatives, records,
</ins><span class="cx">                             infinity=infinity)
</span><span class="cx"> 
</span><del>-        return relatives
</del><ins>+        returnValue(relatives)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def groupMembers(self):
</span><del>-        return succeed(self._getRelatives(&quot;members&quot;))
</del><ins>+        return self._getRelatives(&quot;members&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def expandedGroupMembers(self):
</span><del>-        return succeed(self._getRelatives(&quot;members&quot;, infinity=True))
</del><ins>+        return self._getRelatives(&quot;members&quot;, infinity=True)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def groupMemberships(self, infinity=False):
</span><span class="cx"> 
</span><del>-        cache = getattr(self.record.service, &quot;groupMembershipCache&quot;, None)
-        if cache:
-            log.debug(&quot;groupMemberships is using groupMembershipCache&quot;)
-            guids = (yield self.record.cachedGroups())
-            groups = set()
-            for guid in guids:
-                principal = self.parent.principalForUID(guid)
-                if principal:
-                    groups.add(principal)
-        else:
-            groups = self._getRelatives(&quot;groups&quot;, infinity=infinity)
</del><ins>+        groups = yield self._getRelatives(&quot;groups&quot;, infinity=infinity)
</ins><span class="cx"> 
</span><span class="cx">         if config.EnableProxyPrincipals:
</span><del>-            # Get proxy group UIDs and map to principal resources
-            proxies = []
-            memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
-            for uid in memberships:
-                subprincipal = self.parent.principalForUID(uid)
-                if subprincipal:
-                    proxies.append(subprincipal)
-                else:
-                    yield self._calendar_user_proxy_index().removeGroup(uid)
</del><ins>+            for readWrite, proxyType in (
+                (True, &quot;calendar-proxy-write&quot;),
+                (False, &quot;calendar-proxy-read&quot;)
+            ):
+                proxyFors = yield self.proxyFor(readWrite)
+                for proxyFor in proxyFors:
+                    subPrincipal = yield self.parent.principalForUID(
+                        &quot;{}#{}&quot;.format(proxyFor.record.uid, proxyType)
+                    )
+                    groups.add(subPrincipal)
</ins><span class="cx"> 
</span><del>-            groups.update(proxies)
-
</del><span class="cx">         returnValue(groups)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1086,28 +1120,13 @@
</span><span class="cx">         return self.record.thisServer()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    ##
-    # Extra resource info
-    ##
-
-    @inlineCallbacks
-    def setAutoSchedule(self, autoSchedule):
-        self.record.autoSchedule = autoSchedule
-        augmentRecord = (yield self.record.service.augmentService.getAugmentRecord(self.record.guid, self.record.recordType))
-        augmentRecord.autoSchedule = autoSchedule
-        (yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
-
-
-    def getAutoSchedule(self):
-        return self.record.autoSchedule
-
-
</del><span class="cx">     def canAutoSchedule(self, organizer=None):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Determine the auto-schedule state based on record state, type and config settings.
</span><span class="cx"> 
</span><span class="cx">         @param organizer: the CUA of the organizer trying to schedule this principal
</span><span class="cx">         @type organizer: C{str}
</span><ins>+        @return: C{Deferred} firing a C{bool}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.record.canAutoSchedule(organizer)
</span><span class="cx"> 
</span><span class="lines">@@ -1131,9 +1150,8 @@
</span><span class="cx"> 
</span><span class="cx">         @param organizer: the CUA of the organizer scheduling this principal
</span><span class="cx">         @type organizer: C{str}
</span><del>-        @return: auto schedule mode; one of: none, accept-always, decline-always,
-            accept-if-free, decline-if-busy, automatic (see stdconfig.py)
-        @rtype: C{str}
</del><ins>+        @return: auto schedule mode
+        @rtype: C{Deferred} firing L{AutoScheduleMode}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.record.getAutoScheduleMode(organizer)
</span><span class="cx"> 
</span><span class="lines">@@ -1169,7 +1187,7 @@
</span><span class="cx">         @type organizer: C{str}
</span><span class="cx">         @return: True if the autoAcceptGroup is assigned, and the organizer is a member
</span><span class="cx">             of that group.  False otherwise.
</span><del>-        @rtype: C{bool}
</del><ins>+        @rtype: C{Deferred} firing C{bool}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.record.autoAcceptFromOrganizer()
</span><span class="cx"> 
</span><span class="lines">@@ -1187,18 +1205,19 @@
</span><span class="cx">         raise HTTPError(responsecode.NOT_FOUND)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def locateChild(self, req, segments):
</span><del>-        child = self.getChild(segments[0])
</del><ins>+        child = (yield self.getChild(segments[0]))
</ins><span class="cx">         if child is not None:
</span><del>-            return (child, segments[1:])
-        return (None, ())
</del><ins>+            returnValue((child, segments[1:]))
+        returnValue((None, ()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            return succeed(self)
</ins><span class="cx"> 
</span><del>-        return None
</del><ins>+        return succeed(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><span class="lines">@@ -1221,7 +1240,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def addressBooksEnabled(self):
</span><del>-        return config.EnableCardDAV and self.record.enabledForAddressBooks
</del><ins>+        return config.EnableCardDAV and self.record.hasContacts
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1254,6 +1273,7 @@
</span><span class="cx">         Return a CUA for this principal, preferring in this order:
</span><span class="cx">             urn:uuid: form
</span><span class="cx">             mailto: form
</span><ins>+            /principal/__uids__/ form
</ins><span class="cx">             first in calendarUserAddresses( ) list
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.record.canonicalCalendarUserAddress()
</span><span class="lines">@@ -1288,7 +1308,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def calendarHomeURLs(self):
</span><del>-        if self.record.enabledForCalendaring:
</del><ins>+        if self.record.hasCalendars:
</ins><span class="cx">             homeURL = self._homeChildURL(None)
</span><span class="cx">         else:
</span><span class="cx">             homeURL = &quot;&quot;
</span><span class="lines">@@ -1318,7 +1338,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def addressBookHomeURLs(self):
</span><del>-        if self.record.enabledForAddressBooks:
</del><ins>+        if self.record.hasContacts:
</ins><span class="cx">             homeURL = self._addressBookHomeChildURL(None)
</span><span class="cx">         else:
</span><span class="cx">             homeURL = &quot;&quot;
</span><span class="lines">@@ -1391,22 +1411,26 @@
</span><span class="cx"> 
</span><span class="cx">     def getChild(self, name):
</span><span class="cx">         if name == &quot;&quot;:
</span><del>-            return self
</del><ins>+            return succeed(self)
</ins><span class="cx"> 
</span><del>-        if config.EnableProxyPrincipals and name in (&quot;calendar-proxy-read&quot;,
-                                                     &quot;calendar-proxy-write&quot;):
</del><ins>+        if config.EnableProxyPrincipals and name in (
+            &quot;calendar-proxy-read&quot;, &quot;calendar-proxy-write&quot;,
+            &quot;calendar-proxy-read-for&quot;, &quot;calendar-proxy-write-for&quot;,
+            ):
</ins><span class="cx">             # name is required to be str
</span><span class="cx">             from twistedcaldav.directory.calendaruserproxy import (
</span><span class="cx">                 CalendarUserProxyPrincipalResource
</span><span class="cx">             )
</span><del>-            return CalendarUserProxyPrincipalResource(self, str(name))
</del><ins>+            return succeed(CalendarUserProxyPrincipalResource(self, str(name)))
</ins><span class="cx">         else:
</span><del>-            return None
</del><ins>+            return succeed(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def listChildren(self):
</span><span class="cx">         if config.EnableProxyPrincipals:
</span><del>-            return (&quot;calendar-proxy-read&quot;, &quot;calendar-proxy-write&quot;)
</del><ins>+            return (
+                &quot;calendar-proxy-read&quot;, &quot;calendar-proxy-write&quot;,
+            )
</ins><span class="cx">         else:
</span><span class="cx">             return ()
</span><span class="cx"> 
</span><span class="lines">@@ -1425,71 +1449,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.fullName,)
-        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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/accounts.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/accounts.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/accounts.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,279 +18,382 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;!DOCTYPE accounts SYSTEM &quot;../../../conf/auth/accounts.dtd&quot;&gt;
</span><span class="cx"> 
</span><del>-&lt;accounts realm=&quot;Test&quot;&gt;
-  &lt;user&gt;
-    &lt;uid&gt;admin&lt;/uid&gt;
-    &lt;guid&gt;D11F03A0-97EA-48AF-9A6C-FAC7F3975766&lt;/guid&gt;
</del><ins>+&lt;directory realm=&quot;Test&quot;&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;admin&lt;/short-name&gt;
+    &lt;uid&gt;D11F03A0-97EA-48AF-9A6C-FAC7F3975766&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;nimda&lt;/password&gt;
</span><del>-    &lt;name&gt;Administrators&lt;/name&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;wsanchez&lt;/uid&gt;
</del><ins>+    &lt;full-name&gt;Administrators&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;wsanchez&lt;/short-name&gt;
+    &lt;uid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/uid&gt;
</ins><span class="cx">     &lt;guid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/guid&gt;
</span><span class="cx">     &lt;password&gt;zehcnasw&lt;/password&gt;
</span><del>-    &lt;name&gt;Wilfredo Sanchez&lt;/name&gt;
-    &lt;email-address&gt;wsanchez@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;cdaboo&lt;/uid&gt;
</del><ins>+    &lt;full-name&gt;Wilfredo Sanchez&lt;/full-name&gt;
+    &lt;email&gt;wsanchez@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;cdaboo&lt;/short-name&gt;
+    &lt;uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/uid&gt;
</ins><span class="cx">     &lt;guid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/guid&gt;
</span><span class="cx">     &lt;password&gt;oobadc&lt;/password&gt;
</span><del>-    &lt;name&gt;Cyrus Daboo&lt;/name&gt;
-    &lt;email-address&gt;cdaboo@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;lecroy&lt;/uid&gt;
-    &lt;guid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Cyrus Daboo&lt;/full-name&gt;
+    &lt;email&gt;cdaboo@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;lecroy&lt;/short-name&gt;
+    &lt;uid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;yorcel&lt;/password&gt;
</span><del>-    &lt;name&gt;Chris Lecroy&lt;/name&gt;
-    &lt;email-address&gt;lecroy@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;dreid&lt;/uid&gt;
-    &lt;guid&gt;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Chris Lecroy&lt;/full-name&gt;
+    &lt;email&gt;lecroy@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;dreid&lt;/short-name&gt;
+    &lt;uid&gt;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;dierd&lt;/password&gt;
</span><del>-    &lt;name&gt;David Reid&lt;/name&gt;
-    &lt;email-address&gt;dreid@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;doublequotes&lt;/uid&gt;
</del><ins>+    &lt;full-name&gt;David Reid&lt;/full-name&gt;
+    &lt;email&gt;dreid@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;doublequotes&lt;/short-name&gt;
+    &lt;uid&gt;8E04787E-336D-41ED-A70B-D233AD0DCE6F&lt;/uid&gt;
</ins><span class="cx">     &lt;guid&gt;8E04787E-336D-41ED-A70B-D233AD0DCE6F&lt;/guid&gt;
</span><span class="cx">     &lt;password&gt;setouqelbuod&lt;/password&gt;
</span><del>-    &lt;name&gt;Double &quot;quotey&quot; Quotes&lt;/name&gt;
-    &lt;email-address&gt;doublequotes@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;nocalendar&lt;/uid&gt;
-    &lt;guid&gt;543D28BA-F74F-4D5F-9243-B3E3A61171E5&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Double &quot;quotey&quot; Quotes&lt;/full-name&gt;
+    &lt;email&gt;doublequotes@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;purge1&lt;/short-name&gt;
+    &lt;uid&gt;C76DB741-5A2A-4239-8112-10CF152AFCA4&lt;/uid&gt;
+    &lt;guid&gt;C76DB741-5A2A-4239-8112-10CF152AFCA4&lt;/guid&gt;
+    &lt;password&gt;purge1&lt;/password&gt;
+    &lt;full-name&gt;purge1&lt;/full-name&gt;
+    &lt;email&gt;purge1@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;purge2&lt;/short-name&gt;
+    &lt;uid&gt;FFED7B62-2E08-496E-BD32-B2F95FFDDB6B&lt;/uid&gt;
+    &lt;guid&gt;FFED7B62-2E08-496E-BD32-B2F95FFDDB6B&lt;/guid&gt;
+    &lt;password&gt;purge2&lt;/password&gt;
+    &lt;full-name&gt;purge2&lt;/full-name&gt;
+    &lt;email&gt;purge2@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;home1&lt;/short-name&gt;
+    &lt;uid&gt;home1&lt;/uid&gt;
+    &lt;password&gt;home1&lt;/password&gt;
+    &lt;full-name&gt;Home One&lt;/full-name&gt;
+    &lt;email&gt;home1@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;home2&lt;/short-name&gt;
+    &lt;uid&gt;home2&lt;/uid&gt;
+    &lt;password&gt;home2&lt;/password&gt;
+    &lt;full-name&gt;Home Two&lt;/full-name&gt;
+    &lt;email&gt;home2@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;nocalendar&lt;/short-name&gt;
+    &lt;uid&gt;543D28BA-F74F-4D5F-9243-B3E3A61171E5&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;radnelacon&lt;/password&gt;
</span><del>-    &lt;name&gt;No Calendar&lt;/name&gt;
-    &lt;email-address&gt;nocalendar@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;usera&lt;/uid&gt;
-    &lt;guid&gt;7423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;No Calendar&lt;/full-name&gt;
+    &lt;email&gt;nocalendar@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;usera&lt;/short-name&gt;
+    &lt;uid&gt;7423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;a&lt;/name&gt;
-    &lt;email-address&gt;a@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;userb&lt;/uid&gt;
-    &lt;guid&gt;8A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;a&lt;/full-name&gt;
+    &lt;email&gt;a@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;userb&lt;/short-name&gt;
+    &lt;uid&gt;8A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;b&lt;/password&gt;
</span><del>-    &lt;name&gt;b&lt;/name&gt;
-    &lt;email-address&gt;b@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;userc&lt;/uid&gt;
-    &lt;guid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;b&lt;/full-name&gt;
+    &lt;email&gt;b@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;userc&lt;/short-name&gt;
+    &lt;uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;c&lt;/password&gt;
</span><del>-    &lt;name&gt;c&lt;/name&gt;
-    &lt;email-address&gt;c@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;usercalonly&lt;/uid&gt;
-    &lt;guid&gt;9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;c&lt;/full-name&gt;
+    &lt;email&gt;c@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;usercalonly&lt;/short-name&gt;
+    &lt;uid&gt;9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;a calonly&lt;/name&gt;
-    &lt;email-address&gt;a-calonly@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;useradbkonly&lt;/uid&gt;
-    &lt;guid&gt;7678EC8A-A069-4E82-9066-7279C6718507&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;a calonly&lt;/full-name&gt;
+    &lt;email&gt;a-calonly@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;useradbkonly&lt;/short-name&gt;
+    &lt;uid&gt;7678EC8A-A069-4E82-9066-7279C6718507&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;a adbkonly&lt;/name&gt;
-    &lt;email-address&gt;a-adbkonly@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;nonascii&lt;/uid&gt;
-    &lt;uid&gt;nonascii佐藤&lt;/uid&gt;
-    &lt;guid&gt;320B73A1-46E2-4180-9563-782DFDBE1F63&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;a adbkonly&lt;/full-name&gt;
+    &lt;email&gt;a-adbkonly@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;nonascii&lt;/short-name&gt;
+    &lt;short-name&gt;nonascii佐藤&lt;/short-name&gt;
+    &lt;uid&gt;320B73A1-46E2-4180-9563-782DFDBE1F63&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;佐藤佐藤佐藤&lt;/name&gt;
-    &lt;email-address&gt;nonascii@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;delegator&lt;/uid&gt;
-    &lt;guid&gt;FC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;佐藤佐藤佐藤&lt;/full-name&gt;
+    &lt;email&gt;nonascii@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;delegator&lt;/short-name&gt;
+    &lt;uid&gt;FC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;Calendar Delegator&lt;/name&gt;
-    &lt;email-address&gt;calendardelegator@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
-    &lt;uid&gt;occasionaldelegate&lt;/uid&gt;
-    &lt;guid&gt;EC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Calendar Delegator&lt;/full-name&gt;
+    &lt;email&gt;calendardelegator@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;occasionaldelegate&lt;/short-name&gt;
+    &lt;uid&gt;EC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;Occasional Delegate&lt;/name&gt;
-    &lt;email-address&gt;occasional@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-    &lt;user&gt;
-    &lt;uid&gt;delegateviagroup&lt;/uid&gt;
-    &lt;guid&gt;46D9D716-CBEE-490F-907A-66FA6C3767FF&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Occasional Delegate&lt;/full-name&gt;
+    &lt;email&gt;occasional@example.com&lt;/email&gt;
+  &lt;/record&gt;
+    &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;delegateviagroup&lt;/short-name&gt;
+    &lt;uid&gt;46D9D716-CBEE-490F-907A-66FA6C3767FF&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;a&lt;/password&gt;
</span><del>-    &lt;name&gt;Delegate Via Group&lt;/name&gt;
-    &lt;email-address&gt;delegateviagroup@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;group&gt;
-    &lt;uid&gt;delegategroup&lt;/uid&gt;
-    &lt;guid&gt;00599DAF-3E75-42DD-9DB7-52617E79943F&lt;/guid&gt;
-    &lt;name&gt;Delegate Group&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;delegateviagroup&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;user repeat=&quot;100&quot;&gt;
-    &lt;uid&gt;user%02d&lt;/uid&gt;
-    &lt;guid&gt;user%02d&lt;/guid&gt;
-    &lt;password&gt;%02duser&lt;/password&gt;
-    &lt;name&gt;~35 User %02d&lt;/name&gt;
-    &lt;first-name&gt;~5&lt;/first-name&gt;
-    &lt;last-name&gt;~9 User %02d&lt;/last-name&gt;
-    &lt;email-address&gt;~10@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;group&gt;
-    &lt;uid&gt;managers&lt;/uid&gt;
-    &lt;guid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/guid&gt;
</del><ins>+    &lt;full-name&gt;Delegate Via Group&lt;/full-name&gt;
+    &lt;email&gt;delegateviagroup@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;delegategroup&lt;/short-name&gt;
+    &lt;uid&gt;00599DAF-3E75-42DD-9DB7-52617E79943F&lt;/uid&gt;
+    &lt;full-name&gt;Delegate Group&lt;/full-name&gt;
+    &lt;member-uid&gt;46D9D716-CBEE-490F-907A-66FA6C3767FF&lt;/member-uid&gt;
+  &lt;/record&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;uid&gt;__wsanchez1__&lt;/uid&gt;
+    &lt;short-name&gt;wsanchez1&lt;/short-name&gt;
+    &lt;short-name&gt;wilfredo_sanchez&lt;/short-name&gt;
+    &lt;full-name&gt;Wilfredo Sanchez&lt;/full-name&gt;
+    &lt;password&gt;zehcnasw&lt;/password&gt;
+    &lt;email&gt;wsanchez@bitbucket.calendarserver.org&lt;/email&gt;
+    &lt;email&gt;wsanchez@devnull.twistedmatrix.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;uid&gt;__glyph1__&lt;/uid&gt;
+    &lt;short-name&gt;glyph1&lt;/short-name&gt;
+    &lt;full-name&gt;Glyph Lefkowitz&lt;/full-name&gt;
+    &lt;password&gt;hpylg&lt;/password&gt;
+    &lt;email&gt;glyph@bitbucket.calendarserver.org&lt;/email&gt;
+    &lt;email&gt;glyph@devnull.twistedmatrix.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;uid&gt;__sagen1__&lt;/uid&gt;
+    &lt;short-name&gt;sagen&lt;/short-name&gt;
+    &lt;short-name&gt;sagen1&lt;/short-name&gt;
+    &lt;full-name&gt;Morgen Sagen&lt;/full-name&gt;
+    &lt;password&gt;negas&lt;/password&gt;
+    &lt;email&gt;sagen@bitbucket.calendarserver.org&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;uid&gt;__cdaboo1__&lt;/uid&gt;
+    &lt;short-name&gt;cdaboo1&lt;/short-name&gt;
+    &lt;full-name&gt;Cyrus Daboo&lt;/full-name&gt;
+    &lt;password&gt;suryc&lt;/password&gt;
+    &lt;email&gt;cdaboo@bitbucket.calendarserver.org&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;uid&gt;__dre1__&lt;/uid&gt;
+    &lt;short-name&gt;dre1&lt;/short-name&gt;
+    &lt;short-name&gt;dre&lt;/short-name&gt;
+    &lt;full-name&gt;Andre LaBranche&lt;/full-name&gt;
+    &lt;password&gt;erd&lt;/password&gt;
+    &lt;email&gt;dre@bitbucket.calendarserver.org&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;uid&gt;__top_group_1__&lt;/uid&gt;
+    &lt;short-name&gt;top-group-1&lt;/short-name&gt;
+    &lt;full-name&gt;Top Group 1&lt;/full-name&gt;
+    &lt;email&gt;topgroup1@example.com&lt;/email&gt;
+    &lt;member-uid&gt;__wsanchez1__&lt;/member-uid&gt;
+    &lt;member-uid&gt;__glyph1__&lt;/member-uid&gt;
+    &lt;member-uid&gt;__sub_group_1__&lt;/member-uid&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;uid&gt;__sub_group_1__&lt;/uid&gt;
+    &lt;short-name&gt;sub-group-1&lt;/short-name&gt;
+    &lt;full-name&gt;Sub Group 1&lt;/full-name&gt;
+    &lt;email&gt;subgroup1@example.com&lt;/email&gt;
+    &lt;member-uid&gt;__sagen1__&lt;/member-uid&gt;
+    &lt;member-uid&gt;__cdaboo1__&lt;/member-uid&gt;
+  &lt;/record&gt;
+
+
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;managers&lt;/short-name&gt;
+    &lt;uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/uid&gt;
</ins><span class="cx">     &lt;password&gt;managers&lt;/password&gt;
</span><del>-    &lt;name&gt;Managers&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;users&quot;&gt;lecroy&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Managers&lt;/full-name&gt;
+      &lt;member-uid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;admin&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;admin&lt;/uid&gt;
</span><del>-    &lt;guid&gt;admin&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;admin&lt;/password&gt;
</span><del>-    &lt;name&gt;Administrators&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;managers&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Administrators&lt;/full-name&gt;
+      &lt;member-uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;grunts&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;grunts&lt;/uid&gt;
</span><del>-    &lt;guid&gt;grunts&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;grunts&lt;/password&gt;
</span><del>-    &lt;name&gt;We do all the work&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member&gt;wsanchez&lt;/member&gt;
-      &lt;member&gt;cdaboo&lt;/member&gt;
-      &lt;member&gt;dreid&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;We do all the work&lt;/full-name&gt;
+      &lt;member-uid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/member-uid&gt;
+      &lt;member-uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/member-uid&gt;
+      &lt;member-uid&gt;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;right_coast&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;right_coast&lt;/uid&gt;
</span><del>-    &lt;guid&gt;right_coast&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;right_coast&lt;/password&gt;
</span><del>-    &lt;name&gt;East Coast&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member&gt;cdaboo&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;East Coast&lt;/full-name&gt;
+      &lt;member-uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;left_coast&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;left_coast&lt;/uid&gt;
</span><del>-    &lt;guid&gt;left_coast&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;left_coast&lt;/password&gt;
</span><del>-    &lt;name&gt;West Coast&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member&gt;wsanchez&lt;/member&gt;
-      &lt;member&gt;lecroy&lt;/member&gt;
-      &lt;member&gt;dreid&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;West Coast&lt;/full-name&gt;
+      &lt;member-uid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/member-uid&gt;
+      &lt;member-uid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/member-uid&gt;
+      &lt;member-uid&gt;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;both_coasts&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;both_coasts&lt;/uid&gt;
</span><del>-    &lt;guid&gt;both_coasts&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;both_coasts&lt;/password&gt;
</span><del>-    &lt;name&gt;Both Coasts&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;right_coast&lt;/member&gt;
-      &lt;member type=&quot;groups&quot;&gt;left_coast&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Both Coasts&lt;/full-name&gt;
+      &lt;member-uid&gt;right_coast&lt;/member-uid&gt;
+      &lt;member-uid&gt;left_coast&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;recursive1_coasts&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;recursive1_coasts&lt;/uid&gt;
</span><del>-    &lt;guid&gt;recursive1_coasts&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;recursive1_coasts&lt;/password&gt;
</span><del>-    &lt;name&gt;Recursive1 Coasts&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;recursive2_coasts&lt;/member&gt;
-      &lt;member&gt;wsanchez&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Recursive1 Coasts&lt;/full-name&gt;
+      &lt;member-uid&gt;recursive2_coasts&lt;/member-uid&gt;
+      &lt;member-uid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;recursive2_coasts&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;recursive2_coasts&lt;/uid&gt;
</span><del>-    &lt;guid&gt;recursive2_coasts&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;recursive2_coasts&lt;/password&gt;
</span><del>-    &lt;name&gt;Recursive2 Coasts&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member type=&quot;groups&quot;&gt;recursive1_coasts&lt;/member&gt;
-      &lt;member&gt;cdaboo&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;group&gt;
</del><ins>+    &lt;full-name&gt;Recursive2 Coasts&lt;/full-name&gt;
+      &lt;member-uid&gt;recursive1_coasts&lt;/member-uid&gt;
+      &lt;member-uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/member-uid&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;group&quot;&gt;
+    &lt;short-name&gt;non_calendar_group&lt;/short-name&gt;
</ins><span class="cx">     &lt;uid&gt;non_calendar_group&lt;/uid&gt;
</span><del>-    &lt;guid&gt;non_calendar_group&lt;/guid&gt;
</del><span class="cx">     &lt;password&gt;non_calendar_group&lt;/password&gt;
</span><del>-    &lt;name&gt;Non-calendar group&lt;/name&gt;
-    &lt;members&gt;
-      &lt;member&gt;cdaboo&lt;/member&gt;
-      &lt;member&gt;lecroy&lt;/member&gt;
-    &lt;/members&gt;
-  &lt;/group&gt;
-  &lt;location&gt;
-    &lt;uid&gt;mercury&lt;/uid&gt;
-    &lt;guid&gt;mercury&lt;/guid&gt;
-    &lt;password&gt;mercury&lt;/password&gt;
-    &lt;name&gt;Mercury Seven&lt;/name&gt;
-    &lt;email-address&gt;mercury@example.com&lt;/email-address&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;gemini&lt;/uid&gt;
-    &lt;guid&gt;gemini&lt;/guid&gt;
-    &lt;password&gt;gemini&lt;/password&gt;
-    &lt;name&gt;Gemini Twelve&lt;/name&gt;
-    &lt;email-address&gt;gemini@example.com&lt;/email-address&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;apollo&lt;/uid&gt;
-    &lt;guid&gt;apollo&lt;/guid&gt;
-    &lt;password&gt;apollo&lt;/password&gt;
-    &lt;name&gt;Apollo Eleven&lt;/name&gt;
-    &lt;email-address&gt;apollo@example.com&lt;/email-address&gt;
-  &lt;/location&gt;
-  &lt;location&gt;
-    &lt;uid&gt;orion&lt;/uid&gt;
-    &lt;guid&gt;orion&lt;/guid&gt;
-    &lt;password&gt;orion&lt;/password&gt;
-    &lt;name&gt;Orion&lt;/name&gt;
-    &lt;email-address&gt;orion@example.com&lt;/email-address&gt;
-  &lt;/location&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;transporter&lt;/uid&gt;
-    &lt;guid&gt;transporter&lt;/guid&gt;
-    &lt;password&gt;transporter&lt;/password&gt;
-    &lt;name&gt;Mass Transporter&lt;/name&gt;
-    &lt;email-address&gt;transporter@example.com&lt;/email-address&gt;
-  &lt;/resource&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;ftlcpu&lt;/uid&gt;
-    &lt;guid&gt;ftlcpu&lt;/guid&gt;
-    &lt;password&gt;ftlcpu&lt;/password&gt;
-    &lt;name&gt;Faster-Than-Light Microprocessor&lt;/name&gt;
-    &lt;email-address&gt;ftlcpu@example.com&lt;/email-address&gt;
-  &lt;/resource&gt;
-  &lt;resource&gt;
-    &lt;uid&gt;non_calendar_proxy&lt;/uid&gt;
-    &lt;guid&gt;non_calendar_proxy&lt;/guid&gt;
-    &lt;password&gt;non_calendar_proxy&lt;/password&gt;
-    &lt;name&gt;Non-calendar proxy&lt;/name&gt;
-    &lt;email-address&gt;non_calendar_proxy@example.com&lt;/email-address&gt;
-  &lt;/resource&gt;
-  &lt;resource&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 Record&lt;/name&gt;
-    &lt;email-address&gt;disabled@example.com&lt;/email-address&gt;
-  &lt;/resource&gt;
-&lt;/accounts&gt;
</del><ins>+    &lt;full-name&gt;Non-calendar group&lt;/full-name&gt;
+      &lt;member-uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/member-uid&gt;
+      &lt;member-uid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/member-uid&gt;
+  &lt;/record&gt;
+
+  &lt;!-- Calverify test records --&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;example1&lt;/short-name&gt;
+    &lt;uid&gt;D46F3D71-04B7-43C2-A7B6-6F92F92E61D0&lt;/uid&gt;
+    &lt;guid&gt;D46F3D71-04B7-43C2-A7B6-6F92F92E61D0&lt;/guid&gt;
+    &lt;password&gt;example&lt;/password&gt;
+    &lt;full-name&gt;Example User1&lt;/full-name&gt;
+    &lt;email&gt;example1@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;example2&lt;/short-name&gt;
+    &lt;uid&gt;47B16BB4-DB5F-4BF6-85FE-A7DA54230F92&lt;/uid&gt;
+    &lt;guid&gt;47B16BB4-DB5F-4BF6-85FE-A7DA54230F92&lt;/guid&gt;
+    &lt;password&gt;example&lt;/password&gt;
+    &lt;full-name&gt;Example User2&lt;/full-name&gt;
+    &lt;email&gt;example2@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;example3&lt;/short-name&gt;
+    &lt;uid&gt;AC478592-7783-44D1-B2AE-52359B4E8415&lt;/uid&gt;
+    &lt;guid&gt;AC478592-7783-44D1-B2AE-52359B4E8415&lt;/guid&gt;
+    &lt;password&gt;example&lt;/password&gt;
+    &lt;full-name&gt;Example User3&lt;/full-name&gt;
+    &lt;email&gt;example3@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+  &lt;record type=&quot;user&quot;&gt;
+    &lt;short-name&gt;example4&lt;/short-name&gt;
+    &lt;uid&gt;A89E3A97-1658-4E45-A185-479F3E49D446&lt;/uid&gt;
+    &lt;guid&gt;A89E3A97-1658-4E45-A185-479F3E49D446&lt;/guid&gt;
+    &lt;password&gt;example&lt;/password&gt;
+    &lt;full-name&gt;Example User4&lt;/full-name&gt;
+    &lt;email&gt;example4@example.com&lt;/email&gt;
+  &lt;/record&gt;
+
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestaugmentsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/augments.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/augments.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/augments.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,177 +19,144 @@
</span><span class="cx"> &lt;!DOCTYPE augments SYSTEM &quot;../../../conf/auth/augments.dtd&quot;&gt;
</span><span class="cx"> 
</span><span class="cx"> &lt;augments realm=&quot;Test&quot;&gt;
</span><ins>+  &lt;!--
</ins><span class="cx">   &lt;record&gt;
</span><ins>+    &lt;uid&gt;Location-Default&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;Resource-Default&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  --&gt;
+
+  &lt;record&gt;
</ins><span class="cx">     &lt;uid&gt;D11F03A0-97EA-48AF-9A6C-FAC7F3975766&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">     &lt;server-id&gt;00001&lt;/server-id&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;5A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">     &lt;server-id&gt;00002&lt;/server-id&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;8B4288F6-CC82-491D-8EF9-642EF4F3E7D0&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;543D28BA-F74F-4D5F-9243-B3E3A61171E5&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;false&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;false&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record repeat=&quot;100&quot;&gt;
</span><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><del>-    &lt;uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;admin&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;grunts&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
</del><span class="cx">     &lt;uid&gt;right_coast&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;left_coast&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><del>-    &lt;uid&gt;both_coasts&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;recursive1_coasts&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;recursive2_coasts&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
-    &lt;uid&gt;non_calendar_group&lt;/uid&gt;
-    &lt;enable&gt;true&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
</del><span class="cx">     &lt;uid&gt;mercury&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;gemini&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;apollo&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">     &lt;auto-accept-group&gt;both_coasts&lt;/auto-accept-group&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;orion&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;transporter&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;ftlcpu&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><ins>+  &lt;!--
</ins><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;non_calendar_proxy&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><ins>+--&gt;
</ins><span class="cx">   &lt;record&gt;
</span><del>-    &lt;uid&gt;disabled&lt;/uid&gt;
-    &lt;enable&gt;false&lt;/enable&gt;
-  &lt;/record&gt;
-  &lt;record&gt;
</del><span class="cx">     &lt;uid&gt;7423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;8A985493-EE2C-4665-94CF-4DFEA3A89500&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;7678EC8A-A069-4E82-9066-7279C6718507&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;FC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;EC465590-E9E9-4746-ACE8-6C756A49FE4D&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-login&gt;true&lt;/enable-login&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record&gt;
</span><span class="cx">     &lt;uid&gt;00599DAF-3E75-42DD-9DB7-52617E79943F&lt;/uid&gt;
</span><del>-    &lt;enable&gt;true&lt;/enable&gt;
</del><span class="cx">     &lt;enable-calendar&gt;false&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-login&gt;false&lt;/enable-login&gt;
</span><span class="cx">   &lt;/record&gt;
</span><ins>+  &lt;record&gt;
+    &lt;uid&gt;75EA36BE-F71B-40F9-81F9-CF59BF40CA8F&lt;/uid&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+
</ins><span class="cx"> &lt;/augments&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestresourcesxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/resources.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/resources.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/resources.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,5 +18,103 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;!DOCTYPE accounts SYSTEM &quot;../../../conf/auth/accounts.dtd&quot;&gt;
</span><span class="cx"> 
</span><del>-&lt;accounts realm=&quot;Test&quot;&gt;
-&lt;/accounts&gt;
</del><ins>+&lt;directory realm=&quot;Test&quot;&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;mercury&lt;/uid&gt;
+    &lt;short-name&gt;mercury&lt;/short-name&gt;
+    &lt;password&gt;mercury&lt;/password&gt;
+    &lt;full-name&gt;Mercury Seven&lt;/full-name&gt;
+    &lt;email&gt;mercury@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;gemini&lt;/uid&gt;
+    &lt;short-name&gt;gemini&lt;/short-name&gt;
+    &lt;password&gt;gemini&lt;/password&gt;
+    &lt;full-name&gt;Gemini Twelve&lt;/full-name&gt;
+    &lt;email&gt;gemini@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;apollo&lt;/uid&gt;
+    &lt;short-name&gt;apollo&lt;/short-name&gt;
+    &lt;password&gt;apollo&lt;/password&gt;
+    &lt;full-name&gt;Apollo Eleven&lt;/full-name&gt;
+    &lt;email&gt;apollo@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;orion&lt;/uid&gt;
+    &lt;short-name&gt;orion&lt;/short-name&gt;
+    &lt;password&gt;orion&lt;/password&gt;
+    &lt;full-name&gt;Orion&lt;/full-name&gt;
+    &lt;email&gt;orion@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;uid&gt;transporter&lt;/uid&gt;
+    &lt;short-name&gt;transporter&lt;/short-name&gt;
+    &lt;password&gt;transporter&lt;/password&gt;
+    &lt;full-name&gt;Mass Transporter&lt;/full-name&gt;
+    &lt;email&gt;transporter@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;uid&gt;ftlcpu&lt;/uid&gt;
+    &lt;short-name&gt;ftlcpu&lt;/short-name&gt;
+    &lt;password&gt;ftlcpu&lt;/password&gt;
+    &lt;full-name&gt;Faster-Than-Light Microprocessor&lt;/full-name&gt;
+    &lt;email&gt;ftlcpu@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;uid&gt;non_calendar_proxy&lt;/uid&gt;
+    &lt;short-name&gt;non_calendar_proxy&lt;/short-name&gt;
+    &lt;password&gt;non_calendar_proxy&lt;/password&gt;
+    &lt;full-name&gt;Non-calendar proxy&lt;/full-name&gt;
+    &lt;email&gt;non_calendar_proxy@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;uid&gt;disabled&lt;/uid&gt;
+    &lt;short-name&gt;disabled&lt;/short-name&gt;
+    &lt;password&gt;disabled&lt;/password&gt;
+    &lt;full-name&gt;Disabled Record&lt;/full-name&gt;
+    &lt;email&gt;disabled@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;__sanchezoffice__&lt;/uid&gt;
+    &lt;short-name&gt;sanchezoffice&lt;/short-name&gt;
+    &lt;full-name&gt;Sanchez Office&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location01&lt;/short-name&gt;
+    &lt;uid&gt;75EA36BE-F71B-40F9-81F9-CF59BF40CA8F&lt;/uid&gt;
+    &lt;guid&gt;75EA36BE-F71B-40F9-81F9-CF59BF40CA8F&lt;/guid&gt;
+    &lt;password&gt;location01&lt;/password&gt;
+    &lt;full-name&gt;Room 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;room-with-address-1&lt;/short-name&gt;
+    &lt;uid&gt;room-addr-1&lt;/uid&gt;
+    &lt;guid&gt;634A102B-6902-464F-9451-8A86A31628C1&lt;/guid&gt;
+    &lt;password&gt;room-addr-2&lt;/password&gt;
+    &lt;full-name&gt;Room with Address 1&lt;/full-name&gt;
+    &lt;associated-address&gt;1-infinite-loop&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;room-with-address-2&lt;/short-name&gt;
+    &lt;uid&gt;room-addr-2&lt;/uid&gt;
+    &lt;password&gt;room-addr-2&lt;/password&gt;
+    &lt;full-name&gt;Room with Address 2&lt;/full-name&gt;
+    &lt;associated-address&gt;2-infinite-loop&lt;/associated-address&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il1&lt;/short-name&gt;
+    &lt;uid&gt;1-infinite-loop&lt;/uid&gt;
+    &lt;full-name&gt;One Infinite Loop&lt;/full-name&gt;
+    &lt;street-address&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.331741,-122.030333&lt;/geographic-location&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il2&lt;/short-name&gt;
+    &lt;uid&gt;2-infinite-loop&lt;/uid&gt;
+    &lt;full-name&gt;Two Infinite Loop&lt;/full-name&gt;
+    &lt;street-address&gt;2 Infinite Loop, Cupertino, CA 95014&lt;/street-address&gt;
+    &lt;geographic-location&gt;37.332633,-122.030502&lt;/geographic-location&gt;
+  &lt;/record&gt;
+
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_aggregatepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_aggregate.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_aggregate.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_aggregate.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_augmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_augment.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_augment.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_augment.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_buildquerypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_buildquery.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_buildquery.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_buildquery.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_cachedirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_cachedirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_cachedirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_cachedirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_directorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_directory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_directory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_directory.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_guidchangepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_guidchange.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_guidchange.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_guidchange.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,116 +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 __future__ import print_function
-
-from twistedcaldav.directory.directory import DirectoryService
-
-from txdav.xml import element as davxml
-from txweb2.dav.resource import AccessDeniedError
-from txweb2.test.test_server import SimpleRequest
-
-from twistedcaldav.directory.test.util import maybeCommit
-from twistedcaldav.test.util import TestCase, xmlFile
-
-
-class ProvisionedPrincipals(TestCase):
-    &quot;&quot;&quot;
-    Directory service provisioned principals.
-    &quot;&quot;&quot;
-    def setUp(self):
-        super(ProvisionedPrincipals, self).setUp()
-
-        # Setup the initial directory
-        self.createStockDirectoryService()
-        self.setupCalendars()
-
-        self.site.resource.setAccessControlList(davxml.ACL())
-
-
-    def resetCalendars(self):
-        del self.site.resource.putChildren[&quot;calendars&quot;]
-        self.setupCalendars()
-
-
-    def test_guidchange(self):
-        &quot;&quot;&quot;
-        DirectoryPrincipalResource.proxies()
-        &quot;&quot;&quot;
-        oldUID = &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;
-        newUID = &quot;38D8AC00-5490-4425-BE3A-05FFB9862444&quot;
-
-        homeResource = &quot;/calendars/users/cdaboo/&quot;
-
-        def privs1(result):
-            # Change GUID in record
-            self.xmlFile.setContent(
-                xmlFile.getContent().replace(oldUID, newUID)
-            )
-
-            # Force re-read of records (not sure why _fileInfo has to be wiped here...)
-            self.directoryService._fileInfo = (0, 0)
-            self.directoryService.recordWithShortName(DirectoryService.recordType_users, &quot;cdaboo&quot;)
-
-            # Now force the calendar home resource to be reset
-            self.resetCalendars()
-
-            # Make sure new user cannot access old user's calendar home
-            return self._checkPrivileges(None, homeResource, davxml.HRef(&quot;/principals/__uids__/&quot; + newUID + &quot;/&quot;), davxml.Write, False)
-
-        # Make sure current user has access to their calendar home
-        d = self._checkPrivileges(None, homeResource, davxml.HRef(&quot;/principals/__uids__/&quot; + oldUID + &quot;/&quot;), davxml.Write, True)
-        d.addCallback(privs1)
-        return d
-
-    #
-    # This test fails because /calendars/users/cdaboo/ actually is a
-    # different resource (ie. the /calendars/__uids__/... URL would be
-    # different) when the GUID for cdaboo changes.
-    #
-    # The test needs to create a fixed resource with access granted to
-    # the old cdaboo; calendar homes no longer do this.
-    #
-    # Using the __uids__ URL won't work either because the old URL
-    # goes away with the old account.
-    #
-    test_guidchange.todo = &quot;Test no longer works.&quot;
-
-    def _checkPrivileges(self, resource, url, principal, privilege, allowed):
-        request = SimpleRequest(self.site, &quot;GET&quot;, &quot;/&quot;)
-
-        def gotResource(resource):
-            d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
-            if allowed:
-                def onError(f):
-                    f.trap(AccessDeniedError)
-                    #print(resource.readDeadProperty(davxml.ACL).toxml())
-                    self.fail(&quot;%s should have %s privilege on %r&quot; % (principal, privilege.sname(), resource))
-                d.addErrback(onError)
-            else:
-                def onError(f):
-                    f.trap(AccessDeniedError)
-                def onSuccess(_):
-                    #print(resource.readDeadProperty(davxml.ACL).toxml())
-                    self.fail(&quot;%s should not have %s privilege on %r&quot; % (principal, privilege.sname(), resource))
-                d.addCallback(onSuccess)
-                d.addErrback(onError)
-            def _commit(ignore):
-                maybeCommit(request)
-            d.addBoth(_commit)
-            return d
-
-        d = request.locateResource(url)
-        d.addCallback(gotResource)
-        return d
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_ldapdirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_ldapdirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_ldapdirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_ldapdirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,1856 +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 __future__ import print_function
-
-try:
-    from twistedcaldav.directory.ldapdirectory import (
-        buildFilter, buildFilterFromTokens, LdapDirectoryService,
-        MissingGuidException, MissingRecordNameException,
-        normalizeDNstr, dnContainedIn
-    )
-    from twistedcaldav.directory.util import splitIntoBatches
-    from twistedcaldav.test.util import proxiesFile
-    from twistedcaldav.directory.calendaruserproxyloader import (
-        XMLCalendarUserProxyLoader
-    )
-    from twistedcaldav.directory import calendaruserproxy
-    from twistedcaldav.directory.directory import (
-        GroupMembershipCache, GroupMembershipCacheUpdater
-    )
-    from twisted.internet.defer import inlineCallbacks
-    from string import maketrans
-    import ldap
-except ImportError:
-    print(&quot;Skipping because ldap module not installed&quot;)
-else:
-    from twistedcaldav.test.util import TestCase
-
-    class BuildFilterTestCase(TestCase):
-
-        def test_buildFilter(self):
-            mapping = {
-                &quot;recordName&quot;: &quot;uid&quot;,
-                &quot;fullName&quot;: &quot;cn&quot;,
-                &quot;emailAddresses&quot;: &quot;mail&quot;,
-                &quot;firstName&quot;: &quot;givenName&quot;,
-                &quot;lastName&quot;: &quot;sn&quot;,
-                &quot;guid&quot;: &quot;generateduid&quot;,
-                &quot;memberIDAttr&quot;: &quot;generateduid&quot;,
-            }
-
-            entries = [
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;)
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(cn=mor*)(mail=mor*)(givenName=mor*)(sn=mor*)))&quot;,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor(&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor)&quot;, True, u&quot;contains&quot;),
-                        (&quot;firstName&quot;, &quot;mor*&quot;, True, u&quot;exact&quot;),
-                        (&quot;lastName&quot;, &quot;mor\\&quot;, True, u&quot;starts-with&quot;)
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(cn=mor\\28*)(mail=*mor\\29*)(givenName=mor\\2a)(sn=mor\\5c*)))&quot;,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(cn=mor*))&quot;,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;contains&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;equals&quot;),
-                        (&quot;invalid&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;and&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(&amp;(cn=*mor*)(mail=mor)))&quot;,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;invalid&quot;, &quot;mor&quot;, True, u&quot;contains&quot;),
-                        (&quot;invalid&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;and&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: None,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [],
-                    &quot;operand&quot;: &quot;and&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: None,
-                    &quot;optimize&quot;: False,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;fullName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;users&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(&amp;(givenName=mor*)(sn=sag*))(&amp;(givenName=sag*)(sn=mor*))))&quot;,
-                    &quot;optimize&quot;: True,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;fullName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;groups&quot;,
-                    &quot;expected&quot;: None,
-                    &quot;optimize&quot;: True,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;fullName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;emailAddresses&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;firstName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                        (&quot;lastName&quot;, &quot;sag&quot;, True, u&quot;starts-with&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;groups&quot;,
-                    &quot;expected&quot;: None,
-                    &quot;optimize&quot;: True,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;guid&quot;, &quot;xyzzy&quot;, True, u&quot;equals&quot;),
-                        (&quot;guid&quot;, &quot;plugh&quot;, True, u&quot;equals&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;groups&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(generateduid=xyzzy)(generateduid=plugh)))&quot;,
-                    &quot;optimize&quot;: True,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;contains&quot;),
-                        (&quot;fullName&quot;, &quot;sag&quot;, True, u&quot;contains&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;locations&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(cn=*mor*)(cn=*sag*)))&quot;,
-                    &quot;optimize&quot;: True,
-                },
-                {
-                    &quot;fields&quot;: [
-                        (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;contains&quot;),
-                        (&quot;fullName&quot;, &quot;sag&quot;, True, u&quot;contains&quot;),
-                    ],
-                    &quot;operand&quot;: &quot;or&quot;,
-                    &quot;recordType&quot;: &quot;resources&quot;,
-                    &quot;expected&quot;: &quot;(&amp;(uid=*)(generateduid=*)(|(cn=*mor*)(cn=*sag*)))&quot;,
-                    &quot;optimize&quot;: True,
-                },
-            ]
-            for entry in entries:
-                self.assertEquals(
-                    buildFilter(entry[&quot;recordType&quot;], mapping, entry[&quot;fields&quot;],
-                                operand=entry[&quot;operand&quot;], optimizeMultiName=entry[&quot;optimize&quot;]),
-                    entry[&quot;expected&quot;]
-                )
-
-
-    class BuildFilterFromTokensTestCase(TestCase):
-
-        def test_buildFilterFromTokens(self):
-
-            entries = [
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(a=b)(|(cn=*foo*)(mail=foo*)))&quot;,
-                    &quot;extra&quot;: &quot;(a=b)&quot;,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;foo&quot;, &quot;oo&quot;, &quot;fo&quot;, &quot;bar&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(a=b)(|(cn=*bar*)(mail=bar*))(|(cn=*foo*)(mail=foo*)))&quot;,
-                    &quot;extra&quot;: &quot;(a=b)&quot;,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;fo&quot;, &quot;foo&quot;, &quot;foooo&quot;, &quot;ooo&quot;, &quot;fooo&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(a=b)(|(cn=*foooo*)(mail=foooo*)))&quot;,
-                    &quot;extra&quot;: &quot;(a=b)&quot;,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;, &quot;mailAliases&quot;],
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(a=b)(|(cn=*foo*)(mail=foo*)(mailAliases=foo*)))&quot;,
-                    &quot;extra&quot;: &quot;(a=b)&quot;,
-                },
-                {
-                    &quot;tokens&quot;: [],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: None,
-                    &quot;extra&quot;: None,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;bar&quot;],
-                    &quot;mapping&quot;: {},
-                    &quot;expected&quot;: None,
-                    &quot;extra&quot;: None,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;bar&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(mail=bar*)(mail=foo*))&quot;,
-                    &quot;extra&quot;: None,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;bar&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(|(cn=*bar*)(mail=bar*))(|(cn=*foo*)(mail=foo*)))&quot;,
-                    &quot;extra&quot;: None,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;bar&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: [&quot;mail&quot;, &quot;mailAliases&quot;],
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(|(cn=*bar*)(mail=bar*)(mailAliases=bar*))(|(cn=*foo*)(mail=foo*)(mailAliases=foo*)))&quot;,
-                    &quot;extra&quot;: None,
-                },
-                {
-                    &quot;tokens&quot;: [&quot;foo&quot;, &quot;bar&quot;, &quot;baz(&quot;],
-                    &quot;mapping&quot;: {
-                        &quot;fullName&quot;: &quot;cn&quot;,
-                        &quot;emailAddresses&quot;: &quot;mail&quot;,
-                    },
-                    &quot;expected&quot;: &quot;(&amp;(|(cn=*bar*)(mail=bar*))(|(cn=*baz\\28*)(mail=baz\\28*))(|(cn=*foo*)(mail=foo*)))&quot;,
-                    &quot;extra&quot;: None,
-                },
-            ]
-            for entry in entries:
-                self.assertEquals(
-                    buildFilterFromTokens(None, entry[&quot;mapping&quot;], entry[&quot;tokens&quot;], extra=entry[&quot;extra&quot;]),
-                    entry[&quot;expected&quot;]
-                )
-
-
-    class StubList(object):
-        def __init__(self, wrapper):
-            self.ldap = wrapper
-
-        def startSearch(self, base, scope, filterstr, attrList=None,
-                        timeout=-1, sizelimit=0):
-            self.base = base
-            self.scope = scope
-            self.filterstr = filterstr
-            self.attrList = attrList
-            self.timeout = timeout
-            self.sizelimit = sizelimit
-
-        def processResults(self):
-            self.allResults = self.ldap.search_s(self.base, self.scope,
-                                                 self.filterstr,
-                                                 attrlist=self.attrList)
-
-
-    class StubAsync(object):
-        def List(self, wrapper):
-            return StubList(wrapper)
-
-
-    class LdapDirectoryTestWrapper(object):
-        &quot;&quot;&quot;
-        A test stub which replaces search_s( ) with a version that will return
-        whatever you have previously called addTestResults( ) with.
-        &quot;&quot;&quot;
-
-        def __init__(self, actual, records):
-            self.actual = actual
-            self.async = StubAsync()
-
-            # Test data returned from search_s.
-            # Note that some DNs have various extra whitespace added and mixed
-            # up case since LDAP is pretty loose about these.
-            self.records = records
-
-
-        def search_s(self, base, scope, filterstr=&quot;(objectClass=*)&quot;,
-                     attrlist=None):
-            &quot;&quot;&quot; A simple implementation of LDAP search filter processing &quot;&quot;&quot;
-
-            base = normalizeDNstr(base)
-            results = []
-            for dn, attrs in self.records:
-                dn = normalizeDNstr(dn)
-                if dn == base:
-                    results.append((&quot;ignored&quot;, (dn, attrs)))
-                elif dnContainedIn(ldap.dn.str2dn(dn), ldap.dn.str2dn(base)):
-                    if filterstr in (&quot;(objectClass=*)&quot;, &quot;(!(objectClass=organizationalUnit))&quot;):
-                        results.append((&quot;ignored&quot;, (dn, attrs)))
-                    else:
-                        trans = maketrans(&quot;&amp;(|)&quot;, &quot;   |&quot;)
-                        fragments = filterstr.encode(&quot;utf-8&quot;).translate(trans).split(&quot;|&quot;)
-                        for fragment in fragments:
-                            if not fragment:
-                                continue
-                            fragment = fragment.strip()
-                            key, value = fragment.split(&quot;=&quot;)
-                            if value in attrs.get(key, []):
-                                results.append((&quot;ignored&quot;, (dn, attrs)))
-                                break
-                            elif value == &quot;*&quot; and key in attrs:
-                                results.append((&quot;ignored&quot;, (dn, attrs)))
-                                break
-
-            return results
-
-
-    class LdapDirectoryServiceTestCase(TestCase):
-
-        nestedUsingDifferentAttributeUsingDN = (
-            (
-                (
-                    &quot;cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com&quot;,
-                    {
-                        'cn': ['recursive1_coasts'],
-                        'apple-generateduid': ['recursive1_coasts'],
-                        'uniqueMember': [
-                            'uid=wsanchez ,cn=users, dc=eXAMple,dc=com',
-                        ],
-                        'nestedGroups': [
-                            'cn=recursive2_coasts,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    &quot;cn=recursive2_coasts,cn=groups,dc=example,dc=com&quot;,
-                    {
-                        'cn': ['recursive2_coasts'],
-                        'apple-generateduid': ['recursive2_coasts'],
-                        'uniqueMember': [
-                            'uid=cdaboo,cn=users,dc=example,dc=com',
-                        ],
-                        'nestedGroups': [
-                            'cn=recursive1_coasts,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=both_coasts,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['both_coasts'],
-                        'apple-generateduid': ['both_coasts'],
-                        'nestedGroups': [
-                            'cn=right_coast,cn=groups,dc=example,dc=com',
-                            'cn=left_coast,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=right_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['right_coast'],
-                        'apple-generateduid': ['right_coast'],
-                        'uniqueMember': [
-                            'uid=cdaboo,cn=users,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=left_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['left_coast'],
-                        'apple-generateduid': ['left_coast'],
-                        'uniqueMember': [
-                            'uid=wsanchez, cn=users,dc=example,dc=com',
-                            'uid=lecroy,cn=users,dc=example,dc=com',
-                            'uid=dreid,cn=users,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestamanda'],
-                        # purposely throw in an un-normalized GUID
-                        'apple-generateduid': ['9dc04a70-e6dd-11df-9492-0800200c9a66'],
-                        'sn': ['Test'],
-                        'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                        'givenName': ['Amanda'],
-                        'cn': ['Amanda Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestbetty,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestbetty'],
-                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
-                        'sn': ['Test'],
-                        'mail': ['odtestbetty@example.com'],
-                        'givenName': ['Betty'],
-                        'cn': ['Betty Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestcarlene,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestcarlene'],
-                        # Note: no guid here, to test this record is skipped
-                        'sn': ['Test'],
-                        'mail': ['odtestcarlene@example.com'],
-                        'givenName': ['Carlene'],
-                        'cn': ['Carlene Test']
-                    }
-                ),
-                (
-                    &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['cdaboo'],
-                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                        'sn': ['Daboo'],
-                        'mail': ['daboo@example.com'],
-                        'givenName': ['Cyrus'],
-                        'cn': ['Cyrus Daboo']
-                    }
-                ),
-                (
-                    &quot;uid=wsanchez  ,  cn=users  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['wsanchez'],
-                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
-                        'sn': ['Sanchez'],
-                        'mail': ['wsanchez@example.com'],
-                        'givenName': ['Wilfredo'],
-                        'cn': ['Wilfredo Sanchez']
-                    }
-                ),
-                (
-                    &quot;uid=testresource  ,  cn=resources  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['testresource'],
-                        'apple-generateduid': ['D91B21B9-B856-495A-8E36-0E5AD54EFB3A'],
-                        'sn': ['Resource'],
-                        'givenName': ['Test'],
-                        'cn': ['Test Resource'],
-                        # purposely throw in an un-normalized GUID
-                        'read-write-proxy': ['6423f94a-6b76-4a3a-815b-d52cfd77935d'],
-                        'read-only-proxy': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                    }
-                ),
-                (
-                    &quot;uid=testresource2  ,  cn=resources  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['testresource2'],
-                        'apple-generateduid': ['753E5A60-AFFD-45E4-BF2C-31DAB459353F'],
-                        'sn': ['Resource2'],
-                        'givenName': ['Test'],
-                        'cn': ['Test Resource2'],
-                        'read-write-proxy': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
-                    }
-                ),
-            ),
-            {
-                &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,
-                &quot;queryLocationsImplicitly&quot;: True,
-                &quot;restrictEnabledRecords&quot;: True,
-                &quot;restrictToGroup&quot;: &quot;both_coasts&quot;,
-                &quot;recordTypes&quot;: (&quot;users&quot;, &quot;groups&quot;, &quot;locations&quot;, &quot;resources&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;apple-generateduid&quot;,
-                    &quot;users&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Users&quot;,
-                        &quot;attr&quot;: &quot;uid&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;enable-calendar&quot;,  # attribute controlling calendaring
-                        &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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;groups&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Groups&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;, &quot;emailAliases&quot;],
-                        },
-                    },
-                    &quot;locations&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Places&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &quot;calendarEnabledValue&quot;: &quot;yes&quot;,  # &quot;True&quot; value of above attribute
-                        &quot;associatedAddressAttr&quot;: &quot;assocAddr&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;&quot;,  # old style, single string
-                        },
-                    },
-                    &quot;resources&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Resources&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;: [],  # new style, array
-                        },
-                    },
-                    &quot;addresses&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Buildings&quot;,
-                        &quot;geoAttr&quot;: &quot;coordinates&quot;,
-                        &quot;streetAddressAttr&quot;: &quot;postal&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;uniqueMember&quot;,  # how members are specified
-                    &quot;nestedGroupsAttr&quot;: &quot;nestedGroups&quot;,  # how nested groups are specified
-                    &quot;memberIdAttr&quot;: &quot;&quot;,  # which attribute the above refer to
-                },
-                &quot;resourceSchema&quot;: {
-                    &quot;resourceInfoAttr&quot;: &quot;apple-resource-info&quot;,  # contains location/resource info
-                    &quot;autoScheduleAttr&quot;: None,
-                    &quot;proxyAttr&quot;: &quot;read-write-proxy&quot;,
-                    &quot;readOnlyProxyAttr&quot;: &quot;read-only-proxy&quot;,
-                    &quot;autoAcceptGroupAttr&quot;: None,
-                },
-                &quot;poddingSchema&quot;: {
-                    &quot;serverIdAttr&quot;: &quot;server-id&quot;,  # maps to augments server-id
-                },
-            }
-        )
-        nestedUsingSameAttributeUsingDN = (
-            (
-                (
-                    &quot;cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com&quot;,
-                    {
-                        'cn': ['recursive1_coasts'],
-                        'apple-generateduid': ['recursive1_coasts'],
-                        'uniqueMember': [
-                            'uid=wsanchez ,cn=users, dc=eXAMple,dc=com',
-                            'cn=recursive2_coasts,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    &quot;cn=recursive2_coasts,cn=groups,dc=example,dc=com&quot;,
-                    {
-                        'cn': ['recursive2_coasts'],
-                        'apple-generateduid': ['recursive2_coasts'],
-                        'uniqueMember': [
-                            'uid=cdaboo,cn=users,dc=example,dc=com',
-                            'cn=recursive1_coasts,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=both_coasts,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['both_coasts'],
-                        'apple-generateduid': ['both_coasts'],
-                        'uniqueMember': [
-                            'cn=right_coast,cn=groups,dc=example,dc=com',
-                            'cn=left_coast,cn=groups,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=right_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['right_coast'],
-                        'apple-generateduid': ['right_coast'],
-                        'uniqueMember': [
-                            'uid=cdaboo,cn=users,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    'cn=left_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['left_coast'],
-                        'apple-generateduid': ['left_coast'],
-                        'uniqueMember': [
-                            'uid=wsanchez, cn=users,dc=example,dc=com',
-                            'uid=lecroy,cn=users,dc=example,dc=com',
-                            'uid=dreid,cn=users,dc=example,dc=com',
-                        ],
-                    }
-                ),
-                (
-                    &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestamanda'],
-                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
-                        'sn': ['Test'],
-                        'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                        'givenName': ['Amanda'],
-                        'cn': ['Amanda Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestbetty,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestbetty'],
-                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
-                        'sn': ['Test'],
-                        'mail': ['odtestbetty@example.com'],
-                        'givenName': ['Betty'],
-                        'cn': ['Betty Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestcarlene,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestcarlene'],
-                        # Note: no guid here, to test this record is skipped
-                        'sn': ['Test'],
-                        'mail': ['odtestcarlene@example.com'],
-                        'givenName': ['Carlene'],
-                        'cn': ['Carlene Test']
-                    }
-                ),
-                (
-                    &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['cdaboo'],
-                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                        'sn': ['Daboo'],
-                        'mail': ['daboo@example.com'],
-                        'givenName': ['Cyrus'],
-                        'cn': ['Cyrus Daboo']
-                    }
-                ),
-                (
-                    &quot;uid=wsanchez  ,  cn=users  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['wsanchez'],
-                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
-                        'sn': ['Sanchez'],
-                        'mail': ['wsanchez@example.com'],
-                        'givenName': ['Wilfredo'],
-                        'cn': ['Wilfredo Sanchez']
-                    }
-                ),
-            ),
-            {
-                &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,
-                &quot;queryLocationsImplicitly&quot;: True,
-                &quot;restrictEnabledRecords&quot;: True,
-                &quot;restrictToGroup&quot;: &quot;both_coasts&quot;,
-                &quot;recordTypes&quot;: (&quot;users&quot;, &quot;groups&quot;, &quot;locations&quot;, &quot;resources&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;apple-generateduid&quot;,
-                    &quot;users&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Users&quot;,
-                        &quot;attr&quot;: &quot;uid&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;enable-calendar&quot;,  # attribute controlling calendaring
-                        &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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;groups&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Groups&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;locations&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Places&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;&quot;,  # old style, single string
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;resources&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Resources&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;: [],  # new style, array
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                },
-                &quot;groupSchema&quot;: {
-                    &quot;membersAttr&quot;: &quot;uniqueMember&quot;,  # how members are specified
-                    &quot;nestedGroupsAttr&quot;: &quot;&quot;,  # how nested groups are specified
-                    &quot;memberIdAttr&quot;: &quot;&quot;,  # which attribute the above refer to
-                },
-                &quot;resourceSchema&quot;: {
-                    &quot;resourceInfoAttr&quot;: &quot;apple-resource-info&quot;,  # contains location/resource info
-                    &quot;autoScheduleAttr&quot;: None,
-                    &quot;proxyAttr&quot;: None,
-                    &quot;readOnlyProxyAttr&quot;: None,
-                    &quot;autoAcceptGroupAttr&quot;: None,
-                },
-                &quot;poddingSchema&quot;: {
-                    &quot;serverIdAttr&quot;: &quot;server-id&quot;,  # maps to augments server-id
-                },
-            }
-        )
-        nestedUsingDifferentAttributeUsingGUID = (
-            (
-                (
-                    &quot;cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com&quot;,
-                    {
-                        'cn': ['recursive1_coasts'],
-                        'apple-generateduid': ['recursive1_coasts'],
-                        'uniqueMember': [
-                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                        ],
-                        'nestedGroups': [
-                            'recursive2_coasts',
-                        ],
-                    }
-                ),
-                (
-                    &quot;cn=recursive2_coasts,cn=groups,dc=example,dc=com&quot;,
-                    {
-                        'cn': ['recursive2_coasts'],
-                        'apple-generateduid': ['recursive2_coasts'],
-                        'uniqueMember': [
-                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                        ],
-                        'nestedGroups': [
-                            'recursive1_coasts',
-                        ],
-                    }
-                ),
-                (
-                    'cn=both_coasts,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['both_coasts'],
-                        'apple-generateduid': ['both_coasts'],
-                        'nestedGroups': [
-                            'right_coast',
-                            'left_coast',
-                        ],
-                    }
-                ),
-                (
-                    'cn=right_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['right_coast'],
-                        'apple-generateduid': ['right_coast'],
-                        'uniqueMember': [
-                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                        ],
-                    }
-                ),
-                (
-                    'cn=left_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['left_coast'],
-                        'apple-generateduid': ['left_coast'],
-                        'uniqueMember': [
-                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                        ],
-                    }
-                ),
-                (
-                    &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestamanda'],
-                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
-                        'sn': ['Test'],
-                        'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                        'givenName': ['Amanda'],
-                        'cn': ['Amanda Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestbetty,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestbetty'],
-                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
-                        'sn': ['Test'],
-                        'mail': ['odtestbetty@example.com'],
-                        'givenName': ['Betty'],
-                        'cn': ['Betty Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestcarlene,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestcarlene'],
-                        # Note: no guid here, to test this record is skipped
-                        'sn': ['Test'],
-                        'mail': ['odtestcarlene@example.com'],
-                        'givenName': ['Carlene'],
-                        'cn': ['Carlene Test']
-                    }
-                ),
-                (
-                    &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['cdaboo'],
-                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                        'sn': ['Daboo'],
-                        'mail': ['daboo@example.com'],
-                        'givenName': ['Cyrus'],
-                        'cn': ['Cyrus Daboo']
-                    }
-                ),
-                (
-                    &quot;uid=wsanchez  ,  cn=users  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['wsanchez'],
-                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
-                        'sn': ['Sanchez'],
-                        'mail': ['wsanchez@example.com'],
-                        'givenName': ['Wilfredo'],
-                        'cn': ['Wilfredo Sanchez']
-                    }
-                ),
-            ),
-            {
-                &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,
-                &quot;queryLocationsImplicitly&quot;: True,
-                &quot;restrictEnabledRecords&quot;: True,
-                &quot;restrictToGroup&quot;: &quot;both_coasts&quot;,
-                &quot;recordTypes&quot;: (&quot;users&quot;, &quot;groups&quot;, &quot;locations&quot;, &quot;resources&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;apple-generateduid&quot;,
-                    &quot;users&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Users&quot;,
-                        &quot;attr&quot;: &quot;uid&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;enable-calendar&quot;,  # attribute controlling calendaring
-                        &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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;groups&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Groups&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;locations&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Places&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;&quot;,  # old style, single string
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;resources&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Resources&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;: [],  # new style, array
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                },
-                &quot;groupSchema&quot;: {
-                    &quot;membersAttr&quot;: &quot;uniqueMember&quot;,  # how members are specified
-                    &quot;nestedGroupsAttr&quot;: &quot;nestedGroups&quot;,  # how nested groups are specified
-                    &quot;memberIdAttr&quot;: &quot;apple-generateduid&quot;,  # which attribute the above refer to
-                },
-                &quot;resourceSchema&quot;: {
-                    &quot;resourceInfoAttr&quot;: &quot;apple-resource-info&quot;,  # contains location/resource info
-                    &quot;autoScheduleAttr&quot;: None,
-                    &quot;proxyAttr&quot;: None,
-                    &quot;readOnlyProxyAttr&quot;: None,
-                    &quot;autoAcceptGroupAttr&quot;: None,
-                },
-                &quot;poddingSchema&quot;: {
-                    &quot;serverIdAttr&quot;: &quot;server-id&quot;,  # maps to augments server-id
-                },
-            }
-        )
-        nestedUsingSameAttributeUsingGUID = (
-            (
-                (
-                    &quot;cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com&quot;,
-                    {
-                        'cn': ['recursive1_coasts'],
-                        'apple-generateduid': ['recursive1_coasts'],
-                        'uniqueMember': [
-                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                            'recursive2_coasts',
-                        ],
-                    }
-                ),
-                (
-                    &quot;cn=recursive2_coasts,cn=groups,dc=example,dc=com&quot;,
-                    {
-                        'cn': ['recursive2_coasts'],
-                        'apple-generateduid': ['recursive2_coasts'],
-                        'uniqueMember': [
-                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                            'recursive1_coasts',
-                        ],
-                    }
-                ),
-                (
-                    'cn=both_coasts,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['both_coasts'],
-                        'apple-generateduid': ['both_coasts'],
-                        'uniqueMember': [
-                            'right_coast',
-                            'left_coast',
-                        ],
-                    }
-                ),
-                (
-                    'cn=right_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['right_coast'],
-                        'apple-generateduid': ['right_coast'],
-                        'uniqueMember': [
-                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
-                        ],
-                    }
-                ),
-                (
-                    'cn=left_coast,cn=groups,dc=example,dc=com',
-                    {
-                        'cn': ['left_coast'],
-                        'apple-generateduid': ['left_coast'],
-                        'uniqueMember': [
-                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
-                        ],
-                    }
-                ),
-                (
-                    &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestamanda'],
-                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
-                        'sn': ['Test'],
-                        'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                        'givenName': ['Amanda'],
-                        'cn': ['Amanda Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestbetty,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestbetty'],
-                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
-                        'sn': ['Test'],
-                        'mail': ['odtestbetty@example.com'],
-                        'givenName': ['Betty'],
-                        'cn': ['Betty Test']
-                    }
-                ),
-                (
-                    &quot;uid=odtestcarlene,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['odtestcarlene'],
-                        # Note: no guid here, to test this record is skipped
-                        'sn': ['Test'],
-                        'mail': ['odtestcarlene@example.com'],
-                        'givenName': ['Carlene'],
-                        'cn': ['Carlene Test']
-                    }
-                ),
-                (
-                    &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;,
-                    {
-                        'uid': ['cdaboo'],
-                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                        'sn': ['Daboo'],
-                        'mail': ['daboo@example.com'],
-                        'givenName': ['Cyrus'],
-                        'cn': ['Cyrus Daboo']
-                    }
-                ),
-                (
-                    &quot;uid=wsanchez  ,  cn=users  , dc=example,dc=com&quot;,
-                    {
-                        'uid': ['wsanchez'],
-                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
-                        'sn': ['Sanchez'],
-                        'mail': ['wsanchez@example.com'],
-                        'givenName': ['Wilfredo'],
-                        'cn': ['Wilfredo Sanchez']
-                    }
-                ),
-            ),
-            {
-                &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,
-                &quot;queryLocationsImplicitly&quot;: True,
-                &quot;restrictEnabledRecords&quot;: True,
-                &quot;restrictToGroup&quot;: &quot;both_coasts&quot;,
-                &quot;recordTypes&quot;: (&quot;users&quot;, &quot;groups&quot;, &quot;locations&quot;, &quot;resources&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;apple-generateduid&quot;,
-                    &quot;users&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Users&quot;,
-                        &quot;attr&quot;: &quot;uid&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;enable-calendar&quot;,  # attribute controlling calendaring
-                        &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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;groups&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Groups&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;&quot;,  # 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;, &quot;emailAliases&quot;],
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;locations&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Places&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;&quot;,  # old style, single string
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                    &quot;resources&quot;: {
-                        &quot;rdn&quot;: &quot;cn=Resources&quot;,
-                        &quot;attr&quot;: &quot;cn&quot;,  # used only to synthesize email address
-                        &quot;emailSuffix&quot;: None,  # used only to synthesize email address
-                        &quot;filter&quot;: &quot;(objectClass=apple-resource)&quot;,  # additional filter for this type
-                        &quot;calendarEnabledAttr&quot;: &quot;&quot;,  # attribute controlling calendaring
-                        &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;: [],  # new style, array
-                            &quot;firstName&quot;: &quot;givenName&quot;,
-                            &quot;lastName&quot;: &quot;sn&quot;,
-                        },
-                    },
-                },
-                &quot;groupSchema&quot;: {
-                    &quot;membersAttr&quot;: &quot;uniqueMember&quot;,  # how members are specified
-                    &quot;nestedGroupsAttr&quot;: &quot;&quot;,  # how nested groups are specified
-                    &quot;memberIdAttr&quot;: &quot;apple-generateduid&quot;,  # which attribute the above refer to
-                },
-                &quot;resourceSchema&quot;: {
-                    &quot;resourceInfoAttr&quot;: &quot;apple-resource-info&quot;,  # contains location/resource info
-                    &quot;autoScheduleAttr&quot;: None,
-                    &quot;proxyAttr&quot;: None,
-                    &quot;readOnlyProxyAttr&quot;: None,
-                    &quot;autoAcceptGroupAttr&quot;: None,
-                },
-                &quot;poddingSchema&quot;: {
-                    &quot;serverIdAttr&quot;: &quot;server-id&quot;,  # maps to augments server-id
-                },
-            }
-        )
-
-
-        def setupService(self, scenario):
-            self.service = LdapDirectoryService(scenario[1])
-            self.service.ldap = LdapDirectoryTestWrapper(self.service.ldap, scenario[0])
-            self.patch(ldap, &quot;async&quot;, StubAsync())
-
-
-        def test_ldapWrapper(self):
-            &quot;&quot;&quot;
-            Exercise the fake search_s implementation
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            # Get all groups
-            self.assertEquals(
-                len(self.service.ldap.search_s(&quot;cn=groups,dc=example,dc=com&quot;, 0, &quot;(objectClass=*)&quot;, [])), 5)
-
-            self.assertEquals(
-                len(self.service.ldap.search_s(&quot;cn=recursive1_coasts,cn=groups,dc=example,dc=com&quot;, 2, &quot;(objectClass=*)&quot;, [])), 1)
-
-            self.assertEquals(
-                len(self.service.ldap.search_s(&quot;cn=groups,dc=example,dc=com&quot;, 0, &quot;(|(apple-generateduid=right_coast)(apple-generateduid=left_coast))&quot;, [])), 2)
-
-
-        def test_ldapRecordCreation(self):
-            &quot;&quot;&quot;
-            Exercise _ldapResultToRecord(), which converts a dictionary
-            of LDAP attributes into an LdapDirectoryRecord
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            # User without enabled-for-calendaring specified
-
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            guid = '9DC04A70-E6DD-11DF-9492-0800200C9A66'
-            attrs = {
-                'uid': ['odtestamanda'],
-                'apple-generateduid': [guid],
-                'sn': ['Test'],
-                'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                'givenName': ['Amanda'],
-                'cn': ['Amanda Test']
-            }
-
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_users
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(
-                record.emailAddresses,
-                set(['alternate@example.com', 'odtestamanda@example.com'])
-            )
-            self.assertEquals(record.shortNames, ('odtestamanda',))
-            self.assertEquals(record.fullName, 'Amanda Test')
-            self.assertEquals(record.firstName, 'Amanda')
-            self.assertEquals(record.lastName, 'Test')
-            self.assertEquals(record.serverID, None)
-            self.assertFalse(record.enabledForCalendaring)
-
-            # User with enabled-for-calendaring specified
-
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            guid = '9DC04A70-E6DD-11DF-9492-0800200C9A66'
-            attrs = {
-                'uid': ['odtestamanda'],
-                'apple-generateduid': [guid],
-                'enable-calendar': [&quot;yes&quot;],
-                'sn': ['Test'],
-                'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                'givenName': ['Amanda'],
-                'cn': ['Amanda Test']
-            }
-
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_users
-            )
-            self.assertTrue(record.enabledForCalendaring)
-
-            # User with &quot;podding&quot; info
-
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            guid = '9DC04A70-E6DD-11DF-9492-0800200C9A66'
-            attrs = {
-                'uid': ['odtestamanda'],
-                'apple-generateduid': [guid],
-                'cn': ['Amanda Test'],
-                'server-id': [&quot;test-server-id&quot;],
-            }
-
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_users
-            )
-            self.assertEquals(record.serverID, &quot;test-server-id&quot;)
-
-            # User missing guidAttr
-
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            attrs = {
-                'uid': ['odtestamanda'],
-                'cn': ['Amanda Test'],
-            }
-
-            self.assertRaises(
-                MissingGuidException,
-                self.service._ldapResultToRecord, dn, attrs,
-                self.service.recordType_users
-            )
-
-            # User missing record name
-
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            attrs = {
-                'apple-generateduid': ['9ABDD881-B3A4-4065-9DA7-12095F40A898'],
-                'cn': ['Amanda Test'],
-            }
-
-            self.assertRaises(
-                MissingRecordNameException,
-                self.service._ldapResultToRecord, dn, attrs,
-                self.service.recordType_users
-            )
-
-            # Group with direct user members and nested group
-
-            dn = &quot;cn=odtestgrouptop,cn=groups,dc=example,dc=com&quot;
-            guid = '6C6CD280-E6E3-11DF-9492-0800200C9A66'
-            attrs = {
-                'apple-generateduid': [guid],
-                'uniqueMember': [
-                    'uid=odtestamanda,cn=users,dc=example,dc=com',
-                    'uid=odtestbetty,cn=users,dc=example,dc=com',
-                    'cn=odtestgroupb,cn=groups,dc=example,dc=com',
-                ],
-                'cn': ['odtestgrouptop']
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_groups
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(
-                record.memberGUIDs(),
-                set(
-                    [
-                        'cn=odtestgroupb,cn=groups,dc=example,dc=com',
-                        'uid=odtestamanda,cn=users,dc=example,dc=com',
-                        'uid=odtestbetty,cn=users,dc=example,dc=com',
-                    ]
-                )
-            )
-
-            # Group with illegal DN value in members
-
-            dn = &quot;cn=odtestgrouptop,cn=groups,dc=example,dc=com&quot;
-            guid = '6C6CD280-E6E3-11DF-9492-0800200C9A66'
-            attrs = {
-                'apple-generateduid': [guid],
-                'uniqueMember': [
-                    'uid=odtestamanda,cn=users,dc=example,dc=com',
-                    'uid=odtestbetty ,cn=users,dc=example,dc=com',
-                    'cn=odtestgroupb+foo,cn=groups,dc=example,dc=com',
-                ],
-                'cn': ['odtestgrouptop']
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_groups
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(
-                record.memberGUIDs(),
-                set(
-                    [
-                        'uid=odtestamanda,cn=users,dc=example,dc=com',
-                        'uid=odtestbetty,cn=users,dc=example,dc=com',
-                    ]
-                )
-            )
-
-            # Resource with delegates, autoSchedule = True, and autoAcceptGroup
-
-            dn = &quot;cn=odtestresource,cn=resources,dc=example,dc=com&quot;
-            guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
-            attrs = {
-                'apple-generateduid': [guid],
-                'cn': ['odtestresource'],
-                'apple-resource-info': [&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-&lt;dict&gt;
-&lt;key&gt;com.apple.WhitePagesFramework&lt;/key&gt;
-&lt;dict&gt;
- &lt;key&gt;AutoAcceptsInvitation&lt;/key&gt;
-&lt;true/&gt;
-&lt;key&gt;CalendaringDelegate&lt;/key&gt;
-&lt;string&gt;6C6CD280-E6E3-11DF-9492-0800200C9A66&lt;/string&gt;
-&lt;key&gt;ReadOnlyCalendaringDelegate&lt;/key&gt;
-&lt;string&gt;6AA1AE12-592F-4190-A069-547CD83C47C0&lt;/string&gt;
-&lt;key&gt;AutoAcceptGroup&lt;/key&gt;
-&lt;string&gt;77A8EB52-AA2A-42ED-8843-B2BEE863AC70&lt;/string&gt;
-&lt;/dict&gt;
-&lt;/dict&gt;
-&lt;/plist&gt;&quot;&quot;&quot;]
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_resources
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(
-                record.externalProxies(),
-                set(['6C6CD280-E6E3-11DF-9492-0800200C9A66'])
-            )
-            self.assertEquals(
-                record.externalReadOnlyProxies(),
-                set(['6AA1AE12-592F-4190-A069-547CD83C47C0'])
-            )
-            self.assertTrue(record.autoSchedule)
-            self.assertEquals(
-                record.autoAcceptGroup, '77A8EB52-AA2A-42ED-8843-B2BEE863AC70'
-            )
-
-            # Resource with no delegates and autoSchedule = False
-
-            dn = &quot;cn=odtestresource,cn=resources,dc=example,dc=com&quot;
-            guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
-            attrs = {
-                'apple-generateduid': [guid],
-                'cn': ['odtestresource'],
-                'apple-resource-info': [&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-&lt;dict&gt;
-&lt;key&gt;com.apple.WhitePagesFramework&lt;/key&gt;
-&lt;dict&gt;
- &lt;key&gt;AutoAcceptsInvitation&lt;/key&gt;
-&lt;false/&gt;
-&lt;/dict&gt;
-&lt;/dict&gt;
-&lt;/plist&gt;&quot;&quot;&quot;]
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_resources
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(record.externalProxies(), set())
-            self.assertEquals(record.externalReadOnlyProxies(), set())
-            self.assertFalse(record.autoSchedule)
-            self.assertEquals(record.autoAcceptGroup, &quot;&quot;)
-
-            # Now switch off the resourceInfoAttr and switch to individual
-            # attributes...
-            self.service.resourceSchema = {
-                &quot;resourceInfoAttr&quot;: &quot;&quot;,
-                &quot;autoScheduleAttr&quot;: &quot;auto-schedule&quot;,
-                &quot;autoScheduleEnabledValue&quot;: &quot;yes&quot;,
-                &quot;proxyAttr&quot;: &quot;proxy&quot;,
-                &quot;readOnlyProxyAttr&quot;: &quot;read-only-proxy&quot;,
-                &quot;autoAcceptGroupAttr&quot;: &quot;auto-accept-group&quot;,
-            }
-
-            # Resource with delegates and autoSchedule = True
-
-            dn = &quot;cn=odtestresource,cn=resources,dc=example,dc=com&quot;
-            guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
-            attrs = {
-                'apple-generateduid': [guid],
-                'cn': ['odtestresource'],
-                'auto-schedule': ['yes'],
-                'proxy': ['6C6CD280-E6E3-11DF-9492-0800200C9A66'],
-                'read-only-proxy': ['6AA1AE12-592F-4190-A069-547CD83C47C0'],
-                'auto-accept-group': ['77A8EB52-AA2A-42ED-8843-B2BEE863AC70'],
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_resources
-            )
-            self.assertEquals(record.guid, guid)
-            self.assertEquals(
-                record.externalProxies(),
-                set(['6C6CD280-E6E3-11DF-9492-0800200C9A66'])
-            )
-            self.assertEquals(
-                record.externalReadOnlyProxies(),
-                set(['6AA1AE12-592F-4190-A069-547CD83C47C0'])
-            )
-            self.assertTrue(record.autoSchedule)
-            self.assertEquals(
-                record.autoAcceptGroup,
-                '77A8EB52-AA2A-42ED-8843-B2BEE863AC70'
-            )
-
-            # Record with lowercase guid
-            dn = &quot;uid=odtestamanda,cn=users,dc=example,dc=com&quot;
-            guid = '9dc04a70-e6dd-11df-9492-0800200c9a66'
-            attrs = {
-                'uid': ['odtestamanda'],
-                'apple-generateduid': [guid],
-                'sn': ['Test'],
-                'mail': ['odtestamanda@example.com', 'alternate@example.com'],
-                'givenName': ['Amanda'],
-                'cn': ['Amanda Test']
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_users
-            )
-            self.assertEquals(record.guid, guid.upper())
-
-            # Location with associated Address
-
-            dn = &quot;cn=odtestlocation,cn=locations,dc=example,dc=com&quot;
-            guid = &quot;D3094652-344B-4633-8DB8-09639FA00FB6&quot;
-            attrs = {
-                &quot;apple-generateduid&quot;: [guid],
-                &quot;cn&quot;: [&quot;odtestlocation&quot;],
-                &quot;assocAddr&quot;: [&quot;6C6CD280-E6E3-11DF-9492-0800200C9A66&quot;],
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_locations
-            )
-            self.assertEquals(record.extras, {
-                &quot;associatedAddress&quot;: &quot;6C6CD280-E6E3-11DF-9492-0800200C9A66&quot;
-            })
-
-            # Address with street and geo
-
-            dn = &quot;cn=odtestaddress,cn=buildings,dc=example,dc=com&quot;
-            guid = &quot;6C6CD280-E6E3-11DF-9492-0800200C9A66&quot;
-            attrs = {
-                &quot;apple-generateduid&quot;: [guid],
-                &quot;cn&quot;: [&quot;odtestaddress&quot;],
-                &quot;coordinates&quot;: [&quot;geo:1,2&quot;],
-                &quot;postal&quot;: [&quot;1 Infinite Loop, Cupertino, CA&quot;],
-            }
-            record = self.service._ldapResultToRecord(
-                dn, attrs, self.service.recordType_addresses
-            )
-            self.assertEquals(record.extras, {
-                &quot;geo&quot;: &quot;geo:1,2&quot;,
-                &quot;streetAddress&quot;: &quot;1 Infinite Loop, Cupertino, CA&quot;,
-            })
-
-        def test_listRecords(self):
-            &quot;&quot;&quot;
-            listRecords makes an LDAP query (with fake results in this test)
-            and turns the results into records
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            records = self.service.listRecords(self.service.recordType_users)
-            self.assertEquals(len(records), 4)
-            self.assertEquals(
-                set([r.firstName for r in records]),
-                set([&quot;Amanda&quot;, &quot;Betty&quot;, &quot;Cyrus&quot;, &quot;Wilfredo&quot;])  # Carlene is skipped because no guid in LDAP
-            )
-
-        def test_restrictedPrincipalsUsingDN(self):
-            &quot;&quot;&quot;
-            If restrictToGroup is in place, restrictedPrincipals should return only the principals
-            within that group.  In this case we're testing scenarios in which membership
-            is specified by DN
-            &quot;&quot;&quot;
-            for scenario in (
-                self.nestedUsingSameAttributeUsingDN,
-                self.nestedUsingDifferentAttributeUsingDN,
-            ):
-                self.setupService(scenario)
-
-                self.assertEquals(
-                    set(
-                        [
-                            &quot;cn=left_coast,cn=groups,dc=example,dc=com&quot;,
-                            &quot;cn=right_coast,cn=groups,dc=example,dc=com&quot;,
-                            &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;,
-                            &quot;uid=dreid,cn=users,dc=example,dc=com&quot;,
-                            &quot;uid=lecroy,cn=users,dc=example,dc=com&quot;,
-                            &quot;uid=wsanchez,cn=users,dc=example,dc=com&quot;,
-                        ]
-                    ),
-                    self.service.restrictedPrincipals)
-
-                dn = &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;
-                attrs = {
-                    'uid': ['cdaboo'],
-                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                    'sn': ['Daboo'],
-                    'mail': ['daboo@example.com'],
-                    'givenName': ['Cyrus'],
-                    'cn': ['Cyrus Daboo']
-                }
-                self.assertTrue(self.service.isAllowedByRestrictToGroup(dn, attrs))
-
-                dn = &quot;uid=unknown,cn=users,dc=example,dc=com&quot;
-                attrs = {
-                    'uid': ['unknown'],
-                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                    'sn': ['unknown'],
-                    'mail': ['unknown@example.com'],
-                    'givenName': ['unknown'],
-                    'cn': ['unknown']
-                }
-                self.assertFalse(self.service.isAllowedByRestrictToGroup(dn, attrs))
-
-
-        def test_restrictedPrincipalsUsingGUID(self):
-            &quot;&quot;&quot;
-            If restrictToGroup is in place, restrictedPrincipals should return only the principals
-            within that group.  In this case we're testing scenarios in which membership
-            is specified by an attribute, not DN
-            &quot;&quot;&quot;
-            for scenario in (
-                self.nestedUsingDifferentAttributeUsingGUID,
-                self.nestedUsingSameAttributeUsingGUID,
-            ):
-                self.setupService(scenario)
-
-                self.assertEquals(
-                    set(
-                        [
-                            &quot;left_coast&quot;,
-                            &quot;right_coast&quot;,
-                            &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;,
-                            &quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;,
-                        ]
-                    ),
-                    self.service.restrictedPrincipals)
-
-                dn = &quot;uid=cdaboo,cn=users,dc=example,dc=com&quot;
-                attrs = {
-                    'uid': ['cdaboo'],
-                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
-                    'sn': ['Daboo'],
-                    'mail': ['daboo@example.com'],
-                    'givenName': ['Cyrus'],
-                    'cn': ['Cyrus Daboo']
-                }
-                self.assertTrue(self.service.isAllowedByRestrictToGroup(dn, attrs))
-
-                dn = &quot;uid=unknown,cn=users,dc=example,dc=com&quot;
-                attrs = {
-                    'uid': ['unknown'],
-                    'apple-generateduid': ['unknown'],
-                    'sn': ['unknown'],
-                    'mail': ['unknown@example.com'],
-                    'givenName': ['unknown'],
-                    'cn': ['unknown']
-                }
-                self.assertFalse(self.service.isAllowedByRestrictToGroup(dn, attrs))
-
-
-        @inlineCallbacks
-        def test_groupMembershipAliases(self):
-            &quot;&quot;&quot;
-            Exercise a directory environment where group membership does not refer
-            to guids but instead uses LDAP DNs.  This example uses the LDAP attribute
-            &quot;uniqueMember&quot; to specify members of a group.  The value of this attribute
-            is each members' DN.  Even though the proxy database deals strictly in
-            guids, updateCache( ) is smart enough to map between guids and this
-            attribute which is referred to in the code as record.cachedGroupsAlias().
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            # Set up proxydb and preload it from xml
-            calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(&quot;proxies.sqlite&quot;)
-            yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
-
-            # Set up the GroupMembershipCache
-            cache = GroupMembershipCache(&quot;ProxyDB&quot;, expireSeconds=60)
-            self.service.groupMembershipCache = cache
-            updater = GroupMembershipCacheUpdater(
-                calendaruserproxy.ProxyDBService,
-                self.service, 30, 15, 30, cache=cache, useExternalProxies=False
-            )
-
-            self.assertEquals((False, 8, 8), (yield updater.updateCache()))
-
-            users = self.service.recordType_users
-
-            for shortName, groups in [
-                (&quot;cdaboo&quot;, set([&quot;both_coasts&quot;, &quot;recursive1_coasts&quot;, &quot;recursive2_coasts&quot;])),
-                (&quot;wsanchez&quot;, set([&quot;both_coasts&quot;, &quot;left_coast&quot;, &quot;recursive1_coasts&quot;, &quot;recursive2_coasts&quot;])),
-            ]:
-
-                record = self.service.recordWithShortName(users, shortName)
-                self.assertEquals(groups, (yield record.cachedGroups()))
-
-
-        def test_getExternalProxyAssignments(self):
-            &quot;&quot;&quot;
-            Verify getExternalProxyAssignments can extract assignments from the
-            directory, and that guids are normalized.
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-            self.assertEquals(
-                self.service.getExternalProxyAssignments(),
-                [
-                    ('D91B21B9-B856-495A-8E36-0E5AD54EFB3A#calendar-proxy-read',
-                        ['5A985493-EE2C-4665-94CF-4DFEA3A89500']),
-                    ('D91B21B9-B856-495A-8E36-0E5AD54EFB3A#calendar-proxy-write',
-                        ['6423F94A-6B76-4A3A-815B-D52CFD77935D']),
-                    ('753E5A60-AFFD-45E4-BF2C-31DAB459353F#calendar-proxy-write',
-                        ['6423F94A-6B76-4A3A-815B-D52CFD77935D'])
-                ]
-            )
-
-        def test_splitIntoBatches(self):
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-            # Data is perfect multiple of size
-            results = list(splitIntoBatches(set(range(12)), 4))
-            self.assertEquals(
-                results,
-                [set([0, 1, 2, 3]), set([4, 5, 6, 7]), set([8, 9, 10, 11])]
-            )
-
-            # Some left overs
-            results = list(splitIntoBatches(set(range(12)), 5))
-            self.assertEquals(
-                results,
-                [set([0, 1, 2, 3, 4]), set([8, 9, 5, 6, 7]), set([10, 11])]
-            )
-
-            # Empty
-            results = list(splitIntoBatches(set([]), 5))  # empty data
-            self.assertEquals(results, [set([])])
-
-        def test_recordTypeForDN(self):
-            # Ensure dn comparison is case insensitive and ignores extra
-            # whitespace
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            # Base DNs for each recordtype should already be lowercase
-            for dn in self.service.typeDNs.itervalues():
-                dnStr = ldap.dn.dn2str(dn)
-                self.assertEquals(dnStr, dnStr.lower())
-
-            # Match
-            dnStr = &quot;uid=foo,cn=USers ,dc=EXAMple,dc=com&quot;
-            self.assertEquals(self.service.recordTypeForDN(dnStr), &quot;users&quot;)
-            dnStr = &quot;uid=foo,cn=PLaces,dc=EXAMple,dc=com&quot;
-            self.assertEquals(self.service.recordTypeForDN(dnStr), &quot;locations&quot;)
-            dnStr = &quot;uid=foo,cn=Groups  ,dc=EXAMple,dc=com&quot;
-            self.assertEquals(self.service.recordTypeForDN(dnStr), &quot;groups&quot;)
-            dnStr = &quot;uid=foo,cn=Resources  ,dc=EXAMple,dc=com&quot;
-            self.assertEquals(self.service.recordTypeForDN(dnStr), &quot;resources&quot;)
-
-            # No Match
-            dnStr = &quot;uid=foo,cn=US ers ,dc=EXAMple,dc=com&quot;
-            self.assertEquals(self.service.recordTypeForDN(dnStr), None)
-
-        def test_normalizeDN(self):
-            for input, expected in (
-                (&quot;uid=foo,cn=users,dc=example,dc=com&quot;,
-                 &quot;uid=foo,cn=users,dc=example,dc=com&quot;),
-                (&quot;uid=FoO,cn=uSeRs,dc=ExAmPlE,dc=CoM&quot;,
-                 &quot;uid=foo,cn=users,dc=example,dc=com&quot;),
-                (&quot;uid=FoO , cn=uS eRs , dc=ExA mPlE ,   dc=CoM&quot;,
-                 &quot;uid=foo,cn=us ers,dc=exa mple,dc=com&quot;),
-                (&quot;uid=FoO , cn=uS  eRs , dc=ExA    mPlE ,   dc=CoM&quot;,
-                 &quot;uid=foo,cn=us ers,dc=exa mple,dc=com&quot;),
-            ):
-                self.assertEquals(expected, normalizeDNstr(input))
-
-        def test_queryDirectory(self):
-            &quot;&quot;&quot;
-            Verify queryDirectory skips LDAP queries where there has been no
-            LDAP attribute mapping provided for the given index type.
-            &quot;&quot;&quot;
-            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
-            self.history = []
-
-            def stubSearchMethod(base, scope, filterstr=&quot;(objectClass=*)&quot;,
-                                 attrlist=None, timeoutSeconds=-1,
-                                 resultLimit=0):
-                self.history.append((base, scope, filterstr))
-
-            recordTypes = [
-                self.service.recordType_users,
-                self.service.recordType_groups,
-                self.service.recordType_locations,
-                self.service.recordType_resources,
-            ]
-            self.service.queryDirectory(
-                recordTypes,
-                self.service.INDEX_TYPE_CUA,
-                &quot;mailto:test@example.com&quot;,
-                queryMethod=stubSearchMethod
-            )
-            self.assertEquals(
-                self.history,
-                [('cn=users,dc=example,dc=com', 2, '(&amp;(!(objectClass=organizationalUnit))(|(mail=test@example.com)(emailAliases=test@example.com)))'), ('cn=groups,dc=example,dc=com', 2, '(&amp;(!(objectClass=organizationalUnit))(|(mail=test@example.com)(emailAliases=test@example.com)))')]
-            )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_livedirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_livedirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_livedirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_livedirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,209 +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 __future__ import print_function
-
-runLDAPTests = False
-runODTests = False
-
-try:
-    import ldap
-    import socket
-
-    testServer = &quot;localhost&quot;
-    base = &quot;,&quot;.join([&quot;dc=%s&quot; % (p,) for p in socket.gethostname().split(&quot;.&quot;)])
-    print(&quot;Using base: %s&quot; % (base,))
-
-    try:
-        cxn = ldap.open(testServer)
-        results = cxn.search_s(base, ldap.SCOPE_SUBTREE, &quot;(uid=odtestamanda)&quot;,
-            [&quot;cn&quot;])
-        if len(results) == 1:
-            runLDAPTests = True
-    except ldap.LDAPError:
-        pass # Don't run live tests
-
-except ImportError:
-    print(&quot;Could not import ldap module (skipping LDAP tests)&quot;)
-
-try:
-    from calendarserver.platform.darwin.od import opendirectory, dsattributes
-
-    directory = opendirectory.odInit(&quot;/Search&quot;)
-
-    results = list(opendirectory.queryRecordsWithAttribute_list(
-        directory,
-        dsattributes.kDS1AttrGeneratedUID,
-        &quot;9DC04A70-E6DD-11DF-9492-0800200C9A66&quot;,
-        dsattributes.eDSExact,
-        False,
-        dsattributes.kDSStdRecordTypeUsers,
-        None,
-        count=0
-    ))
-    recordNames = [x[0] for x in results]
-    if &quot;odtestamanda&quot; in recordNames:
-        runODTests = True
-    else:
-        print(&quot;Test OD records not found (skipping OD tests)&quot;)
-
-except ImportError:
-    print(&quot;Could not import OpenDirectory framework (skipping OD tests)&quot;)
-
-
-if runLDAPTests or runODTests:
-
-    from twistedcaldav.test.util import TestCase
-    from twistedcaldav.directory import augment
-    from twistedcaldav.directory.test.test_xmlfile import augmentsFile
-    from twisted.internet.defer import inlineCallbacks
-
-    class LiveDirectoryTests(object):
-
-        def test_ldapRecordWithShortName(self):
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestamanda&quot;)
-            self.assertTrue(record is not None)
-
-        def test_ldapRecordWithGUID(self):
-            record = self.svc.recordWithGUID(&quot;9DC04A70-E6DD-11DF-9492-0800200C9A66&quot;)
-            self.assertTrue(record is not None)
-
-        @inlineCallbacks
-        def test_ldapRecordsMatchingFields(self):
-            fields = (
-                (&quot;firstName&quot;, &quot;Amanda&quot;, True, &quot;exact&quot;),
-                (&quot;lastName&quot;, &quot;Te&quot;, True, &quot;starts-with&quot;),
-            )
-            records = list(
-                (yield self.svc.recordsMatchingFields(fields, operand=&quot;and&quot;))
-            )
-            self.assertEquals(1, len(records))
-            record = self.svc.recordWithGUID(&quot;9DC04A70-E6DD-11DF-9492-0800200C9A66&quot;)
-            self.assertEquals(records, [record])
-
-        @inlineCallbacks
-        def test_restrictToGroup(self):
-            self.svc.restrictEnabledRecords = True
-            self.svc.restrictToGroup = &quot;odtestgrouptop&quot;
-
-            # Faulting in specific records will return records outside of
-            # the restrictToGroup, but they won't be enabledForCalendaring
-            # and AddressBooks:
-
-            # Amanda is a direct member of that group
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestamanda&quot;)
-            self.assertTrue(record.enabledForCalendaring)
-            self.assertTrue(record.enabledForAddressBooks)
-
-            # Betty is a direct member of that group
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestbetty&quot;)
-            self.assertTrue(record.enabledForCalendaring)
-            self.assertTrue(record.enabledForAddressBooks)
-
-            # Carlene is in a nested group
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestcarlene&quot;)
-            self.assertTrue(record.enabledForCalendaring)
-            self.assertTrue(record.enabledForAddressBooks)
-
-            # Denise is not in the group
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestdenise&quot;)
-            self.assertFalse(record.enabledForCalendaring)
-            self.assertFalse(record.enabledForAddressBooks)
-
-            # Searching for records using principal-property-search will not
-            # yield records outside of the restrictToGroup:
-
-            fields = (
-                (&quot;lastName&quot;, &quot;Test&quot;, True, &quot;exact&quot;),
-            )
-            records = list(
-                (yield self.svc.recordsMatchingFields(fields))
-            )
-            self.assertEquals(3, len(records))
-
-            # These two are directly in the restrictToGroup:
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestamanda&quot;)
-            self.assertTrue(record in records)
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestbetty&quot;)
-            self.assertTrue(record in records)
-            # Carlene is still picked up because she is in a nested group
-            record = self.svc.recordWithShortName(&quot;users&quot;, &quot;odtestcarlene&quot;)
-            self.assertTrue(record in records)
-
-    if runLDAPTests:
-
-        from twistedcaldav.directory.ldapdirectory import LdapDirectoryService
-        print(&quot;Running live LDAP tests against %s&quot; % (testServer,))
-
-        class LiveLDAPDirectoryServiceCase(LiveDirectoryTests, TestCase):
-
-            def setUp(self):
-                params = {
-                    &quot;augmentService&quot;:
-                        augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-                    &quot;uri&quot;: &quot;ldap://%s&quot; % (testServer,),
-                    &quot;rdnSchema&quot;: {
-                        &quot;base&quot;: base,
-                        &quot;guidAttr&quot;: &quot;apple-generateduid&quot;,
-                        &quot;users&quot;: {
-                            &quot;rdn&quot;: &quot;cn=users&quot;,
-                            &quot;attr&quot;: &quot;uid&quot;, # used only to synthesize email address
-                            &quot;emailSuffix&quot;: None, # used only to synthesize email address
-                            &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;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;cn=groups&quot;,
-                            &quot;attr&quot;: &quot;cn&quot;, # used only to synthesize email address
-                            &quot;emailSuffix&quot;: None, # used only to synthesize email address
-                            &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;groupSchema&quot;: {
-                        &quot;membersAttr&quot;: &quot;apple-group-memberguid&quot;, # how members are specified
-                        &quot;nestedGroupsAttr&quot; : &quot;apple-group-nestedgroup&quot;, # how nested groups are specified
-                        &quot;memberIdAttr&quot;: &quot;apple-generateduid&quot;, # which attribute the above refers to
-                    },
-                }
-                self.svc = LdapDirectoryService(params)
-
-    if runODTests:
-
-        from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-        print(&quot;Running live OD tests&quot;)
-
-        class LiveODDirectoryServiceCase(LiveDirectoryTests, TestCase):
-
-            def setUp(self):
-                params = {
-                    &quot;augmentService&quot;:
-                        augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-                }
-                self.svc = OpenDirectoryService(params)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_modifypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_modify.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_modify.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_modify.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_opendirectorypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,498 +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.
-##
-
-try:
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
-except ImportError:
-    pass
-else:
-    from calendarserver.platform.darwin.od import dsattributes
-    from collections import defaultdict
-    from txweb2.auth.digest import DigestedCredentials
-    from twisted.internet.defer import inlineCallbacks
-    from twisted.python.runtime import platform
-    from twisted.trial.unittest import SkipTest
-    from twistedcaldav.directory import augment
-    from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
-    from twistedcaldav.directory.directory import DirectoryService
-    from txdav.common.datastore.test.util import deriveValue, withSpecialValue
-    import twistedcaldav.directory.test.util
-
-    class DigestAuthModule(object):
-        &quot;&quot;&quot;
-        Stand-in for either configurable OD module, that verifies the response
-        according to its '.response' attribute, set by the test.
-        &quot;&quot;&quot;
-        class ODError(Exception):
-            pass
-
-        def odInit(self, node):
-            return self
-
-        def authenticateUserDigest(self, directory, node, user, challenge,
-                                   response, method):
-            val = (response == self.response)
-            return val
-
-
-    # Wonky hack to prevent unclean reactor shutdowns
-    class DummyReactor(object):
-        @staticmethod
-        def callLater(*args):
-            pass
-
-    twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
-
-    class OpenDirectory (
-        twistedcaldav.directory.test.util.BasicTestCase,
-        twistedcaldav.directory.test.util.DigestTestCase
-    ):
-        &quot;&quot;&quot;
-        Test Open Directory directory implementation.
-        &quot;&quot;&quot;
-        if not platform.isMacOSX():
-            skip = &quot;Currently, OpenDirectory backend only works on MacOS X.&quot;
-        recordTypes = set((
-            DirectoryService.recordType_users,
-            DirectoryService.recordType_groups,
-        ))
-
-        users = groups = {}
-
-        def setUp(self):
-            super(OpenDirectory, self).setUp()
-            try:
-                self._service = OpenDirectoryService(
-                    {
-                        &quot;node&quot; : &quot;/Search&quot;,
-                        &quot;augmentService&quot;: augment.AugmentXMLDB(xmlFiles=()),
-                    },
-                    odModule=deriveValue(self, &quot;odModule&quot;, lambda self: None)
-                )
-            except ImportError, e:
-                raise SkipTest(&quot;OpenDirectory module is not available: %s&quot; % (e,))
-
-        def service(self):
-            return self._service
-
-        def test_fullNameNone(self):
-            record = OpenDirectoryRecord(
-                service=self.service(),
-                recordType=DirectoryService.recordType_users,
-                guid=&quot;B1F93EB1-DA93-4772-9141-81C250DA36C2&quot;,
-                nodeName=&quot;/LDAPv2/127.0.0.1&quot;,
-                shortNames=(&quot;user&quot;,),
-                authIDs=set(),
-                fullName=None,
-                firstName=&quot;Some&quot;,
-                lastName=&quot;User&quot;,
-                emailAddresses=set((&quot;someuser@example.com&quot;,)),
-                memberGUIDs=[],
-                nestedGUIDs=[],
-                extProxies=[],
-                extReadOnlyProxies=[],
-            )
-            self.assertEquals(record.fullName, &quot;&quot;)
-
-
-        @withSpecialValue(&quot;odModule&quot;, DigestAuthModule())
-        def test_invalidODDigest(self):
-            record = OpenDirectoryRecord(
-                service=self.service(),
-                recordType=DirectoryService.recordType_users,
-                guid=&quot;B1F93EB1-DA93-4772-9141-81C250DA35B3&quot;,
-                nodeName=&quot;/LDAPv2/127.0.0.1&quot;,
-                shortNames=(&quot;user&quot;,),
-                authIDs=set(),
-                fullName=&quot;Some user&quot;,
-                firstName=&quot;Some&quot;,
-                lastName=&quot;User&quot;,
-                emailAddresses=set((&quot;someuser@example.com&quot;,)),
-                memberGUIDs=[],
-                nestedGUIDs=[],
-                extProxies=[],
-                extReadOnlyProxies=[],
-            )
-
-            digestFields = defaultdict(lambda: &quot;...&quot;)
-            digested = DigestedCredentials(&quot;user&quot;, &quot;GET&quot;, &quot;example.com&quot;,
-                                           digestFields)
-            od = deriveValue(self, &quot;odModule&quot;, lambda x: None)
-            od.response = &quot;invalid&quot;
-
-            self.assertFalse(record.verifyCredentials(digested))
-
-
-        @withSpecialValue(&quot;odModule&quot;, DigestAuthModule())
-        def test_validODDigest(self):
-            record = OpenDirectoryRecord(
-                service=self.service(),
-                recordType=DirectoryService.recordType_users,
-                guid=&quot;B1F93EB1-DA93-4772-9141-81C250DA35B3&quot;,
-                nodeName=&quot;/LDAPv2/127.0.0.1&quot;,
-                shortNames=(&quot;user&quot;,),
-                authIDs=set(),
-                fullName=&quot;Some user&quot;,
-                firstName=&quot;Some&quot;,
-                lastName=&quot;User&quot;,
-                emailAddresses=set((&quot;someuser@example.com&quot;,)),
-                memberGUIDs=[],
-                nestedGUIDs=[],
-                extProxies=[],
-                extReadOnlyProxies=[],
-            )
-
-            digestFields = {
-                &quot;username&quot;: &quot;user&quot;,
-                &quot;realm&quot;: &quot;/Search&quot;,
-                &quot;nonce&quot;: &quot;ABC&quot;,
-                &quot;uri&quot;: &quot;/&quot;,
-                &quot;response&quot;: &quot;123&quot;,
-                &quot;algorithm&quot;: &quot;md5&quot;,
-            }
-            od = deriveValue(self, &quot;odModule&quot;, lambda self: None)
-            od.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'
-            ) % digestFields
-
-            digested = DigestedCredentials(&quot;user&quot;, &quot;GET&quot;, &quot;example.com&quot;,
-                                           digestFields)
-
-            self.assertTrue(record.verifyCredentials(digested))
-
-            # This should be defaulted
-            del digestFields[&quot;algorithm&quot;]
-
-            self.assertTrue(record.verifyCredentials(digested))
-
-        def test_queryDirectorySingleGUID(self):
-            &quot;&quot;&quot; Test for lookup on existing and non-existing GUIDs &quot;&quot;&quot;
-
-            def lookupMethod(obj, attr, value, matchType, casei, recordTypes, attributes, count=0):
-
-                data = {
-                    &quot;dsRecTypeStandard:Users&quot; : [
-                        {
-                            dsattributes.kDS1AttrGeneratedUID : &quot;1234567890&quot;,
-                            dsattributes.kDSNAttrRecordName : [&quot;user1&quot;, &quot;User 1&quot;],
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                        },
-                    ],
-                    &quot;dsRecTypeStandard:Groups&quot; : [],
-                }
-                results = []
-                for recordType in recordTypes:
-                    for entry in data[recordType]:
-                        if entry[attr] == value:
-                            results.append((&quot;&quot;, entry))
-                return results
-
-            recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
-            self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, &quot;1234567890&quot;, lookupMethod=lookupMethod)
-            self.assertTrue(self.service().recordWithGUID(&quot;1234567890&quot;))
-            self.assertFalse(self.service().recordWithGUID(&quot;987654321&quot;))
-
-
-        def test_queryDirectoryDuplicateGUIDs(self):
-            &quot;&quot;&quot; Test for lookup on duplicate GUIDs, ensuring they don't get
-                faulted in &quot;&quot;&quot;
-
-            def lookupMethod(obj, attr, value, matchType, casei, recordType, attributes, count=0):
-
-                data = [
-                    {
-                        dsattributes.kDS1AttrGeneratedUID : &quot;1234567890&quot;,
-                        dsattributes.kDSNAttrRecordName : [&quot;user1&quot;, &quot;User 1&quot;],
-                        dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                    },
-                    {
-                        dsattributes.kDS1AttrGeneratedUID : &quot;1234567890&quot;,
-                        dsattributes.kDSNAttrRecordName : [&quot;user2&quot;, &quot;User 2&quot;],
-                        dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                    },
-                ]
-                results = []
-                for entry in data:
-                    if entry[attr] == value:
-                        results.append((&quot;&quot;, entry))
-                return results
-
-            recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
-            self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, &quot;1234567890&quot;, lookupMethod=lookupMethod)
-            self.assertFalse(self.service().recordWithGUID(&quot;1234567890&quot;))
-
-        def test_queryDirectoryLocalUsers(self):
-            &quot;&quot;&quot; Test for lookup on local users, ensuring they do get
-                faulted in &quot;&quot;&quot;
-
-            def lookupMethod(obj, attr, value, matchType, casei, recordTypes, attributes, count=0):
-                data = {
-                    &quot;dsRecTypeStandard:Users&quot; : [
-                        {
-                            dsattributes.kDS1AttrGeneratedUID : &quot;1234567890&quot;,
-                            dsattributes.kDSNAttrRecordName : [&quot;user1&quot;, &quot;User 1&quot;],
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/Local/Default&quot;,
-                        },
-                        {
-                            dsattributes.kDS1AttrGeneratedUID : &quot;987654321&quot;,
-                            dsattributes.kDSNAttrRecordName : [&quot;user2&quot;, &quot;User 2&quot;],
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                        },
-                    ],
-                    &quot;dsRecTypeStandard:Groups&quot; : [],
-                }
-                results = []
-                for recordType in recordTypes:
-                    for entry in data[recordType]:
-                        if entry[attr] == value:
-                            results.append((&quot;&quot;, entry))
-                return results
-
-            recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
-            self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, &quot;1234567890&quot;, lookupMethod=lookupMethod)
-            self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, &quot;987654321&quot;, lookupMethod=lookupMethod)
-            self.assertTrue(self.service().recordWithGUID(&quot;1234567890&quot;))
-            self.assertTrue(self.service().recordWithGUID(&quot;987654321&quot;))
-
-        def test_queryDirectoryEmailAddresses(self):
-            &quot;&quot;&quot; Test to ensure we only ask for users when email address is
-                part of the query &quot;&quot;&quot;
-
-            def lookupMethod(obj, attr, value, matchType, casei, recordType, attributes, count=0):
-
-                if recordType != ['dsRecTypeStandard:Users']:
-                    raise ValueError
-
-                return []
-
-            recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
-            self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_CUA, &quot;mailto:user1@example.com&quot;, lookupMethod=lookupMethod)
-
-
-        @inlineCallbacks
-        def test_recordsMatchingFields(self):
-
-
-            def lookupMethod(obj, attribute, value, matchType, caseless,
-                recordTypes, attributes):
-
-                data = {
-                    dsattributes.kDSStdRecordTypeUsers : (
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Morgen Sagen&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;morgen&quot;,
-                            dsattributes.kDS1AttrFirstName : &quot;Morgen&quot;,
-                            dsattributes.kDS1AttrLastName : &quot;Sagen&quot;,
-                            dsattributes.kDSNAttrEMailAddress : &quot;morgen@example.com&quot;,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;83479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                        },
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Morgan Sagan&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;morgan&quot;,
-                            dsattributes.kDS1AttrFirstName : &quot;Morgan&quot;,
-                            dsattributes.kDS1AttrLastName : &quot;Sagan&quot;,
-                            dsattributes.kDSNAttrEMailAddress : &quot;morgan@example.com&quot;,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;93479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                        },
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Shari Sagen&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;shari&quot;,
-                            dsattributes.kDS1AttrFirstName : &quot;Shari&quot;,
-                            dsattributes.kDS1AttrLastName : &quot;Sagen&quot;,
-                            dsattributes.kDSNAttrEMailAddress : &quot;shari@example.com&quot;,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;A3479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                        },
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Local Morgen&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;localmorgen&quot;,
-                            dsattributes.kDS1AttrFirstName : &quot;Local&quot;,
-                            dsattributes.kDS1AttrLastName : &quot;Morgen&quot;,
-                            dsattributes.kDSNAttrEMailAddress : &quot;localmorgen@example.com&quot;,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/Local/Default&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;B3479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
-                        },
-                    ),
-                    dsattributes.kDSStdRecordTypeGroups : (
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Test Group&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;testgroup&quot;,
-                            dsattributes.kDS1AttrFirstName : None,
-                            dsattributes.kDS1AttrLastName : None,
-                            dsattributes.kDSNAttrEMailAddress : None,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;C3479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeGroups,
-                        },
-                        {
-                            dsattributes.kDS1AttrDistinguishedName : &quot;Morgen's Group&quot;,
-                            dsattributes.kDSNAttrRecordName : &quot;morgensgroup&quot;,
-                            dsattributes.kDS1AttrFirstName : None,
-                            dsattributes.kDS1AttrLastName : None,
-                            dsattributes.kDSNAttrEMailAddress : None,
-                            dsattributes.kDSNAttrMetaNodeLocation : &quot;/LDAPv3/127.0.0.1&quot;,
-                            dsattributes.kDS1AttrGeneratedUID : &quot;D3479230-821E-11DE-B6B0-DBB02C6D659D&quot;,
-                            dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeGroups,
-                        },
-                    ),
-                    dsattributes.kDSStdRecordTypePlaces : (),
-                    dsattributes.kDSStdRecordTypeResources : (),
-                }
-
-                def attributeMatches(fieldValue, value, caseless, matchType):
-                    if fieldValue is None:
-                        return False
-                    if caseless:
-                        fieldValue = fieldValue.lower()
-                        value = value.lower()
-                    if matchType == dsattributes.eDSStartsWith:
-                        if fieldValue.startswith(value):
-                            return True
-                    elif matchType == dsattributes.eDSContains:
-                        try:
-                            fieldValue.index(value)
-                            return True
-                        except ValueError:
-                            pass
-                    else: # exact
-                        if fieldValue == value:
-                            return True
-                    return False
-
-                results = []
-                for recordType in recordTypes:
-                    for row in data[recordType]:
-                        if attributeMatches(row[attribute], value, caseless,
-                            matchType):
-                            results.append((row[dsattributes.kDSNAttrRecordName], row))
-
-                return results
-
-            #
-            # OR
-            #
-            fields = [
-                (&quot;fullName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                (&quot;emailAddresses&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                (&quot;firstName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-                (&quot;lastName&quot;, &quot;mor&quot;, True, u&quot;starts-with&quot;),
-            ]
-
-            # any record type
-            results = (yield self.service().recordsMatchingFields(
-                fields,
-                lookupMethod=lookupMethod
-            ))
-            results = list(results)
-            self.assertEquals(len(results), 4)
-            for record in results:
-                self.assertTrue(isinstance(record, OpenDirectoryRecord))
-
-            # just users
-            results = (yield self.service().recordsMatchingFields(fields,
-                recordType=&quot;users&quot;,
-                lookupMethod=lookupMethod))
-            results = list(results)
-            self.assertEquals(len(results), 3)
-
-            # just groups
-            results = (yield self.service().recordsMatchingFields(fields,
-                recordType=&quot;groups&quot;,
-                lookupMethod=lookupMethod))
-            results = list(results)
-            self.assertEquals(len(results), 1)
-
-            #
-            # AND
-            #
-            fields = [
-                (&quot;firstName&quot;, &quot;morgen&quot;, True, u&quot;equals&quot;),
-                (&quot;lastName&quot;, &quot;age&quot;, True, u&quot;contains&quot;)
-            ]
-            results = (yield self.service().recordsMatchingFields(fields,
-                operand=&quot;and&quot;, lookupMethod=lookupMethod))
-            results = list(results)
-            self.assertEquals(len(results), 1)
-
-            #
-            # case sensitivity
-            #
-            fields = [
-                (&quot;firstName&quot;, &quot;morgen&quot;, False, u&quot;equals&quot;),
-            ]
-            results = (yield self.service().recordsMatchingFields(fields,
-                lookupMethod=lookupMethod))
-            results = list(results)
-            self.assertEquals(len(results), 0)
-
-            fields = [
-                (&quot;firstName&quot;, &quot;morgen&quot;, True, u&quot;equals&quot;),
-            ]
-            results = (yield self.service().recordsMatchingFields(
-                fields,
-                lookupMethod=lookupMethod
-            ))
-            results = list(results)
-            self.assertEquals(len(results), 1)
-
-            #
-            # no matches
-            #
-            fields = [
-                (&quot;firstName&quot;, &quot;xyzzy&quot;, True, u&quot;starts-with&quot;),
-                (&quot;lastName&quot;, &quot;plugh&quot;, True, u&quot;contains&quot;)
-            ]
-            results = (yield self.service().recordsMatchingFields(
-                fields,
-                operand=&quot;and&quot;,
-                lookupMethod=lookupMethod
-            ))
-            results = list(results)
-            self.assertEquals(len(results), 0)
-
-
-    class OpenDirectorySubset (OpenDirectory):
-        &quot;&quot;&quot;
-        Test the recordTypes subset feature of Apple OpenDirectoryService.
-        &quot;&quot;&quot;
-        recordTypes = set((
-            DirectoryService.recordType_users,
-            DirectoryService.recordType_groups,
-        ))
-
-        def setUp(self):
-            super(OpenDirectorySubset, self).setUp()
-            self._service = OpenDirectoryService(
-                {
-                    &quot;node&quot; : &quot;/Search&quot;,
-                    &quot;recordTypes&quot; : (DirectoryService.recordType_users, DirectoryService.recordType_groups),
-                    &quot;augmentService&quot; : augment.AugmentXMLDB(xmlFiles=()),
-                },
-                odModule=deriveValue(self, &quot;odModule&quot;, lambda x: None)
-            )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_opendirectorybackerpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectorybacker.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectorybacker.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_opendirectorybacker.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,55 +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.
-##
-
-try:
-    from twistedcaldav.directory.opendirectorybacker import VCardRecord
-except ImportError:
-    pass
-else:
-    from twistedcaldav.test.util import TestCase
-
-    class VCardRecordTestCase(TestCase):
-
-
-        def test_multiplePhoneNumbersAndEmailAddresses(self):
-            attributes = {
-                u'dsAttrTypeStandard:AppleMetaRecordName': ['uid=odtestamanda,cn=users,dc=dalek,dc=example,dc=com'],
-                u'dsAttrTypeStandard:ModificationTimestamp': '20111017170937Z',
-                u'dsAttrTypeStandard:PhoneNumber': ['408 555-1212', '415 555-1212'],
-                u'dsAttrTypeStandard:RecordType': ['dsRecTypeStandard:Users'],
-                u'dsAttrTypeStandard:AppleMetaNodeLocation': ['/LDAPv3/127.0.0.1'],
-                u'dsAttrTypeStandard:RecordName': ['odtestamanda'],
-                u'dsAttrTypeStandard:FirstName': 'Amanda',
-                u'dsAttrTypeStandard:GeneratedUID': '9DC04A70-E6DD-11DF-9492-0800200C9A66',
-                u'dsAttrTypeStandard:LastName': 'Test',
-                u'dsAttrTypeStandard:CreationTimestamp': '20110927182945Z',
-                u'dsAttrTypeStandard:EMailAddress': ['amanda@example.com', 'second@example.com'],
-                u'dsAttrTypeStandard:RealName': 'Amanda Test',
-            }
-            vcardRecord = VCardRecord(StubService(), attributes)
-            vcard = vcardRecord.vCard()
-            properties = set([prop.value() for prop in vcard.properties(&quot;TEL&quot;)])
-            self.assertEquals(properties, set([&quot;408 555-1212&quot;, &quot;415 555-1212&quot;]))
-            properties = set([prop.value() for prop in vcard.properties(&quot;EMAIL&quot;)])
-            self.assertEquals(properties, set([&quot;amanda@example.com&quot;, &quot;second@example.com&quot;]))
-
-
-
-    class StubService(object):
-        addDSAttrXProperties = False
-        directoryBackedAddressBook = None
-        appleInternalServer = False
-        realmName = &quot;testing&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_principalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_principal.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_principal.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_principal.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,71 +15,45 @@
</span><span class="cx"> ##
</span><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><del>-import os
</del><ins>+from urllib import quote
+from uuid import UUID
</ins><span class="cx"> 
</span><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twisted.cred.credentials import UsernamePassword
</span><del>-from twisted.internet.defer import inlineCallbacks
-from txdav.xml import element as davxml
-from txweb2.dav.fileop import rmdir
</del><ins>+
</ins><span class="cx"> from txweb2.dav.resource import AccessDeniedError
</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><ins>+from txdav.xml import element as davxml
+
+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
-from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.principal import DirectoryPrincipalTypeProvisioningResource
-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
-from twistedcaldav import carddavxml
-import twistedcaldav.test.util
</del><ins>+from twistedcaldav.directory.principal import (
+    DirectoryCalendarPrincipalResource,
+    DirectoryPrincipalResource,
+    DirectoryPrincipalTypeProvisioningResource,
+)
+from twistedcaldav.test.util import StoreTestCase
+from txdav.who.idirectory import AutoScheduleMode, RecordType as CalRecordType
+from twext.who.idirectory import RecordType
</ins><span class="cx"> 
</span><del>-from txdav.common.datastore.file import CommonDataStore
-from urllib import quote
</del><span class="cx"> 
</span><del>-
-
-class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
</del><ins>+class ProvisionedPrincipals(StoreTestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory service provisioned principals.
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def setUp(self):
</span><del>-        super(ProvisionedPrincipals, self).setUp()
</del><ins>+        yield super(ProvisionedPrincipals, self).setUp()
</ins><span class="cx"> 
</span><del>-        self.directoryServices = (
-            XMLDirectoryService(
-                {
-                    'xmlFile' : xmlFile,
-                    'augmentService' :
-                        augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
-                }
-            ),
-        )
</del><ins>+        self.principalRootResource = self.actualRoot.getChild(&quot;principals&quot;)
</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><span class="cx"> 
</span><del>-            provisioningResource = DirectoryPrincipalProvisioningResource(url, directory)
-            directory.setPrincipalCollection(provisioningResource)
</del><span class="cx"> 
</span><del>-            self.site.resource.putChild(name, provisioningResource)
-
-            self.principalRootResources[directory.__class__.__name__] = provisioningResource
-
-        calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB(os.path.abspath(self.mktemp()))
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_hierarchy(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -95,57 +69,109 @@
</span><span class="cx"> 
</span><span class="cx">         DirectoryPrincipalResource.principalURL(),
</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>+        provisioningResource = self.principalRootResource
</ins><span class="cx"> 
</span><del>-            provisioningURL = &quot;/&quot; + directory.__class__.__name__ + &quot;/&quot;
-            self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
</del><ins>+        provisioningURL = &quot;/principals/&quot;
+        self.assertEquals(
+            provisioningURL,
+            provisioningResource.principalCollectionURL()
+        )
</ins><span class="cx"> 
</span><del>-            principalCollections = provisioningResource.principalCollections()
-            self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
</del><ins>+        principalCollections = provisioningResource.principalCollections()
+        self.assertEquals(
+            set((provisioningURL,)),
+            set(pc.principalCollectionURL() for pc in principalCollections)
+        )
</ins><span class="cx"> 
</span><del>-            recordTypes = set((yield provisioningResource.listChildren()))
-            self.assertEquals(recordTypes, set(directory.recordTypes()))
</del><ins>+        recordTypes = set((yield provisioningResource.listChildren()))
+        self.assertEquals(
+            recordTypes,
+            set(
+                [
+                    self.directory.recordTypeToOldName(rt) for rt in
+                    (
+                        self.directory.recordType.user,
+                        self.directory.recordType.group,
+                        self.directory.recordType.location,
+                        self.directory.recordType.resource,
+                        self.directory.recordType.address,
+                    )
+                ]
+            )
+        )
</ins><span class="cx"> 
</span><del>-            for recordType in recordTypes:
-                #print(&quot;   -&gt; %s&quot; % (recordType,))
-                typeResource = provisioningResource.getChild(recordType)
-                self.failUnless(isinstance(typeResource, DirectoryPrincipalTypeProvisioningResource))
</del><ins>+        for recordType in recordTypes:
+            typeResource = yield provisioningResource.getChild(recordType)
+            self.failUnless(
+                isinstance(
+                    typeResource,
+                    DirectoryPrincipalTypeProvisioningResource
+                )
+            )
</ins><span class="cx"> 
</span><del>-                typeURL = provisioningURL + recordType + &quot;/&quot;
-                self.assertEquals(typeURL, typeResource.principalCollectionURL())
</del><ins>+            typeURL = provisioningURL + recordType + &quot;/&quot;
+            self.assertEquals(
+                typeURL, typeResource.principalCollectionURL()
+            )
</ins><span class="cx"> 
</span><del>-                principalCollections = typeResource.principalCollections()
-                self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
</del><ins>+            principalCollections = typeResource.principalCollections()
+            self.assertEquals(
+                set((provisioningURL,)),
+                set(
+                    pc.principalCollectionURL()
+                    for pc in principalCollections
+                )
+            )
</ins><span class="cx"> 
</span><del>-                shortNames = set((yield typeResource.listChildren()))
-                # Handle records with mulitple shortNames
-                expected = []
-                for r in directory.listRecords(recordType):
-                    if r.uid != &quot;disabled&quot;:
-                        expected.extend(r.shortNames)
-                self.assertEquals(shortNames, set(expected))
</del><ins>+            shortNames = set((yield typeResource.listChildren()))
+            # Handle records with mulitple shortNames
+            expected = []
+            for r in (
+                yield self.directory.recordsWithRecordType(
+                    self.directory.oldNameToRecordType(recordType)
+                )
+            ):
+                expected.extend(r.shortNames)
+            self.assertEquals(shortNames, set(expected))
</ins><span class="cx"> 
</span><del>-                for shortName in shortNames:
-                    #print(&quot;     -&gt; %s&quot; % (shortName,))
-                    recordResource = typeResource.getChild(shortName)
-                    self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
</del><ins>+            for shortName in shortNames:
+                #print(&quot;     -&gt; %s&quot; % (shortName,))
+                recordResource = yield typeResource.getChild(shortName)
+                self.failUnless(
+                    isinstance(recordResource, DirectoryPrincipalResource)
+                )
</ins><span class="cx"> 
</span><del>-                    # shortName may be non-ascii
-                    recordURL = typeURL + quote(shortName) + &quot;/&quot;
-                    self.assertIn(recordURL, (recordResource.principalURL(),) + tuple(recordResource.alternateURIs()))
</del><ins>+                # shortName may be non-ascii
+                recordURL = typeURL + quote(shortName.encode(&quot;utf-8&quot;)) + &quot;/&quot;
+                self.assertIn(
+                    recordURL,
+                    (
+                        (recordResource.principalURL(),) +
+                        tuple(recordResource.alternateURIs())
+                    )
+                )
</ins><span class="cx"> 
</span><del>-                    principalCollections = recordResource.principalCollections()
-                    self.assertEquals(set((provisioningURL,)), set(pc.principalCollectionURL() for pc in principalCollections))
</del><ins>+                principalCollections = (
+                    recordResource.principalCollections()
+                )
+                self.assertEquals(
+                    set((provisioningURL,)),
+                    set(
+                        pc.principalCollectionURL()
+                        for pc in principalCollections
+                    )
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_allRecords(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test of a test routine...
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if True:  # user.enabled:
</ins><span class="cx">                 self.assertEquals(recordResource.record, record)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -153,85 +179,108 @@
</span><span class="cx">     # DirectoryPrincipalProvisioningResource
</span><span class="cx">     ##
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalForShortName(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalProvisioningResource.principalForShortName()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
-            principal = provisioningResource.principalForShortName(recordType, record.shortNames[0])
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            principal = yield provisioningResource.principalForShortName(
+                recordType, record.shortNames[0]
+            )
+            if True:  # user.enabled:
</ins><span class="cx">                 self.failIf(principal is None)
</span><span class="cx">                 self.assertEquals(record, principal.record)
</span><span class="cx">             else:
</span><span class="cx">                 self.failIf(principal is not None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalForUser(self):
</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>+        provisioningResource = self.principalRootResource
</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 (
+            yield self.directory.recordsWithRecordType(
+                self.directory.recordType.user
+            )
+        ):
+            userResource = yield provisioningResource.principalForUser(
+                user.shortNames[0]
+            )
+            if True:  # 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><ins>+    @inlineCallbacks
</ins><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>+        provisioningResource = self.principalRootResource
</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 (
+            yield self.directory.recordsWithRecordType(
+                self.directory.recordType.user
+            )
+        ):
+            creds = UsernamePassword(user.shortNames[0], &quot;bogus&quot;)
+            userResource = yield provisioningResource.principalForAuthID(creds)
+            if True:  # 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><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalForUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalProvisioningResource.principalForUID()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
-            principal = provisioningResource.principalForUID(record.uid)
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            principal = yield provisioningResource.principalForUID(record.uid)
+            if True:  # user.enabled:
</ins><span class="cx">                 self.failIf(principal is None)
</span><span class="cx">                 self.assertEquals(record, principal.record)
</span><span class="cx">             else:
</span><span class="cx">                 self.failIf(principal is not None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalForRecord(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalProvisioningResource.principalForRecord()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
-            principal = provisioningResource.principalForRecord(record)
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            principal = yield provisioningResource.principalForRecord(record)
+            if True:  # user.enabled:
</ins><span class="cx">                 self.failIf(principal is None)
</span><span class="cx">                 self.assertEquals(record, principal.record)
</span><span class="cx">             else:
</span><span class="cx">                 self.failIf(principal is not None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalForCalendarUserAddress(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+        DirectoryPrincipalProvisioningResource
+        .principalForCalendarUserAddress()
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for (
</span><del>-            provisioningResource, _ignore_recordType, recordResource, record
-        ) in self._allRecords():
</del><ins>+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
</ins><span class="cx"> 
</span><span class="cx">             test_items = tuple(record.calendarUserAddresses)
</span><span class="cx">             if recordResource:
</span><span class="lines">@@ -243,62 +292,89 @@
</span><span class="cx">                 test_items += (principalURL, alternateURL)
</span><span class="cx"> 
</span><span class="cx">             for address in test_items:
</span><del>-                principal = provisioningResource.principalForCalendarUserAddress(address)
-                if record.enabledForCalendaring:
</del><ins>+                principal = (
+                    yield provisioningResource
+                    .principalForCalendarUserAddress(address)
+                )
+                if record.hasCalendars:
</ins><span class="cx">                     self.failIf(principal is None)
</span><span class="cx">                     self.assertEquals(record, principal.record)
</span><span class="cx">                 else:
</span><span class="cx">                     self.failIf(principal is not None)
</span><span class="cx"> 
</span><span class="cx">         # Explicitly check the disabled record
</span><del>-        provisioningResource = self.principalRootResources['XMLDirectoryService']
</del><ins>+        provisioningResource = yield self.actualRoot.getChild(&quot;principals&quot;)
</ins><span class="cx"> 
</span><span class="cx">         self.failUnlessIdentical(
</span><del>-            provisioningResource.principalForCalendarUserAddress(
-                &quot;mailto:nocalendar@example.com&quot;
</del><ins>+            (
+                yield provisioningResource.principalForCalendarUserAddress(
+                    &quot;mailto:nocalendar@example.com&quot;
+                )
</ins><span class="cx">             ),
</span><span class="cx">             None
</span><span class="cx">         )
</span><span class="cx">         self.failUnlessIdentical(
</span><del>-            provisioningResource.principalForCalendarUserAddress(
-                &quot;urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5&quot;
</del><ins>+            (
+                yield provisioningResource.principalForCalendarUserAddress(
+                    &quot;urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5&quot;
+                )
</ins><span class="cx">             ),
</span><span class="cx">             None
</span><span class="cx">         )
</span><span class="cx">         self.failUnlessIdentical(
</span><del>-            provisioningResource.principalForCalendarUserAddress(
-                &quot;/principals/users/nocalendar/&quot;
</del><ins>+            (
+                yield provisioningResource.principalForCalendarUserAddress(
+                    &quot;/principals/users/nocalendar/&quot;
+                )
</ins><span class="cx">             ),
</span><span class="cx">             None
</span><span class="cx">         )
</span><span class="cx">         self.failUnlessIdentical(
</span><del>-            provisioningResource.principalForCalendarUserAddress(
-                &quot;/principals/__uids__/543D28BA-F74F-4D5F-9243-B3E3A61171E5/&quot;
</del><ins>+            (
+                yield provisioningResource.principalForCalendarUserAddress(
+                    &quot;/principals/__uids__/&quot;
+                    &quot;543D28BA-F74F-4D5F-9243-B3E3A61171E5/&quot;
+                )
</ins><span class="cx">             ),
</span><span class="cx">             None
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_enabledForCalendaring(self):
</del><ins>+    def test_hasCalendars(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+        DirectoryPrincipalProvisioningResource
+        .principalForCalendarUserAddress()
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        for provisioningResource, _ignore_recordType, _ignore_recordResource, record in self._allRecords():
-            principal = provisioningResource.principalForRecord(record)
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            principal = yield provisioningResource.principalForRecord(record)
+            if True:  # user.enabled:
</ins><span class="cx">                 self.failIf(principal is None)
</span><span class="cx">             else:
</span><span class="cx">                 self.failIf(principal is not None)
</span><span class="cx">                 continue
</span><del>-            if record.enabledForCalendaring:
-                self.assertTrue(isinstance(principal, DirectoryCalendarPrincipalResource))
</del><ins>+            if record.hasCalendars:
+                self.assertTrue(
+                    isinstance(principal, DirectoryCalendarPrincipalResource)
+                )
</ins><span class="cx">             else:
</span><del>-                self.assertTrue(isinstance(principal, DirectoryPrincipalResource))
-                if record.enabledForAddressBooks:
-                    self.assertTrue(isinstance(principal, DirectoryCalendarPrincipalResource))
</del><ins>+                self.assertTrue(
+                    isinstance(principal, DirectoryPrincipalResource)
+                )
+                if record.hasContacts:
+                    self.assertTrue(
+                        isinstance(
+                            principal, DirectoryCalendarPrincipalResource
+                        )
+                    )
</ins><span class="cx">                 else:
</span><del>-                    self.assertFalse(isinstance(principal, DirectoryCalendarPrincipalResource))
</del><ins>+                    self.assertFalse(
+                        isinstance(
+                            principal, DirectoryCalendarPrincipalResource
+                        )
+                    )
</ins><span class="cx"> 
</span><span class="cx">             @inlineCallbacks
</span><span class="cx">             def hasProperty(property):
</span><span class="lines">@@ -315,88 +391,150 @@
</span><span class="cx">                 except:
</span><span class="cx">                     self.fail(&quot;Wrong exception type&quot;)
</span><span class="cx">                 else:
</span><del>-                    self.fail(&quot;No exception principal: %s, property %s&quot; % (principal, property,))
</del><ins>+                    self.fail(
+                        &quot;No exception principal: %s, property %s&quot;
+                        % (principal, property,)
+                    )
</ins><span class="cx"> 
</span><del>-            if record.enabledForCalendaring:
-                yield hasProperty((caldav_namespace, &quot;calendar-home-set&quot;))
-                yield hasProperty((caldav_namespace, &quot;calendar-user-address-set&quot;))
-                yield hasProperty((caldav_namespace, &quot;schedule-inbox-URL&quot;))
-                yield hasProperty((caldav_namespace, &quot;schedule-outbox-URL&quot;))
-                yield hasProperty((caldav_namespace, &quot;calendar-user-type&quot;))
-                yield hasProperty((calendarserver_namespace, &quot;calendar-proxy-read-for&quot;))
-                yield hasProperty((calendarserver_namespace, &quot;calendar-proxy-write-for&quot;))
-                yield hasProperty((calendarserver_namespace, &quot;auto-schedule&quot;))
</del><ins>+            if record.hasCalendars:
+                yield hasProperty(
+                    (caldav_namespace, &quot;calendar-home-set&quot;)
+                )
+                yield hasProperty(
+                    (caldav_namespace, &quot;calendar-user-address-set&quot;)
+                )
+                yield hasProperty(
+                    (caldav_namespace, &quot;schedule-inbox-URL&quot;)
+                )
+                yield hasProperty(
+                    (caldav_namespace, &quot;schedule-outbox-URL&quot;)
+                )
+                yield hasProperty(
+                    (caldav_namespace, &quot;calendar-user-type&quot;)
+                )
+                yield hasProperty(
+                    (calendarserver_namespace, &quot;calendar-proxy-read-for&quot;)
+                )
+                yield hasProperty(
+                    (calendarserver_namespace, &quot;calendar-proxy-write-for&quot;)
+                )
+                # yield hasProperty(
+                #     (calendarserver_namespace, &quot;auto-schedule&quot;)
+                # )
</ins><span class="cx">             else:
</span><del>-                yield doesNotHaveProperty((caldav_namespace, &quot;calendar-home-set&quot;))
-                yield doesNotHaveProperty((caldav_namespace, &quot;calendar-user-address-set&quot;))
-                yield doesNotHaveProperty((caldav_namespace, &quot;schedule-inbox-URL&quot;))
-                yield doesNotHaveProperty((caldav_namespace, &quot;schedule-outbox-URL&quot;))
-                yield doesNotHaveProperty((caldav_namespace, &quot;calendar-user-type&quot;))
-                yield doesNotHaveProperty((calendarserver_namespace, &quot;calendar-proxy-read-for&quot;))
-                yield doesNotHaveProperty((calendarserver_namespace, &quot;calendar-proxy-write-for&quot;))
-                yield doesNotHaveProperty((calendarserver_namespace, &quot;auto-schedule&quot;))
</del><ins>+                yield doesNotHaveProperty(
+                    (caldav_namespace, &quot;calendar-home-set&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (caldav_namespace, &quot;calendar-user-address-set&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (caldav_namespace, &quot;schedule-inbox-URL&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (caldav_namespace, &quot;schedule-outbox-URL&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (caldav_namespace, &quot;calendar-user-type&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (calendarserver_namespace, &quot;calendar-proxy-read-for&quot;)
+                )
+                yield doesNotHaveProperty(
+                    (calendarserver_namespace, &quot;calendar-proxy-write-for&quot;)
+                )
+                # yield doesNotHaveProperty(
+                #     (calendarserver_namespace, &quot;auto-schedule&quot;)
+                # )
</ins><span class="cx"> 
</span><del>-            if record.enabledForAddressBooks:
</del><ins>+            if record.hasContacts:
</ins><span class="cx">                 yield hasProperty(carddavxml.AddressBookHomeSet.qname())
</span><span class="cx">             else:
</span><del>-                yield doesNotHaveProperty(carddavxml.AddressBookHomeSet.qname())
</del><ins>+                yield doesNotHaveProperty(
+                    carddavxml.AddressBookHomeSet.qname()
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_enabledAsOrganizer(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+        DirectoryPrincipalProvisioningResource
+        .principalForCalendarUserAddress()
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-
</del><span class="cx">         ok_types = (
</span><del>-            DirectoryService.recordType_users,
</del><ins>+            self.directory.recordType.user,
</ins><span class="cx">         )
</span><del>-        for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
</ins><span class="cx"> 
</span><del>-            if record.enabledForCalendaring:
-                principal = provisioningResource.principalForRecord(record)
</del><ins>+            if record.hasCalendars:
+                principal = (
+                    yield provisioningResource.principalForRecord(record)
+                )
</ins><span class="cx">                 self.failIf(principal is None)
</span><del>-                self.assertEqual(principal.enabledAsOrganizer(), recordType in ok_types)
</del><ins>+                self.assertEqual(
+                    principal.enabledAsOrganizer(),
+                    recordType in ok_types
+                )
</ins><span class="cx"> 
</span><span class="cx">         config.Scheduling.Options.AllowGroupAsOrganizer = True
</span><span class="cx">         config.Scheduling.Options.AllowLocationAsOrganizer = True
</span><span class="cx">         config.Scheduling.Options.AllowResourceAsOrganizer = True
</span><span class="cx">         ok_types = (
</span><del>-            DirectoryService.recordType_users,
-            DirectoryService.recordType_groups,
-            DirectoryService.recordType_locations,
-            DirectoryService.recordType_resources,
</del><ins>+            self.directory.recordType.user,
+            self.directory.recordType.group,
+            self.directory.recordType.location,
+            self.directory.recordType.resource,
</ins><span class="cx">         )
</span><del>-        for provisioningResource, recordType, _ignore_recordResource, record in self._allRecords():
-
-            if record.enabledForCalendaring:
-                principal = provisioningResource.principalForRecord(record)
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                principal = (
+                    yield provisioningResource.principalForRecord(record)
+                )
</ins><span class="cx">                 self.failIf(principal is None)
</span><del>-                self.assertEqual(principal.enabledAsOrganizer(), recordType in ok_types)
</del><ins>+                self.assertEqual(
+                    principal.enabledAsOrganizer(),
+                    recordType in ok_types
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    # FIXME: Run DirectoryPrincipalProvisioningResource tests on DirectoryPrincipalTypeProvisioningResource also
</del><ins>+    # FIXME: Run DirectoryPrincipalProvisioningResource tests on
+    # DirectoryPrincipalTypeProvisioningResource also
</ins><span class="cx"> 
</span><span class="cx">     ##
</span><span class="cx">     # DirectoryPrincipalResource
</span><span class="cx">     ##
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_cacheNotifier(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Each DirectoryPrincipalResource should have a cacheNotifier attribute
</span><span class="cx">         that is an instance of DisabledCacheNotifier
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
-                self.failUnless(isinstance(recordResource.cacheNotifier,
-                                           DisabledCacheNotifier))
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if True:  # user.enabled:
+                self.failUnless(
+                    isinstance(
+                        recordResource.cacheNotifier,
+                        DisabledCacheNotifier
+                    )
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_displayName(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.displayName()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if True:  # user.enabled:
</ins><span class="cx">                 self.failUnless(recordResource.displayName())
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -405,10 +543,15 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.groupMembers()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
-                members = yield recordResource.groupMembers()
-                self.failUnless(set(record.members()).issubset(set(r.record for r in members)))
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            members = yield recordResource.groupMembers()
+            self.failUnless(
+                set((yield record.members())).issubset(
+                    set(r.record for r in members)
+                )
+            )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -416,138 +559,157 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.groupMemberships()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if True:  # user.enabled:
</ins><span class="cx">                 memberships = yield recordResource.groupMemberships()
</span><del>-                self.failUnless(set(record.groups()).issubset(set(r.record for r in memberships if hasattr(r, &quot;record&quot;))))
</del><ins>+                self.failUnless(
+                    set((yield record.groups())).issubset(
+                        set(
+                            r.record
+                            for r in memberships if hasattr(r, &quot;record&quot;)
+                        )
+                    )
+                )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_principalUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.principalUID()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
-                self.assertEquals(record.guid, recordResource.principalUID())
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            self.assertEquals(record.uid, recordResource.principalUID())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_calendarUserAddresses(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.calendarUserAddresses()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.assertEqual(set(record.calendarUserAddresses), set(recordResource.calendarUserAddresses()))
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                self.assertEqual(
+                    set(record.calendarUserAddresses),
+                    set(recordResource.calendarUserAddresses())
+                )
</ins><span class="cx"> 
</span><span class="cx">                 # Verify that if not enabled for calendaring, no CUAs:
</span><del>-                record.enabledForCalendaring = False
</del><ins>+                record.hasCalendars = False
</ins><span class="cx">                 self.failIf(recordResource.calendarUserAddresses())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_canonicalCalendarUserAddress(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.canonicalCalendarUserAddress()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.failUnless(recordResource.canonicalCalendarUserAddress().startswith(&quot;urn:uuid:&quot;))
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                if self.directory.fieldName.guid in record.fields:
+                    self.failUnless(
+                        recordResource.canonicalCalendarUserAddress()
+                        .startswith(&quot;urn:uuid:&quot;)
+                    )
+                elif self.directory.fieldName.emailAddresses in record.fields:
+                    self.failUnless(
+                        recordResource.canonicalCalendarUserAddress()
+                        .startswith(&quot;mailto:&quot;)
+                    )
+                else:
+                    self.failUnless(
+                        recordResource.canonicalCalendarUserAddress()
+                        .startswith(&quot;/principals/__uids__/&quot;)
+                    )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_addressBookHomeURLs(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.addressBookHomeURLs(),
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        # No addressbook home provisioner should result in no addressbook homes.
-        for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForAddressBooks:
-                self.failIf(tuple(recordResource.addressBookHomeURLs()))
</del><span class="cx"> 
</span><del>-        # Need to create a addressbook home provisioner for each service.
-        addressBookRootResources = {}
-
-        for directory in self.directoryServices:
-            path = os.path.join(self.docroot, directory.__class__.__name__)
-
-            if os.path.exists(path):
-                rmdir(path)
-            os.mkdir(path)
-
-            # Need a data store
-            _newStore = CommonDataStore(path, None, None, True, False)
-
-            provisioningResource = DirectoryAddressBookHomeProvisioningResource(
-                directory,
-                &quot;/addressbooks/&quot;,
-                _newStore
-            )
-
-            addressBookRootResources[directory.__class__.__name__] = provisioningResource
-
-        # AddressBook home provisioners should result in addressBook homes.
-        for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForAddressBooks:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasContacts:
</ins><span class="cx">                 homeURLs = tuple(recordResource.addressBookHomeURLs())
</span><span class="cx">                 self.failUnless(homeURLs)
</span><span class="cx"> 
</span><del>-                # Turn off enabledForAddressBooks and addressBookHomeURLs should
-                # be empty
-                record.enabledForAddressBooks = False
</del><ins>+                # Turn off hasContacts and addressBookHomeURLs
+                # should be empty
+                record.hasContacts = False
</ins><span class="cx">                 self.failIf(tuple(recordResource.addressBookHomeURLs()))
</span><del>-                record.enabledForAddressBooks = True
</del><ins>+                record.hasContacts = True
</ins><span class="cx"> 
</span><del>-                addressBookRootURL = addressBookRootResources[record.service.__class__.__name__].url()
</del><ins>+                addressBookRootResource = yield self.actualRoot.getChild(&quot;addressbooks&quot;)
+                addressBookRootURL = addressBookRootResource.url()
</ins><span class="cx"> 
</span><span class="cx">                 for homeURL in homeURLs:
</span><span class="cx">                     self.failUnless(homeURL.startswith(addressBookRootURL))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_calendarHomeURLs(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.calendarHomeURLs(),
</span><span class="cx">         DirectoryPrincipalResource.scheduleInboxURL(),
</span><span class="cx">         DirectoryPrincipalResource.scheduleOutboxURL()
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        # No calendar home provisioner should result in no calendar homes.
-        for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.failIf(tuple(recordResource.calendarHomeURLs()))
-                self.failIf(recordResource.scheduleInboxURL())
-                self.failIf(recordResource.scheduleOutboxURL())
</del><ins>+        # # No calendar home provisioner should result in no calendar homes.
+        # for (
+        #     provisioningResource, recordType, recordResource, record
+        # ) in (yield self._allRecords()):
+        #     if record.hasCalendars:
+        #         self.failIf(tuple(recordResource.calendarHomeURLs()))
+        #         self.failIf(recordResource.scheduleInboxURL())
+        #         self.failIf(recordResource.scheduleOutboxURL())
</ins><span class="cx"> 
</span><del>-        # Need to create a calendar home provisioner for each service.
-        calendarRootResources = {}
</del><ins>+        # # Need to create a calendar home provisioner for each service.
+        # calendarRootResources = {}
</ins><span class="cx"> 
</span><del>-        for directory in self.directoryServices:
-            path = os.path.join(self.docroot, directory.__class__.__name__)
</del><ins>+        # path = os.path.join(self.docroot, self.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(
+        #     self.directory,
+        #     &quot;/calendars/&quot;,
+        #     _newStore
+        # )
</ins><span class="cx"> 
</span><del>-            calendarRootResources[directory.__class__.__name__] = provisioningResource
</del><ins>+        # calendarRootResources[self.directory.__class__.__name__] = (
+        #     provisioningResource
+        # )
</ins><span class="cx"> 
</span><span class="cx">         # Calendar home provisioners should result in calendar homes.
</span><del>-        for provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
</ins><span class="cx">                 homeURLs = tuple(recordResource.calendarHomeURLs())
</span><span class="cx">                 self.failUnless(homeURLs)
</span><span class="cx"> 
</span><del>-                # Turn off enabledForCalendaring and calendarHomeURLs should
</del><ins>+                # Turn off hasCalendars and calendarHomeURLs should
</ins><span class="cx">                 # be empty
</span><del>-                record.enabledForCalendaring = False
</del><ins>+                record.hasCalendars = False
</ins><span class="cx">                 self.failIf(tuple(recordResource.calendarHomeURLs()))
</span><del>-                record.enabledForCalendaring = True
</del><ins>+                record.hasCalendars = True
</ins><span class="cx"> 
</span><del>-                calendarRootURL = calendarRootResources[record.service.__class__.__name__].url()
</del><ins>+                calendarRootResource = yield self.rootResource.getChild(&quot;calendars&quot;)
+                calendarRootURL = calendarRootResource.url()
</ins><span class="cx"> 
</span><span class="cx">                 inboxURL = recordResource.scheduleInboxURL()
</span><span class="cx">                 outboxURL = recordResource.scheduleOutboxURL()
</span><span class="lines">@@ -572,69 +734,121 @@
</span><span class="cx">                 self.failIf(outboxURL)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_canAutoSchedule(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.canAutoSchedule()
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        # Set all resources and locations to auto-schedule, plus one user
-        for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                if recordType in (&quot;locations&quot;, &quot;resources&quot;) or record.uid == &quot;cdaboo&quot;:
-                    recordResource.record.autoSchedule = True
</del><ins>+        # This test used to set the autoschedule mode in a separate loop, but
+        # the records aren't cached, so I've moved this into the later loop
</ins><span class="cx"> 
</span><ins>+        # # Set all resources and locations to auto-schedule, plus one user
+        # for (
+        #     provisioningResource, recordType, recordResource, record
+        # ) in (yield self._allRecords()):
+        #     if record.hasCalendars:
+        #         print(&quot;before&quot;, record, record.recordType, record.uid)
+        #         if (
+        #             recordType in (CalRecordType.location, CalRecordType.resource) or
+        #             record.uid == &quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;
+        #         ):
+        #             record.fields[record.service.fieldName.lookupByName(&quot;autoScheduleMode&quot;)] = AutoScheduleMode.acceptIfFreeDeclineIfBusy
+        #             print(&quot;modifying&quot;, record, record.fields)
+
</ins><span class="cx">         # Default state - resources and locations, enabled, others not
</span><del>-        for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                if recordType in (&quot;locations&quot;, &quot;resources&quot;):
-                    self.assertTrue(recordResource.canAutoSchedule())
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                if recordType in (CalRecordType.location, CalRecordType.resource):
+                    self.assertTrue((yield recordResource.canAutoSchedule()))
</ins><span class="cx">                 else:
</span><del>-                    self.assertFalse(recordResource.canAutoSchedule())
</del><ins>+                    self.assertFalse((yield recordResource.canAutoSchedule()))
</ins><span class="cx"> 
</span><span class="cx">         # Set config to allow users
</span><span class="cx">         self.patch(config.Scheduling.Options.AutoSchedule, &quot;AllowUsers&quot;, True)
</span><del>-        for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                if recordType in (&quot;locations&quot;, &quot;resources&quot;) or record.uid == &quot;cdaboo&quot;:
-                    self.assertTrue(recordResource.canAutoSchedule())
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                if (
+                    recordType in (CalRecordType.location, CalRecordType.resource) or
+                    record.uid == u&quot;5A985493-EE2C-4665-94CF-4DFEA3A89500&quot;
+                ):
+                    record.fields[
+                        record.service.fieldName.lookupByName(
+                            &quot;autoScheduleMode&quot;
+                        )
+                    ] = AutoScheduleMode.acceptIfFreeDeclineIfBusy
+
+                    self.assertTrue((yield recordResource.canAutoSchedule()))
</ins><span class="cx">                 else:
</span><del>-                    self.assertFalse(recordResource.canAutoSchedule())
</del><ins>+                    self.assertFalse((yield recordResource.canAutoSchedule()))
</ins><span class="cx"> 
</span><span class="cx">         # Set config to disallow all
</span><span class="cx">         self.patch(config.Scheduling.Options.AutoSchedule, &quot;Enabled&quot;, False)
</span><del>-        for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
-            if record.enabledForCalendaring:
-                self.assertFalse(recordResource.canAutoSchedule())
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if record.hasCalendars:
+                self.assertFalse((yield recordResource.canAutoSchedule()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_canAutoScheduleAutoAcceptGroup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DirectoryPrincipalResource.canAutoSchedule(organizer)
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        # Location &quot;apollo&quot; has an auto-accept group (&quot;both_coasts&quot;) set in augments.xml,
-        # therefore any organizer in that group should be able to auto schedule
</del><ins>+        # Location &quot;apollo&quot; has an auto-accept group (&quot;both_coasts&quot;) set in
+        # augments.xml, therefore any organizer in that group should be able to
+        # auto schedule
</ins><span class="cx"> 
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.uid == &quot;apollo&quot;:
</del><ins>+        record = yield self.directory.recordWithUID(u&quot;apollo&quot;)
</ins><span class="cx"> 
</span><del>-                # No organizer
-                self.assertFalse(recordResource.canAutoSchedule())
</del><ins>+        # Turn off this record's autoschedule
+        record.fields[
+            record.service.fieldName.lookupByName(
+                &quot;autoScheduleMode&quot;
+            )
+        ] = AutoScheduleMode.none
</ins><span class="cx"> 
</span><del>-                # Organizer in auto-accept group
-                self.assertTrue(recordResource.canAutoSchedule(organizer=&quot;mailto:wsanchez@example.com&quot;))
-                # Organizer not in auto-accept group
-                self.assertFalse(recordResource.canAutoSchedule(organizer=&quot;mailto:a@example.com&quot;))
</del><ins>+        # No organizer
+        self.assertFalse((yield record.canAutoSchedule()))
</ins><span class="cx"> 
</span><ins>+        # Organizer in auto-accept group
+        self.assertTrue(
+            (
+                yield record.canAutoSchedule(
+                    organizer=&quot;mailto:wsanchez@example.com&quot;
+                )
+            )
+        )
+        # Organizer not in auto-accept group
+        self.assertFalse(
+            (
+                yield record.canAutoSchedule(
+                    organizer=&quot;mailto:a@example.com&quot;
+                )
+            )
+        )
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_defaultAccessControlList_principals(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Default access controls for principals.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-            if record.enabled:
-                for args in _authReadOnlyPrivileges(self, recordResource, recordResource.principalURL()):
</del><ins>+        for (
+            provisioningResource, recordType, recordResource, record
+        ) in (yield self._allRecords()):
+            if True:  # user.enabled:
+                for args in (
+                    yield _authReadOnlyPrivileges(
+                        self, recordResource, recordResource.principalURL()
+                    )
+                ):
</ins><span class="cx">                     yield self._checkPrivileges(*args)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -643,19 +857,26 @@
</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>+        provisioningResource = self.principalRootResource
</ins><span class="cx"> 
</span><del>-            for args in _authReadOnlyPrivileges(self, provisioningResource, provisioningResource.principalCollectionURL()):
-                yield self._checkPrivileges(*args)
</del><ins>+        for args in (
+            yield _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 = yield 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 (
+                yield _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">@@ -668,23 +889,71 @@
</span><span class="cx">             def qname(self):
</span><span class="cx">                 return self.ns, self.name
</span><span class="cx"> 
</span><del>-        provisioningResource = self.principalRootResources['XMLDirectoryService']
</del><ins>+        provisioningResource = self.principalRootResource
</ins><span class="cx"> 
</span><span class="cx">         expected = (
</span><del>-            (&quot;DAV:&quot;, &quot;displayname&quot;, &quot;morgen&quot;, &quot;fullName&quot;, &quot;morgen&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;, &quot;INDIVIDUAL&quot;, &quot;recordType&quot;, &quot;users&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;, &quot;GROUP&quot;, &quot;recordType&quot;, &quot;groups&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;, &quot;RESOURCE&quot;, &quot;recordType&quot;, &quot;resources&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;, &quot;ROOM&quot;, &quot;recordType&quot;, &quot;locations&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/&quot;, &quot;guid&quot;, &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;http://example.com:8008/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/&quot;, &quot;guid&quot;, &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;urn:uuid:AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;, &quot;guid&quot;, &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;/principals/users/example/&quot;, &quot;recordName&quot;, &quot;example&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;https://example.com:8443/principals/users/example/&quot;, &quot;recordName&quot;, &quot;example&quot;),
-            (&quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;, &quot;mailto:example@example.com&quot;, &quot;emailAddresses&quot;, &quot;example@example.com&quot;),
-            (&quot;http://calendarserver.org/ns/&quot;, &quot;first-name&quot;, &quot;morgen&quot;, &quot;firstName&quot;, &quot;morgen&quot;),
-            (&quot;http://calendarserver.org/ns/&quot;, &quot;last-name&quot;, &quot;sagen&quot;, &quot;lastName&quot;, &quot;sagen&quot;),
-            (&quot;http://calendarserver.org/ns/&quot;, &quot;email-address-set&quot;, &quot;example@example.com&quot;, &quot;emailAddresses&quot;, &quot;example@example.com&quot;),
</del><ins>+            (
+                &quot;DAV:&quot;, &quot;displayname&quot;,
+                &quot;morgen&quot;, &quot;fullNames&quot;, &quot;morgen&quot;
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;,
+                &quot;INDIVIDUAL&quot;, &quot;recordType&quot;, RecordType.user
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;,
+                &quot;GROUP&quot;, &quot;recordType&quot;, RecordType.group
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;,
+                &quot;RESOURCE&quot;, &quot;recordType&quot;, CalRecordType.resource
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-type&quot;,
+                &quot;ROOM&quot;, &quot;recordType&quot;, CalRecordType.location
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/&quot;,
+                &quot;uid&quot;, &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;http://example.com:8008/principals/__uids__/&quot;
+                &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/&quot;,
+                &quot;uid&quot;, &quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;urn:uuid:AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;,
+                &quot;guid&quot;, UUID(&quot;AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA&quot;)
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;/principals/users/example/&quot;, &quot;recordName&quot;, &quot;example&quot;
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;https://example.com:8443/principals/users/example/&quot;,
+                &quot;recordName&quot;, &quot;example&quot;
+            ),
+            (
+                &quot;urn:ietf:params:xml:ns:caldav&quot;, &quot;calendar-user-address-set&quot;,
+                &quot;mailto:example@example.com&quot;,
+                &quot;emailAddresses&quot;, &quot;example@example.com&quot;
+            ),
+            (
+                &quot;http://calendarserver.org/ns/&quot;, &quot;first-name&quot;,
+                &quot;morgen&quot;, &quot;firstName&quot;, &quot;morgen&quot;
+            ),
+            (
+                &quot;http://calendarserver.org/ns/&quot;, &quot;last-name&quot;,
+                &quot;sagen&quot;, &quot;lastName&quot;, &quot;sagen&quot;
+            ),
+            (
+                &quot;http://calendarserver.org/ns/&quot;, &quot;email-address-set&quot;,
+                &quot;example@example.com&quot;, &quot;emailAddresses&quot;, &quot;example@example.com&quot;
+            ),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for ns, property, match, field, converted in expected:
</span><span class="lines">@@ -695,43 +964,57 @@
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _allRecords(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @return: an iterable of tuples
</span><del>-            C{(provisioningResource, recordType, recordResource, record)}, where
-            C{provisioningResource} is the root provisioning resource,
-            C{recordType} is the record type,
-            C{recordResource} is the principal resource and
-            C{record} is the directory service record
-            for each record in each directory in C{directoryServices}.
</del><ins>+            C{(provisioningResource, recordType, recordResource, record)},
+            where C{provisioningResource} is the root provisioning resource,
+            C{recordType} is the record type, C{recordResource} is the
+            principal resource and C{record} is the directory service record
+            for each record the directory.
</ins><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>+        provisioningResource = self.principalRootResource
+        results = []
+        for recordType in self.directory.recordTypes():
+            for record in (
+                yield self.directory.recordsWithRecordType(recordType)
+            ):
+                recordResource = (
+                    yield provisioningResource.principalForRecord(record)
+                )
+                results.append(
+                    (provisioningResource, recordType, recordResource, record)
+                )
+        returnValue(results)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _checkPrivileges(self, resource, url, principal, privilege, allowed):
</span><span class="cx">         request = SimpleRequest(self.site, &quot;GET&quot;, &quot;/&quot;)
</span><span class="cx"> 
</span><span class="cx">         def gotResource(resource):
</span><del>-            d = resource.checkPrivileges(request, (privilege,), principal=davxml.Principal(principal))
</del><ins>+            d = resource.checkPrivileges(
+                request, (privilege,), principal=davxml.Principal(principal)
+            )
</ins><span class="cx">             if allowed:
</span><span class="cx">                 def onError(f):
</span><span class="cx">                     f.trap(AccessDeniedError)
</span><span class="cx">                     #print(resource.readDeadProperty(davxml.ACL))
</span><del>-                    self.fail(&quot;%s should have %s privilege on %r&quot; % (principal.sname(), privilege.sname(), resource))
</del><ins>+                    self.fail(
+                        &quot;%s should have %s privilege on %r&quot;
+                        % (principal.sname(), privilege.sname(), resource)
+                    )
</ins><span class="cx">                 d.addErrback(onError)
</span><span class="cx">             else:
</span><span class="cx">                 def expectAccessDenied(f):
</span><span class="cx">                     f.trap(AccessDeniedError)
</span><ins>+
</ins><span class="cx">                 def onSuccess(_):
</span><span class="cx">                     #print(resource.readDeadProperty(davxml.ACL))
</span><del>-                    self.fail(&quot;%s should not have %s privilege on %r&quot; % (principal.sname(), privilege.sname(), resource))
</del><ins>+                    self.fail(
+                        &quot;%s should not have %s privilege on %r&quot;
+                        % (principal.sname(), privilege.sname(), resource)
+                    )
</ins><span class="cx">                 d.addCallbacks(onSuccess, expectAccessDenied)
</span><span class="cx">             return d
</span><span class="cx"> 
</span><span class="lines">@@ -741,14 +1024,30 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def _authReadOnlyPrivileges(self, resource, url):
</span><span class="cx">     items = []
</span><del>-    for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
-        if record.enabled:
-            items.append((davxml.HRef().fromString(recordResource.principalURL()), davxml.Read()  , True))
-            items.append((davxml.HRef().fromString(recordResource.principalURL()), davxml.Write() , False))
-    items.append((davxml.Unauthenticated() , davxml.Read()  , False))
-    items.append((davxml.Unauthenticated() , davxml.Write() , False))
</del><ins>+    for (
+        provisioningResource, recordType, recordResource, record
+    ) in (yield self._allRecords()):
+        if True:  # user.enabled:
+            items.append((
+                davxml.HRef().fromString(recordResource.principalURL()),
+                davxml.Read(), True
+            ))
+            items.append((
+                davxml.HRef().fromString(recordResource.principalURL()),
+                davxml.Write(), False
+            ))
+    items.append((
+        davxml.Unauthenticated(), davxml.Read(), False
+    ))
+    items.append((
+        davxml.Unauthenticated(), davxml.Write(), False
+    ))
</ins><span class="cx"> 
</span><ins>+    results = []
</ins><span class="cx">     for principal, privilege, allowed in items:
</span><del>-        yield resource, url, principal, privilege, allowed
</del><ins>+        results.append((resource, url, principal, privilege, allowed))
+
+    returnValue(results)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_proxyprincipalmemberspy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_proxyprincipalmembers.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_proxyprincipalmembers.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_proxyprincipalmembers.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_resourcespy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_resources.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_resources.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_resources.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_wikipy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_wiki.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_wiki.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_wiki.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,116 +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.test.util import TestCase
-from twistedcaldav.directory.wiki import (
-    WikiDirectoryService, WikiDirectoryRecord, getWikiAccess
-)
-from twisted.internet.defer import inlineCallbacks, succeed
-from twisted.web.xmlrpc import Fault
-from txweb2.http import HTTPError
-from txweb2 import responsecode
-from twistedcaldav.config import config
-from twisted.web.error import Error as WebError
-
-class WikiTestCase(TestCase):
-    &quot;&quot;&quot;
-    Test the Wiki Directory Service
-    &quot;&quot;&quot;
-
-    def test_enabled(self):
-        service = WikiDirectoryService()
-        service.realmName = &quot;Test&quot;
-        record = WikiDirectoryRecord(service,
-            WikiDirectoryService.recordType_wikis,
-            &quot;test&quot;,
-            None
-        )
-        self.assertTrue(record.enabled)
-        self.assertTrue(record.enabledForCalendaring)
-
-
-    @inlineCallbacks
-    def test_getWikiAccessLion(self):
-        &quot;&quot;&quot;
-        XMLRPC Faults result in HTTPErrors
-        &quot;&quot;&quot;
-        self.patch(config.Authentication.Wiki, &quot;LionCompatibility&quot;, True)
-
-        def successful(self, user, wiki):
-            return succeed(&quot;read&quot;)
-
-        def fault2(self, user, wiki):
-            raise Fault(2, &quot;Bad session&quot;)
-
-        def fault12(self, user, wiki):
-            raise Fault(12, &quot;Non-existent wiki&quot;)
-
-        def fault13(self, user, wiki):
-            raise Fault(13, &quot;Non-existent wiki&quot;)
-
-        access = (yield getWikiAccess(&quot;user&quot;, &quot;wiki&quot;, method=successful))
-        self.assertEquals(access, &quot;read&quot;)
-
-        for (method, code) in (
-            (fault2, responsecode.FORBIDDEN),
-            (fault12, responsecode.NOT_FOUND),
-            (fault13, responsecode.SERVICE_UNAVAILABLE),
-        ):
-            try:
-                access = (yield getWikiAccess(&quot;user&quot;, &quot;wiki&quot;, method=method))
-            except HTTPError, e:
-                self.assertEquals(e.response.code, code)
-            except:
-                self.fail(&quot;Incorrect exception&quot;)
-            else:
-                self.fail(&quot;Didn't raise exception&quot;)
-
-
-    @inlineCallbacks
-    def test_getWikiAccess(self):
-        &quot;&quot;&quot;
-        Non-200 GET responses result in HTTPErrors
-        &quot;&quot;&quot;
-        self.patch(config.Authentication.Wiki, &quot;LionCompatibility&quot;, False)
-
-        def successful(user, wiki, host=None, port=None):
-            return succeed(&quot;read&quot;)
-
-        def forbidden(user, wiki, host=None, port=None):
-            raise WebError(403, message=&quot;Unknown user&quot;)
-
-        def notFound(user, wiki, host=None, port=None):
-            raise WebError(404, message=&quot;Non-existent wiki&quot;)
-
-        def other(user, wiki, host=None, port=None):
-            raise WebError(500, &quot;Error&quot;)
-
-        access = (yield getWikiAccess(&quot;user&quot;, &quot;wiki&quot;, method=successful))
-        self.assertEquals(access, &quot;read&quot;)
-
-        for (method, code) in (
-            (forbidden, responsecode.FORBIDDEN),
-            (notFound, responsecode.NOT_FOUND),
-            (other, responsecode.SERVICE_UNAVAILABLE),
-        ):
-            try:
-                access = (yield getWikiAccess(&quot;user&quot;, &quot;wiki&quot;, method=method))
-            except HTTPError, e:
-                self.assertEquals(e.response.code, code)
-            except:
-                self.fail(&quot;Incorrect exception&quot;)
-            else:
-                self.fail(&quot;Didn't raise exception&quot;)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytesttest_xmlfilepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_xmlfile.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_xmlfile.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/test_xmlfile.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorytestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/test/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -212,9 +212,9 @@
</span><span class="cx">             guid = record.guid
</span><span class="cx"> 
</span><span class="cx">         addresses = set(value(&quot;addresses&quot;))
</span><del>-        if record.enabledForCalendaring:
</del><ins>+        if record.hasCalendars:
</ins><span class="cx">             addresses.add(&quot;urn:uuid:%s&quot; % (record.guid,))
</span><del>-            addresses.add(&quot;/principals/__uids__/%s/&quot; % (record.guid,))
</del><ins>+            addresses.add(&quot;/principals/__uids__/%s/&quot; % (record.uid,))
</ins><span class="cx">             addresses.add(&quot;/principals/%s/%s/&quot; % (record.recordType, record.shortNames[0],))
</span><span class="cx"> 
</span><span class="cx">         if hasattr(record.service, &quot;recordTypePrefix&quot;):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorywikipy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/wiki.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/wiki.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/wiki.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,367 +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 for users who are allowed to authorize
-as other principals.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;WikiDirectoryService&quot;,
-]
-
-from calendarserver.platform.darwin.wiki import accessForUserToWiki
-
-from twext.internet.gaiendpoint import MultiFailure
-from twext.python.log import Logger
-from txweb2 import responsecode
-from txweb2.auth.wrapper import UnauthorizedResponse
-from txweb2.dav.resource import TwistedACLInheritable
-from txweb2.http import HTTPError, StatusResponse
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.web.error import Error as WebError
-from twisted.web.xmlrpc import Proxy, Fault
-
-from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService, \
-    DirectoryRecord, UnknownRecordTypeError
-
-from txdav.xml import element as davxml
-
-log = Logger()
-
-class WikiDirectoryService(DirectoryService):
-    &quot;&quot;&quot;
-    L{IDirectoryService} implementation for Wikis.
-    &quot;&quot;&quot;
-    baseGUID = &quot;D79EF1E0-9A42-11DD-AD8B-0800200C9A66&quot;
-
-    realmName = None
-
-    recordType_wikis = &quot;wikis&quot;
-
-    UIDPrefix = &quot;wiki-&quot;
-
-
-    def __repr__(self):
-        return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.realmName)
-
-
-    def __init__(self):
-        super(WikiDirectoryService, self).__init__()
-        self.byUID = {}
-        self.byShortName = {}
-
-
-    def recordTypes(self):
-        return (WikiDirectoryService.recordType_wikis,)
-
-
-    def listRecords(self, recordType):
-        return ()
-
-
-    def recordWithShortName(self, recordType, shortName):
-        if recordType != WikiDirectoryService.recordType_wikis:
-            raise UnknownRecordTypeError(recordType)
-
-        if shortName in self.byShortName:
-            record = self.byShortName[shortName]
-            return record
-
-        record = self._addRecord(shortName)
-        return record
-
-
-    def recordWithUID(self, uid):
-
-        if uid in self.byUID:
-            record = self.byUID[uid]
-            return record
-
-        if uid.startswith(self.UIDPrefix):
-            shortName = uid[len(self.UIDPrefix):]
-            record = self._addRecord(shortName)
-            return record
-        else:
-            return None
-
-
-    def _addRecord(self, shortName):
-
-        record = WikiDirectoryRecord(
-            self,
-            WikiDirectoryService.recordType_wikis,
-            shortName,
-            None
-        )
-        self.byUID[record.uid] = record
-        self.byShortName[shortName] = record
-        return record
-
-
-
-class WikiDirectoryRecord(DirectoryRecord):
-    &quot;&quot;&quot;
-    L{DirectoryRecord} implementation for Wikis.
-    &quot;&quot;&quot;
-
-    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
-
-
-
-@inlineCallbacks
-def getWikiAccess(userID, wikiID, method=None):
-    &quot;&quot;&quot;
-    Ask the wiki server we're paired with what level of access the userID has
-    for the given wikiID.  Possible values are &quot;read&quot;, &quot;write&quot;, and &quot;admin&quot;
-    (which we treat as &quot;write&quot;).
-
-    @param userID: the GUID (UUID) of the user's directory record.
-    @type userID: L{bytes} (UTF-8)
-
-    @param wikiID: the short name of the wiki principal's synthetic directory
-        record.  (See L{WikiDirectoryService}).
-    @type wikiID: L{bytes} (UTF-8)
-
-    @return: A string indicating the level of access that the given user has to
-        the given wiki.  Possible values are:
-
-        1. C{b&quot;no-access&quot;} for read-only access
-
-        2. C{b&quot;no-access&quot;} for read/write access
-
-        3. C{b&quot;no-access&quot;} for administrative access (which, for calendaring
-           purposes, should be equialent to read/write)
-
-        4. C{b&quot;no-access&quot;} for a user who is not allowed to see the wiki at
-           all.
-
-    @rtype: L{bytes}
-
-    @raise: L{HTTPError} indicating that there is a problem requesting
-        permission information.  This may be raised with a few different status
-        codes, each indicating a different problem:
-
-        1. L{responsecode.FORBIDDEN}: The user represented by C{userID} did not
-           exist.
-
-        2. L{responsecode.NOT_FOUND}: The wiki represented by C{wikiID} did not
-           exist.
-
-        3. L{responsecode.SERVICE_UNAVAILABLE}: The service that we are
-           checking permissions with is currently offline or responding with an
-           unknown fault.
-    &quot;&quot;&quot;
-    wikiConfig = config.Authentication.Wiki
-    if method is None:
-        if wikiConfig.LionCompatibility:
-            method = Proxy(wikiConfig[&quot;URL&quot;]).callRemote
-        else:
-            method = accessForUserToWiki
-    try:
-
-        log.debug(&quot;Looking up Wiki ACL for: user [%s], wiki [%s]&quot; % (userID,
-            wikiID))
-        if wikiConfig.LionCompatibility:
-            access = (yield method(wikiConfig[&quot;WikiMethod&quot;],
-                userID, wikiID))
-        else:
-            access = (yield method(userID, wikiID,
-                host=wikiConfig.CollabHost, port=wikiConfig.CollabPort))
-
-        log.debug(&quot;Wiki ACL result: user [%s], wiki [%s], access [%s]&quot; %
-            (userID, wikiID, access))
-        returnValue(access)
-
-    except MultiFailure, e:
-        log.error(&quot;Wiki ACL error: user [%s], wiki [%s], MultiFailure [%s]&quot; %
-            (userID, wikiID, e))
-        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
-            &quot;\n&quot;.join([str(f) for f in e.failures])))
-
-    except Fault, fault:
-
-        log.debug(&quot;Wiki ACL result: user [%s], wiki [%s], FAULT [%s]&quot; % (userID,
-            wikiID, fault))
-
-        if fault.faultCode == 2: # non-existent user
-            raise HTTPError(StatusResponse(responsecode.FORBIDDEN,
-                fault.faultString))
-
-        elif fault.faultCode == 12: # non-existent wiki
-            raise HTTPError(StatusResponse(responsecode.NOT_FOUND,
-                fault.faultString))
-
-        else:
-            # Unknown fault returned from wiki server.  Log the error and
-            # return 503 Service Unavailable to the client.
-            log.error(&quot;Wiki ACL error: user [%s], wiki [%s], FAULT [%s]&quot; %
-                (userID, wikiID, fault))
-            raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
-                fault.faultString))
-
-    except WebError, w:
-        status = int(w.status)
-
-        log.debug(&quot;Wiki ACL result: user [%s], wiki [%s], status [%s]&quot; %
-            (userID, wikiID, status))
-
-        if status == responsecode.FORBIDDEN: # non-existent user
-            raise HTTPError(StatusResponse(responsecode.FORBIDDEN,
-                &quot;Unknown User&quot;))
-
-        elif status == responsecode.NOT_FOUND: # non-existent wiki
-            raise HTTPError(StatusResponse(responsecode.NOT_FOUND,
-                &quot;Unknown Wiki&quot;))
-
-        else:
-            # Unknown fault returned from wiki server.  Log the error and
-            # return 503 Service Unavailable to the client.
-            log.error(&quot;Wiki ACL error: user [%s], wiki [%s], status [%s]&quot; %
-                (userID, wikiID, status))
-            raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
-                w.message))
-
-
-
-@inlineCallbacks
-def getWikiACL(resource, request):
-    &quot;&quot;&quot;
-    Ask the wiki server we're paired with what level of access the authnUser has.
-
-    Returns an ACL.
-
-    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
-
-    if (not hasattr(resource, &quot;record&quot;) or
-        resource.record.recordType != WikiDirectoryService.recordType_wikis):
-        returnValue(None)
-
-    if hasattr(request, 'wikiACL'):
-        returnValue(request.wikiACL)
-
-    userID = &quot;unauthenticated&quot;
-    wikiID = resource.record.shortNames[0]
-
-    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
-
-    try:
-        access = (yield getWikiAccess(userID, wikiID))
-
-        # 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()),
-
-                        # 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)
-
-        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)
-
-        else: # &quot;no-access&quot;:
-
-            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)
-
-            raise HTTPError(
-                StatusResponse(
-                    responsecode.FORBIDDEN,
-                    &quot;You are not allowed to access this wiki&quot;
-                )
-            )
-
-    except HTTPError:
-        # pass through the HTTPError we might have raised above
-        raise
-
-    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></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryxmlaccountsparserpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlaccountsparser.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlaccountsparser.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlaccountsparser.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,283 +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;
-XML based user/group/resource configuration file handling.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;XMLAccountsParser&quot;,
-]
-
-from twext.python.filepath import CachingFilePath as FilePath
-
-from twext.python.log import Logger
-
-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.util import normalizeUUID
-from twistedcaldav.xmlutil import readXML
-
-import re
-import hashlib
-
-log = Logger()
-
-ELEMENT_ACCOUNTS = &quot;accounts&quot;
-ELEMENT_USER = &quot;user&quot;
-ELEMENT_GROUP = &quot;group&quot;
-ELEMENT_LOCATION = &quot;location&quot;
-ELEMENT_RESOURCE = &quot;resource&quot;
-ELEMENT_ADDRESS = &quot;address&quot;
-
-ELEMENT_SHORTNAME = &quot;uid&quot;
-ELEMENT_GUID = &quot;guid&quot;
-ELEMENT_PASSWORD = &quot;password&quot;
-ELEMENT_NAME = &quot;name&quot;
-ELEMENT_FIRST_NAME = &quot;first-name&quot;
-ELEMENT_LAST_NAME = &quot;last-name&quot;
-ELEMENT_EMAIL_ADDRESS = &quot;email-address&quot;
-ELEMENT_MEMBERS = &quot;members&quot;
-ELEMENT_MEMBER = &quot;member&quot;
-ELEMENT_EXTRAS = &quot;extras&quot;
-
-ATTRIBUTE_REALM = &quot;realm&quot;
-ATTRIBUTE_REPEAT = &quot;repeat&quot;
-ATTRIBUTE_RECORDTYPE = &quot;type&quot;
-
-VALUE_TRUE = &quot;true&quot;
-VALUE_FALSE = &quot;false&quot;
-
-RECORD_TYPES = {
-    ELEMENT_USER     : DirectoryService.recordType_users,
-    ELEMENT_GROUP    : DirectoryService.recordType_groups,
-    ELEMENT_LOCATION : DirectoryService.recordType_locations,
-    ELEMENT_RESOURCE : DirectoryService.recordType_resources,
-    ELEMENT_ADDRESS  : DirectoryService.recordType_addresses,
-}
-
-class XMLAccountsParser(object):
-    &quot;&quot;&quot;
-    XML account configuration file parser.
-    &quot;&quot;&quot;
-    def __repr__(self):
-        return &quot;&lt;%s %r&gt;&quot; % (self.__class__.__name__, self.xmlFile)
-
-
-    def __init__(self, xmlFile, externalUpdate=True):
-
-        if type(xmlFile) is str:
-            xmlFile = FilePath(xmlFile)
-
-        self.xmlFile = xmlFile
-        self.realm = None
-        self.items = {}
-
-        for recordType in RECORD_TYPES.values():
-            self.items[recordType] = {}
-
-        # Read in XML
-        try:
-            _ignore_tree, accounts_node = readXML(self.xmlFile.path, ELEMENT_ACCOUNTS)
-        except ValueError, e:
-            raise RuntimeError(&quot;XML parse error for '%s' because: %s&quot; % (self.xmlFile, e,))
-        self._parseXML(accounts_node)
-
-
-    def _parseXML(self, node):
-        &quot;&quot;&quot;
-        Parse the XML root node from the accounts configuration document.
-        @param node: the L{Node} to parse.
-        &quot;&quot;&quot;
-        self.realm = node.get(ATTRIBUTE_REALM, &quot;&quot;).encode(&quot;utf-8&quot;)
-
-        def updateMembership(group):
-            # Update group membership
-            for recordType, shortName in group.members:
-                item = self.items[recordType].get(shortName)
-                if item is not None:
-                    item.groups.add(group.shortNames[0])
-
-        for child in node:
-            try:
-                recordType = RECORD_TYPES[child.tag]
-            except KeyError:
-                raise RuntimeError(&quot;Unknown account type: %s&quot; % (child.tag,))
-
-            repeat = int(child.get(ATTRIBUTE_REPEAT, 0))
-
-            principal = XMLAccountRecord(recordType)
-            principal.parseXML(child)
-            if repeat &gt; 0:
-                for i in xrange(1, repeat + 1):
-                    newprincipal = principal.repeat(i)
-                    self.items[recordType][newprincipal.shortNames[0]] = newprincipal
-            else:
-                self.items[recordType][principal.shortNames[0]] = principal
-
-        # Do reverse membership mapping only after all records have been read in
-        for records in self.items.itervalues():
-            for principal in records.itervalues():
-                updateMembership(principal)
-
-
-
-class XMLAccountRecord (object):
-    &quot;&quot;&quot;
-    Contains provision information for one user.
-    &quot;&quot;&quot;
-    def __init__(self, recordType):
-        &quot;&quot;&quot;
-        @param recordType: record type for directory entry.
-        &quot;&quot;&quot;
-        self.recordType = recordType
-        self.shortNames = []
-        self.guid = None
-        self.password = None
-        self.fullName = None
-        self.firstName = None
-        self.lastName = None
-        self.emailAddresses = set()
-        self.members = set()
-        self.groups = set()
-        self.extras = {}
-
-
-    def repeat(self, ctr):
-        &quot;&quot;&quot;
-        Create another object like this but with all text items having % substitution
-        done on them with the numeric value provided.
-        @param ctr: an integer to substitute into text.
-        &quot;&quot;&quot;
-
-        # Regular expression which matches ~ followed by a number
-        matchNumber = re.compile(r&quot;(~\d+)&quot;)
-
-        def expand(text, ctr):
-            &quot;&quot;&quot;
-            Returns a string where ~&lt;number&gt; is replaced by the first &lt;number&gt;
-            characters from the md5 hexdigest of str(ctr), e.g.::
-
-                expand(&quot;~9 foo&quot;, 1)
-
-            returns::
-
-                &quot;c4ca4238a foo&quot;
-
-            ...since &quot;c4ca4238a&quot; is the first 9 characters of::
-
-                hashlib.md5(str(1)).hexdigest()
-
-            If &lt;number&gt; is larger than 32, the hash will repeat as needed.
-            &quot;&quot;&quot;
-            if text:
-                m = matchNumber.search(text)
-                if m:
-                    length = int(m.group(0)[1:])
-                    hash = hashlib.md5(str(ctr)).hexdigest()
-                    string = (hash * ((length / 32) + 1))[:-(32 - (length % 32))]
-                    return text.replace(m.group(0), string)
-            return text
-
-        shortNames = []
-        for shortName in self.shortNames:
-            if shortName.find(&quot;%&quot;) != -1:
-                shortNames.append(shortName % ctr)
-            else:
-                shortNames.append(shortName)
-        if self.guid and self.guid.find(&quot;%&quot;) != -1:
-            guid = self.guid % ctr
-        else:
-            guid = self.guid
-        if self.password.find(&quot;%&quot;) != -1:
-            password = self.password % ctr
-        else:
-            password = self.password
-        if self.fullName.find(&quot;%&quot;) != -1:
-            fullName = self.fullName % ctr
-        else:
-            fullName = self.fullName
-        fullName = expand(fullName, ctr)
-        if self.firstName and self.firstName.find(&quot;%&quot;) != -1:
-            firstName = self.firstName % ctr
-        else:
-            firstName = self.firstName
-        firstName = expand(firstName, ctr)
-        if self.lastName and self.lastName.find(&quot;%&quot;) != -1:
-            lastName = self.lastName % ctr
-        else:
-            lastName = self.lastName
-        lastName = expand(lastName, ctr)
-        emailAddresses = set()
-        for emailAddr in self.emailAddresses:
-            emailAddr = expand(emailAddr, ctr)
-            if emailAddr.find(&quot;%&quot;) != -1:
-                emailAddresses.add(emailAddr % ctr)
-            else:
-                emailAddresses.add(emailAddr)
-
-        result = XMLAccountRecord(self.recordType)
-        result.shortNames = shortNames
-        result.guid = normalizeUUID(guid)
-        result.password = password
-        result.fullName = fullName
-        result.firstName = firstName
-        result.lastName = lastName
-        result.emailAddresses = emailAddresses
-        result.members = self.members
-        result.extras = self.extras
-        return result
-
-
-    def parseXML(self, node):
-        for child in node:
-            if child.tag == ELEMENT_SHORTNAME:
-                self.shortNames.append(child.text.encode(&quot;utf-8&quot;))
-            elif child.tag == ELEMENT_GUID:
-                self.guid = normalizeUUID(child.text.encode(&quot;utf-8&quot;))
-                if len(self.guid) &lt; 4:
-                    self.guid += &quot;?&quot; * (4 - len(self.guid))
-            elif child.tag == ELEMENT_PASSWORD:
-                self.password = child.text.encode(&quot;utf-8&quot;)
-            elif child.tag == ELEMENT_NAME:
-                self.fullName = child.text.encode(&quot;utf-8&quot;)
-            elif child.tag == ELEMENT_FIRST_NAME:
-                self.firstName = child.text.encode(&quot;utf-8&quot;)
-            elif child.tag == ELEMENT_LAST_NAME:
-                self.lastName = child.text.encode(&quot;utf-8&quot;)
-            elif child.tag == ELEMENT_EMAIL_ADDRESS:
-                self.emailAddresses.add(child.text.encode(&quot;utf-8&quot;).lower())
-            elif child.tag == ELEMENT_MEMBERS:
-                self._parseMembers(child, self.members)
-            elif child.tag == ELEMENT_EXTRAS:
-                self._parseExtras(child, self.extras)
-            else:
-                raise RuntimeError(&quot;Unknown account attribute: %s&quot; % (child.tag,))
-
-        if not self.shortNames:
-            self.shortNames.append(self.guid)
-
-
-    def _parseMembers(self, node, addto):
-        for child in node:
-            if child.tag == ELEMENT_MEMBER:
-                recordType = child.get(ATTRIBUTE_RECORDTYPE, DirectoryService.recordType_users)
-                addto.add((recordType, child.text.encode(&quot;utf-8&quot;)))
-
-
-    def _parseExtras(self, node, addto):
-        for child in node:
-            addto[child.tag] = child.text.encode(&quot;utf-8&quot;)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectoryxmlfilepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlfile.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlfile.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directory/xmlfile.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,633 +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;
-XML based user/group/resource directory service implementation.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;XMLDirectoryService&quot;,
-]
-
-from time import time
-import grp
-import os
-import pwd
-import types
-
-from twisted.cred.credentials import UsernamePassword
-from txweb2.auth.digest import DigestedCredentials
-from twext.python.filepath import CachingFilePath as FilePath
-from twistedcaldav.config import config
-from twisted.internet.defer import succeed
-
-from twistedcaldav.config import fullServerPath
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError
-from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser, XMLAccountRecord
-from twistedcaldav.directory.util import normalizeUUID
-from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
-from twistedcaldav.xmlutil import addSubElement, createElement, elementToXML
-from uuid import uuid4
-
-
-class XMLDirectoryService(DirectoryService):
-    &quot;&quot;&quot;
-    XML based implementation of L{IDirectoryService}.
-    &quot;&quot;&quot;
-    baseGUID = &quot;9CA8DEC5-5A17-43A9-84A8-BE77C1FB9172&quot;
-
-    realmName = None
-
-    INDEX_TYPE_GUID = &quot;guid&quot;
-    INDEX_TYPE_SHORTNAME = &quot;shortname&quot;
-    INDEX_TYPE_CUA = &quot;cua&quot;
-    INDEX_TYPE_AUTHID = &quot;authid&quot;
-
-
-    def __repr__(self):
-        return &quot;&lt;%s %r: %r&gt;&quot; % (self.__class__.__name__, self.realmName, self.xmlFile)
-
-
-    def __init__(self, params, alwaysStat=False):
-
-        defaults = {
-            'xmlFile' : None,
-            'directoryBackedAddressBook': None,
-            'recordTypes' : (
-                self.recordType_users,
-                self.recordType_groups,
-                self.recordType_locations,
-                self.recordType_resources,
-                self.recordType_addresses,
-            ),
-            'realmName' : '/Search',
-            'statSeconds' : 15,
-            'augmentService' : None,
-            'groupMembershipCache' : None,
-        }
-        ignored = None
-        params = self.getParams(params, defaults, ignored)
-
-        self._recordTypes = params['recordTypes']
-        self.realmName = params['realmName']
-        self.statSeconds = params['statSeconds']
-        self.augmentService = params['augmentService']
-        self.groupMembershipCache = params['groupMembershipCache']
-
-        super(XMLDirectoryService, self).__init__()
-
-        xmlFile = fullServerPath(config.DataRoot, params.get(&quot;xmlFile&quot;))
-        if type(xmlFile) is str:
-            xmlFile = FilePath(xmlFile)
-
-        if not xmlFile.exists():
-            xmlFile.setContent(&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-
-&lt;accounts realm=&quot;%s&quot;&gt;
-&lt;/accounts&gt;
-&quot;&quot;&quot; % (self.realmName,))
-
-        uid = -1
-        if config.UserName:
-            try:
-                uid = pwd.getpwnam(config.UserName).pw_uid
-            except KeyError:
-                self.log.error(&quot;User not found: %s&quot; % (config.UserName,))
-
-        gid = -1
-        if config.GroupName:
-            try:
-                gid = grp.getgrnam(config.GroupName).gr_gid
-            except KeyError:
-                self.log.error(&quot;Group not found: %s&quot; % (config.GroupName,))
-
-        if uid != -1 and gid != -1:
-            os.chown(xmlFile.path, uid, gid)
-
-        self.xmlFile = xmlFile
-        self._fileInfo = None
-        self._lastCheck = 0
-        self._alwaysStat = alwaysStat
-        self.directoryBackedAddressBook = params.get('directoryBackedAddressBook')
-        self._initIndexes()
-        self._accounts()
-
-
-    def _initIndexes(self):
-        &quot;&quot;&quot;
-        Create empty indexes
-        &quot;&quot;&quot;
-        self.records = {}
-        self.recordIndexes = {}
-
-        for recordType in self.recordTypes():
-            self.records[recordType] = set()
-            self.recordIndexes[recordType] = {
-                self.INDEX_TYPE_GUID     : {},
-                self.INDEX_TYPE_SHORTNAME: {},
-                self.INDEX_TYPE_CUA      : {},
-                self.INDEX_TYPE_AUTHID   : {},
-            }
-
-
-    def _accounts(self):
-        &quot;&quot;&quot;
-        Parses XML file, creates XMLDirectoryRecords and indexes them, and
-        because some other code in this module still works directly with
-        XMLAccountRecords as returned by XMLAccountsParser, returns a list
-        of XMLAccountRecords.
-
-        The XML file is only stat'ed at most every self.statSeconds, and is
-        only reparsed if it's been modified.
-
-        FIXME: don't return XMLAccountRecords, and have any code in this module
-        which currently does work with XMLAccountRecords, modify such code to
-        use XMLDirectoryRecords instead.
-        &quot;&quot;&quot;
-        currentTime = time()
-        if self._alwaysStat or currentTime - self._lastCheck &gt; self.statSeconds:
-            self.xmlFile.restat()
-            self._lastCheck = currentTime
-            fileInfo = (self.xmlFile.getmtime(), self.xmlFile.getsize())
-            if fileInfo != self._fileInfo:
-                self._initIndexes()
-                parser = XMLAccountsParser(self.xmlFile)
-                self._parsedAccounts = parser.items
-                self.realmName = parser.realm
-                self._fileInfo = fileInfo
-
-                for accountDict in self._parsedAccounts.itervalues():
-                    for xmlAccountRecord in accountDict.itervalues():
-                        if xmlAccountRecord.recordType not in self.recordTypes():
-                            continue
-                        record = XMLDirectoryRecord(
-                            service=self,
-                            recordType=xmlAccountRecord.recordType,
-                            shortNames=tuple(xmlAccountRecord.shortNames),
-                            xmlPrincipal=xmlAccountRecord,
-                        )
-                        if self.augmentService is not None:
-                            d = self.augmentService.getAugmentRecord(record.guid,
-                                record.recordType)
-                            d.addCallback(lambda x: record.addAugmentInformation(x))
-
-                        self._addToIndex(record)
-
-        return self._parsedAccounts
-
-
-    def _addToIndex(self, record):
-        &quot;&quot;&quot;
-        Index the record by GUID, shortName(s), authID(s) and CUA(s)
-        &quot;&quot;&quot;
-
-        self.recordIndexes[record.recordType][self.INDEX_TYPE_GUID][record.guid] = record
-        for shortName in record.shortNames:
-            self.recordIndexes[record.recordType][self.INDEX_TYPE_SHORTNAME][shortName] = record
-        for authID in record.authIDs:
-            self.recordIndexes[record.recordType][self.INDEX_TYPE_AUTHID][authID] = record
-        for cua in record.calendarUserAddresses:
-            cua = normalizeCUAddr(cua)
-            self.recordIndexes[record.recordType][self.INDEX_TYPE_CUA][cua] = record
-        self.records[record.recordType].add(record)
-
-
-    def _removeFromIndex(self, record):
-        &quot;&quot;&quot;
-        Removes a record from all indexes.  Note this is only used for unit
-        testing, to simulate a user being removed from the directory.
-        &quot;&quot;&quot;
-        del self.recordIndexes[record.recordType][self.INDEX_TYPE_GUID][record.guid]
-        for shortName in record.shortNames:
-            del self.recordIndexes[record.recordType][self.INDEX_TYPE_SHORTNAME][shortName]
-        for authID in record.authIDs:
-            del self.recordIndexes[record.recordType][self.INDEX_TYPE_AUTHID][authID]
-        for cua in record.calendarUserAddresses:
-            cua = normalizeCUAddr(cua)
-            del self.recordIndexes[record.recordType][self.INDEX_TYPE_CUA][cua]
-        if record in self.records[record.recordType]:
-            self.records[record.recordType].remove(record)
-
-
-    def _lookupInIndex(self, recordType, indexType, key):
-        &quot;&quot;&quot;
-        Look for an existing record of the given recordType with the key for
-        the given index type.  Returns None if no match.
-        &quot;&quot;&quot;
-        self._accounts()
-        return self.recordIndexes.get(recordType, {}).get(indexType, {}).get(key, None)
-
-
-    def _initCaches(self):
-        &quot;&quot;&quot;
-        Invalidates the indexes
-        &quot;&quot;&quot;
-        self._lastCheck = 0
-        self._initIndexes()
-
-
-    def _forceReload(self):
-        &quot;&quot;&quot;
-        Invalidates the indexes, re-reads the XML file and re-indexes
-        &quot;&quot;&quot;
-        self._initCaches()
-        self._fileInfo = None
-        return self._accounts()
-
-
-    def recordWithShortName(self, recordType, shortName):
-        return self._lookupInIndex(recordType, self.INDEX_TYPE_SHORTNAME, shortName)
-
-
-    def recordWithAuthID(self, authID):
-        for recordType in self.recordTypes():
-            record = self._lookupInIndex(recordType, self.INDEX_TYPE_AUTHID, authID)
-            if record is not None:
-                return record
-        return None
-
-
-    def recordWithGUID(self, guid):
-        guid = normalizeUUID(guid)
-        for recordType in self.recordTypes():
-            record = self._lookupInIndex(recordType, self.INDEX_TYPE_GUID, guid)
-            if record is not None:
-                return record
-        return None
-
-    recordWithUID = recordWithGUID
-
-    def createCache(self):
-        &quot;&quot;&quot;
-        No-op to pacify addressbook backing.
-        &quot;&quot;&quot;
-
-    def recordTypes(self):
-        return self._recordTypes
-
-
-    def listRecords(self, recordType):
-        self._accounts()
-        return self.records[recordType]
-
-
-    def recordsMatchingFields(self, fields, operand=&quot;or&quot;, recordType=None):
-        # Default, brute force method search of underlying XML data
-
-        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 xmlPrincipalMatches(xmlPrincipal):
-            if operand == &quot;and&quot;:
-                for fieldName, value, caseless, matchType in fields:
-                    try:
-                        fieldValue = getattr(xmlPrincipal, 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(xmlPrincipal, fieldName)
-                        if fieldMatches(fieldValue, value, caseless, matchType):
-                            return True
-                    except AttributeError:
-                        # No value
-                        pass
-                # we didn't hit any
-                return False
-
-        if recordType is None:
-            recordTypes = list(self.recordTypes())
-        else:
-            recordTypes = (recordType,)
-
-        records = []
-        for recordType in recordTypes:
-            for xmlPrincipal in self._accounts()[recordType].itervalues():
-                if xmlPrincipalMatches(xmlPrincipal):
-
-                    # Load/cache record from its GUID
-                    record = self.recordWithGUID(xmlPrincipal.guid)
-                    if record:
-                        records.append(record)
-        return succeed(records)
-
-
-    def _addElement(self, parent, principal):
-        &quot;&quot;&quot;
-        Create an XML element from principal and add it as a child of parent
-        &quot;&quot;&quot;
-
-        # TODO: derive this from xmlaccountsparser.py
-        xmlTypes = {
-            'users'     : 'user',
-            'groups'    : 'group',
-            'locations' : 'location',
-            'resources' : 'resource',
-            'addresses' : 'address',
-        }
-        xmlType = xmlTypes[principal.recordType]
-
-        element = addSubElement(parent, xmlType)
-        for value in principal.shortNames:
-            addSubElement(element, &quot;uid&quot;, text=value.decode(&quot;utf-8&quot;))
-        addSubElement(element, &quot;guid&quot;, text=principal.guid)
-        if principal.fullName is not None:
-            addSubElement(element, &quot;name&quot;, text=principal.fullName.decode(&quot;utf-8&quot;))
-        if principal.firstName is not None:
-            addSubElement(element, &quot;first-name&quot;, text=principal.firstName.decode(&quot;utf-8&quot;))
-        if principal.lastName is not None:
-            addSubElement(element, &quot;last-name&quot;, text=principal.lastName.decode(&quot;utf-8&quot;))
-        for value in principal.emailAddresses:
-            addSubElement(element, &quot;email-address&quot;, text=value.decode(&quot;utf-8&quot;))
-        if principal.extras:
-            extrasElement = addSubElement(element, &quot;extras&quot;)
-            for key, value in principal.extras.iteritems():
-                addSubElement(extrasElement, key, text=value.decode(&quot;utf-8&quot;))
-
-        return element
-
-
-    def _persistRecords(self, element):
-
-        def indent(elem, level=0):
-            i = &quot;\n&quot; + level * &quot;  &quot;
-            if len(elem):
-                if not elem.text or not elem.text.strip():
-                    elem.text = i + &quot;  &quot;
-                if not elem.tail or not elem.tail.strip():
-                    elem.tail = i
-                for elem in elem:
-                    indent(elem, level + 1)
-                if not elem.tail or not elem.tail.strip():
-                    elem.tail = i
-            else:
-                if level and (not elem.tail or not elem.tail.strip()):
-                    elem.tail = i
-
-        indent(element)
-
-        self.xmlFile.setContent(elementToXML(element))
-
-        # Fix up the file ownership because setContent doesn't maintain it
-        uid = -1
-        if config.UserName:
-            try:
-                uid = pwd.getpwnam(config.UserName).pw_uid
-            except KeyError:
-                self.log.error(&quot;User not found: %s&quot; % (config.UserName,))
-
-        gid = -1
-        if config.GroupName:
-            try:
-                gid = grp.getgrnam(config.GroupName).gr_gid
-            except KeyError:
-                self.log.error(&quot;Group not found: %s&quot; % (config.GroupName,))
-
-        if uid != -1 and gid != -1:
-            os.chown(self.xmlFile.path, uid, gid)
-
-
-    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 and persist a record using the provided information.  In this
-        XML-based implementation, the xml accounts are read in and converted
-        to elementtree elements, a new element is added for the new record,
-        and the document is serialized to disk.
-        &quot;&quot;&quot;
-        if guid is None:
-            guid = str(uuid4())
-        guid = normalizeUUID(guid)
-
-        if not shortNames:
-            shortNames = (guid,)
-
-        # Make sure latest XML records are read in
-        accounts = self._forceReload()
-
-        accountsElement = createElement(&quot;accounts&quot;, realm=self.realmName)
-        for recType in self.recordTypes():
-            for xmlPrincipal in accounts[recType].itervalues():
-                if xmlPrincipal.guid == guid:
-                    raise DirectoryError(&quot;Duplicate guid: %s&quot; % (guid,))
-                for shortName in shortNames:
-                    if shortName in xmlPrincipal.shortNames:
-                        raise DirectoryError(&quot;Duplicate shortName: %s&quot; %
-                            (shortName,))
-                self._addElement(accountsElement, xmlPrincipal)
-
-        xmlPrincipal = XMLAccountRecord(recordType)
-        xmlPrincipal.shortNames = shortNames
-        xmlPrincipal.guid = guid
-        xmlPrincipal.password = password
-        xmlPrincipal.fullName = fullName
-        xmlPrincipal.firstName = firstName
-        xmlPrincipal.lastName = lastName
-        xmlPrincipal.emailAddresses = emailAddresses
-        xmlPrincipal.extras = kwargs
-        self._addElement(accountsElement, xmlPrincipal)
-
-        self._persistRecords(accountsElement)
-        self._forceReload()
-        return self.recordWithGUID(guid)
-
-
-    def destroyRecord(self, recordType, guid=None):
-        &quot;&quot;&quot;
-        Remove the record matching guid.  In this XML-based implementation,
-        the xml accounts are read in and those not matching the given guid are
-        converted to elementtree elements, then the document is serialized to
-        disk.
-        &quot;&quot;&quot;
-
-        guid = normalizeUUID(guid)
-
-        # Make sure latest XML records are read in
-        accounts = self._forceReload()
-
-        accountsElement = createElement(&quot;accounts&quot;, realm=self.realmName)
-        for recType in self.recordTypes():
-
-            for xmlPrincipal in accounts[recType].itervalues():
-                if xmlPrincipal.guid != guid:
-                    self._addElement(accountsElement, xmlPrincipal)
-
-        self._persistRecords(accountsElement)
-        self._forceReload()
-
-
-    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 the record matching guid.  In this XML-based implementation,
-        the xml accounts are read in and converted to elementtree elements.
-        The account matching the given guid is replaced, then the document
-        is serialized to disk.
-        &quot;&quot;&quot;
-
-        guid = normalizeUUID(guid)
-
-        # Make sure latest XML records are read in
-        accounts = self._forceReload()
-
-        accountsElement = createElement(&quot;accounts&quot;, realm=self.realmName)
-        for recType in self.recordTypes():
-
-            for xmlPrincipal in accounts[recType].itervalues():
-                if xmlPrincipal.guid == guid:
-                    # Replace this record
-                    xmlPrincipal.shortNames = shortNames
-                    xmlPrincipal.password = password
-                    xmlPrincipal.fullName = fullName
-                    xmlPrincipal.firstName = firstName
-                    xmlPrincipal.lastName = lastName
-                    xmlPrincipal.emailAddresses = emailAddresses
-                    xmlPrincipal.extras = kwargs
-                    self._addElement(accountsElement, xmlPrincipal)
-                else:
-                    self._addElement(accountsElement, xmlPrincipal)
-
-        self._persistRecords(accountsElement)
-        self._forceReload()
-        return self.recordWithGUID(guid)
-
-
-    def createRecords(self, data):
-        &quot;&quot;&quot;
-        Create records in bulk
-        &quot;&quot;&quot;
-
-        # Make sure latest XML records are read in
-        accounts = self._forceReload()
-
-        knownGUIDs = {}
-        knownShortNames = {}
-
-        accountsElement = createElement(&quot;accounts&quot;, realm=self.realmName)
-        for recType in self.recordTypes():
-            for xmlPrincipal in accounts[recType].itervalues():
-                self._addElement(accountsElement, xmlPrincipal)
-                knownGUIDs[xmlPrincipal.guid] = 1
-                for shortName in xmlPrincipal.shortNames:
-                    knownShortNames[shortName] = 1
-
-        for recordType, recordData in data:
-            guid = recordData[&quot;guid&quot;]
-            if guid is None:
-                guid = str(uuid4())
-
-            shortNames = recordData[&quot;shortNames&quot;]
-            if not shortNames:
-                shortNames = (guid,)
-
-            if guid in knownGUIDs:
-                raise DirectoryError(&quot;Duplicate guid: %s&quot; % (guid,))
-
-            for shortName in shortNames:
-                if shortName in knownShortNames:
-                    raise DirectoryError(&quot;Duplicate shortName: %s&quot; %
-                        (shortName,))
-
-            xmlPrincipal = XMLAccountRecord(recordType)
-            xmlPrincipal.shortNames = shortNames
-            xmlPrincipal.guid = guid
-            xmlPrincipal.fullName = recordData[&quot;fullName&quot;]
-            self._addElement(accountsElement, xmlPrincipal)
-
-        self._persistRecords(accountsElement)
-        self._forceReload()
-
-
-
-class XMLDirectoryRecord(DirectoryRecord):
-    &quot;&quot;&quot;
-    XML based implementation implementation of L{IDirectoryRecord}.
-    &quot;&quot;&quot;
-    def __init__(self, service, recordType, shortNames, xmlPrincipal):
-        super(XMLDirectoryRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            guid=xmlPrincipal.guid,
-            shortNames=shortNames,
-            fullName=xmlPrincipal.fullName,
-            firstName=xmlPrincipal.firstName,
-            lastName=xmlPrincipal.lastName,
-            emailAddresses=xmlPrincipal.emailAddresses,
-            **xmlPrincipal.extras
-        )
-
-        self.password = xmlPrincipal.password
-        self._members = xmlPrincipal.members
-        self._groups = xmlPrincipal.groups
-
-
-    def members(self):
-        for recordType, shortName in self._members:
-            yield self.service.recordWithShortName(recordType, shortName)
-
-
-    def groups(self):
-        for shortName in self._groups:
-            yield self.service.recordWithShortName(DirectoryService.recordType_groups, shortName)
-
-
-    def memberGUIDs(self):
-        results = set()
-        for recordType, shortName in self._members:
-            record = self.service.recordWithShortName(recordType, shortName)
-            results.add(record.guid)
-        return results
-
-
-    def verifyCredentials(self, credentials):
-        if self.enabled:
-            if isinstance(credentials, UsernamePassword):
-                return credentials.password == self.password
-            if isinstance(credentials, DigestedCredentials):
-                return credentials.checkPassword(self.password)
-
-        return super(XMLDirectoryRecord, self).verifyCredentials(credentials)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavdirectorybackedaddressbookpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directorybackedaddressbook.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directorybackedaddressbook.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/directorybackedaddressbook.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -22,89 +22,72 @@
</span><span class="cx">     &quot;DirectoryBackedAddressBookResource&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> from twext.python.log import Logger
</span><ins>+from twext.who.expression import Operand, MatchType, MatchFlags, \
+    MatchExpression, CompoundExpression
+from twext.who.idirectory import FieldName
+from twisted.internet.defer import deferredGenerator
+from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, \
+    returnValue
+from twisted.python.constants import NamedConstant
+from twistedcaldav import carddavxml
+from twistedcaldav.config import config
+from twistedcaldav.resource import CalDAVResource
+from txdav.carddav.datastore.query.filter import IsNotDefined, TextMatch, \
+    ParameterFilter
+from txdav.who.idirectory import FieldName as CalFieldName
+from txdav.who.vcard import vCardKindToRecordTypeMap, recordTypeToVCardKindMap, \
+    vCardPropToParamMap, vCardConstantProperties, vCardFromRecord
+from txdav.xml import element as davxml
+from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, \
+    twisted_private_namespace
</ins><span class="cx"> from txweb2 import responsecode
</span><del>-from txdav.xml import element as davxml
</del><ins>+from txweb2.dav.resource import DAVPropertyMixIn
</ins><span class="cx"> from txweb2.dav.resource import TwistedACLInheritable
</span><ins>+from txweb2.dav.util import joinURL
</ins><span class="cx"> from txweb2.http import HTTPError, StatusResponse
</span><del>-
-from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, returnValue
-from twisted.python.reflect import namedClass
-
-from twistedcaldav.config import config
-from twistedcaldav.resource import CalDAVResource
-
</del><ins>+from txweb2.http_headers import MimeType, generateContentType, ETag
+from xmlrpclib import datetime
+import hashlib
</ins><span class="cx"> import uuid
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+MatchFlags_none = MatchFlags.NOT &amp; ~MatchFlags.NOT  # can't import MatchFlags_none
</ins><span class="cx"> 
</span><del>-
</del><span class="cx"> class DirectoryBackedAddressBookResource (CalDAVResource):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory-backed address book
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def __init__(self, principalCollections):
</del><ins>+    def __init__(self, principalCollections, principalDirectory, uri):
</ins><span class="cx"> 
</span><span class="cx">         CalDAVResource.__init__(self, principalCollections=principalCollections)
</span><span class="cx"> 
</span><del>-        self.directory = None       # creates directory attribute
</del><ins>+        self.principalDirectory = principalDirectory
+        self.uri = uri
+        self.directory = None
</ins><span class="cx"> 
</span><del>-        # create with permissions, similar to CardDAVOptions in tap.py
-        # FIXME:  /Directory does not need to be in file system unless debug-only caching options are used
-#        try:
-#            os.mkdir(path)
-#            os.chmod(path, 0750)
-#            if config.UserName and config.GroupName:
-#                import pwd
-#                import grp
-#                uid = pwd.getpwnam(config.UserName)[2]
-#                gid = grp.getgrnam(config.GroupName)[2]
-#                os.chown(path, uid, gid)
-#
-#            log.info(&quot;Created %s&quot; % (path,))
-#
-#        except (OSError,), e:
-#            # this is caused by multiprocessor race and is harmless
-#            if e.errno != errno.EEXIST:
-#                raise
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def makeChild(self, name):
</span><span class="cx">         from twistedcaldav.simpleresource import SimpleCalDAVResource
</span><span class="cx">         return SimpleCalDAVResource(principalCollections=self.principalCollections())
</span><ins>+        return self.directory
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def provisionDirectory(self):
</span><span class="cx">         if self.directory is None:
</span><del>-            directoryClass = namedClass(config.DirectoryAddressBook.type)
-
</del><span class="cx">             log.info(
</span><del>-                &quot;Configuring: {t}:{p}&quot;,
-                t=config.DirectoryAddressBook.type,
-                p=config.DirectoryAddressBook.params,
-            )
</del><ins>+                &quot;Setting search directory to {principalDirectory}&quot;,
+                principalDirectory=self.principalDirectory)
+            self.directory = self.principalDirectory
+            # future: instantiate another directory based on /Search/Contacts (?)
</ins><span class="cx"> 
</span><del>-            #add self as &quot;directoryBackedAddressBook&quot; parameter
-            params = config.DirectoryAddressBook.params.copy()
-            params[&quot;directoryBackedAddressBook&quot;] = self
-
-            try:
-                self.directory = directoryClass(params)
-            except ImportError, e:
-                log.error(&quot;Unable to set up directory address book: %s&quot; % (e,))
-                return succeed(None)
-
-            return self.directory.createCache()
-
-            #print (&quot;DirectoryBackedAddressBookResource.provisionDirectory: provisioned&quot;)
-
</del><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        #print( &quot;DirectoryBackedAddressBookResource.defaultAccessControlList&quot; )
</del><span class="cx">         if config.AnonymousDirectoryAddressBookAccess:
</span><span class="cx">             # DAV:Read for all principals (includes anonymous)
</span><span class="cx">             accessPrincipal = davxml.All()
</span><span class="lines">@@ -112,16 +95,18 @@
</span><span class="cx">             # DAV:Read for all authenticated principals (does not include anonymous)
</span><span class="cx">             accessPrincipal = davxml.Authenticated()
</span><span class="cx"> 
</span><del>-        return davxml.ACL(
-            davxml.ACE(
-                davxml.Principal(accessPrincipal),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
-                                ),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-           ),
</del><ins>+        return succeed(
+            davxml.ACL(
+                davxml.ACE(
+                    davxml.Principal(accessPrincipal),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
+                                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+               ),
+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -139,7 +124,7 @@
</span><span class="cx"> 
</span><span class="cx">     def resourceID(self):
</span><span class="cx">         if self.directory:
</span><del>-            resource_id = uuid.uuid5(uuid.UUID(&quot;5AAD67BF-86DD-42D7-9161-6AF977E4DAA3&quot;), self.directory.baseGUID).urn
</del><ins>+            resource_id = uuid.uuid5(uuid.UUID(&quot;5AAD67BF-86DD-42D7-9161-6AF977E4DAA3&quot;), self.directory.guid).urn
</ins><span class="cx">         else:
</span><span class="cx">             resource_id = &quot;tag:unknown&quot;
</span><span class="cx">         return resource_id
</span><span class="lines">@@ -150,7 +135,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def isAddressBookCollection(self):
</span><del>-        #print( &quot;DirectoryBackedAddressBookResource.isAddressBookCollection: return True&quot; )
</del><span class="cx">         return True
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -160,28 +144,546 @@
</span><span class="cx"> 
</span><span class="cx">     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
</span><span class="cx">         # Permissions here are fixed, and are not subject to inheritance rules, etc.
</span><del>-        return succeed(self.defaultAccessControlList())
</del><ins>+        return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def renderHTTP(self, request):
</span><span class="cx">         if not self.directory:
</span><span class="cx">             raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Service is starting up&quot;))
</span><del>-        elif self.directory.liveQuery:
-            response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
-            returnValue(response)
-        else:
-            available = (yield maybeDeferred(self.directory.available,))
</del><span class="cx"> 
</span><del>-            if not available:
-                raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Service is starting up&quot;))
</del><ins>+        response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def doAddressBookDirectoryQuery(self, addressBookFilter, addressBookQuery, maxResults, defaultKind=None):
+        &quot;&quot;&quot;
+        Get vCards for a given addressBookFilter and addressBookQuery
+        &quot;&quot;&quot;
+
+        log.debug(&quot;doAddressBookDirectoryQuery: directory={directory} addressBookFilter={addressBookFilter}, addressBookQuery={addressBookQuery}, maxResults={maxResults}&quot;,
+                  directory=self.directory, addressBookFilter=addressBookFilter, addressBookQuery=addressBookQuery, maxResults=maxResults)
+        results = []
+        limited = False
+        maxQueryRecords = 0
+
+        vcardPropToRecordFieldMap = {
+            &quot;FN&quot;: FieldName.fullNames,
+            &quot;N&quot;: FieldName.fullNames,
+            &quot;EMAIL&quot;: FieldName.emailAddresses,
+            &quot;UID&quot;: FieldName.uid,
+            &quot;ADR&quot;: (
+                    CalFieldName.streetAddress,
+                    CalFieldName.floor,
+                    ),
+            &quot;KIND&quot;: FieldName.recordType,
+            # LATER &quot;X-ADDRESSBOOKSERVER-MEMBER&quot;: FieldName.membersUIDs,
+        }
+
+        propNames, expression = expressionFromABFilter(
+            addressBookFilter, vcardPropToRecordFieldMap, vCardConstantProperties
+        )
+
+        if expression:
+            if defaultKind and &quot;KIND&quot; not in propNames:
+                defaultRecordExpression = MatchExpression(
+                    FieldName.recordType,
+                    vCardKindToRecordTypeMap[defaultKind],
+                    MatchType.equals
+                )
+                if expression is True:
+                    expression = defaultRecordExpression
+                else:
+                    expression = CompoundExpression(
+                        (expression, defaultRecordExpression,),
+                        Operand.AND
+                    )
+            elif expression is True: # True means all records
+                allowedRecordTypes = set(self.directory.recordTypes()) &amp; set(recordTypeToVCardKindMap.keys())
+                expression = CompoundExpression(
+                    [
+                        MatchExpression(FieldName.recordType, recordType, MatchType.equals)
+                            for recordType in allowedRecordTypes
+                    ], Operand.OR
+                )
+
+            maxRecords = int(maxResults * 1.2)
+
+            # keep trying query till we get results based on filter.  Especially when doing &quot;all results&quot; query
+            while True:
+
+                log.debug(&quot;doAddressBookDirectoryQuery: expression={expression!r}, propNames={propNames}&quot;, expression=expression, propNames=propNames)
+
+                records = yield self.directory.recordsFromExpression(expression)
+                log.debug(&quot;doAddressBookDirectoryQuery: #records={n}, records={records!r}&quot;, n=len(records), records=records)
+                queryLimited = False
+
+                vCardsResults = [(yield ABDirectoryQueryResult(self).generate(record)) for record in records]
+
+                filteredResults = []
+                for vCardResult in vCardsResults:
+                    if addressBookFilter.match(vCardResult.vCard()):
+                        filteredResults.append(vCardResult)
+                    else:
+                        log.debug(&quot;doAddressBookDirectoryQuery: vCard did not match filter:\n{vcard}&quot;, vcard=vCardResult.vCard())
+
+                #no more results
+                if not queryLimited:
+                    break
+
+                # more than requested results
+                if maxResults and len(filteredResults) &gt;= maxResults:
+                    break
+
+                # more than max report results
+                if len(filteredResults) &gt;= config.MaxQueryWithDataResults:
+                    break
+
+                # more than self limit
+                if maxQueryRecords and maxRecords &gt;= maxQueryRecords:
+                    break
+
+                # try again with 2x
+                maxRecords *= 2
+                if maxQueryRecords and maxRecords &gt; maxQueryRecords:
+                    maxRecords = maxQueryRecords
+
+            results = sorted(list(filteredResults), key=lambda result: result.vCard().propertyValue(&quot;UID&quot;))
+            limited = maxResults and len(results) &gt;= maxResults
+
+        log.info(&quot;limited={l} #results={n}&quot;, l=limited, n=len(results))
+        returnValue((results, limited,))
+
+
+
+def propertiesInAddressBookQuery(addressBookQuery):
+    &quot;&quot;&quot;
+    Get the vCard properties requested by a given query
+    &quot;&quot;&quot;
+
+    etagRequested = False
+    propertyNames = []
+    if addressBookQuery.qname() == (&quot;DAV:&quot;, &quot;prop&quot;):
+
+        for property in addressBookQuery.children:
+            if isinstance(property, carddavxml.AddressData):
+                for addressProperty in property.children:
+                    if isinstance(addressProperty, carddavxml.Property):
+                        propertyNames.append(addressProperty.attributes[&quot;name&quot;])
+
+            elif property.qname() == (&quot;DAV:&quot;, &quot;getetag&quot;):
+                # for a real etag == md5(vCard), we need all properties
+                etagRequested = True
+
+    return (etagRequested, propertyNames if len(propertyNames) else None)
+
+
+
+def expressionFromABFilter(addressBookFilter, vcardPropToSearchableFieldMap, constantProperties={}):
+    &quot;&quot;&quot;
+    Convert the supplied addressbook-query into a ds expression tree.
+
+    @param addressBookFilter: the L{Filter} for the addressbook-query to convert.
+    @param vcardPropToSearchableFieldMap: a mapping from vcard properties to searchable query attributes.
+    @param constantProperties: a mapping of constant properties.  A query on a constant property will return all or None
+    @return: (filterProperyNames, expressions) tuple.  expression==True means list all results, expression==False means no results
+    &quot;&quot;&quot;
+
+    def propFilterListQuery(filterAllOf, propFilters):
+
+        &quot;&quot;&quot;
+        Create an expression for a list of prop-filter elements.
+
+        @param filterAllOf: the C{True} if parent filter test is &quot;allof&quot;
+        @param propFilters: the C{list} of L{ComponentFilter} elements.
+        @return: (filterProperyNames, expressions) tuple.  expression==True means list all results, expression==False means no results
+        &quot;&quot;&quot;
+
+        def combineExpressionLists(expressionList, allOf, addedExpressions):
+            &quot;&quot;&quot;
+            deal with the 4-state logic
+                addedExpressions=None means ignore
+                addedExpressions=True means all records
+                addedExpressions=False means no records
+                addedExpressions=[expressionlist] add to expression list
+            &quot;&quot;&quot;
+            if expressionList is None:
+                expressionList = addedExpressions
+            elif addedExpressions is not None:
+                if addedExpressions is True:
+                    if not allOf:
+                        expressionList = True  # expressionList or True is True
+                    #else  expressionList and True is expressionList
+                elif addedExpressions is False:
+                    if allOf:
+                        expressionList = False  # expressionList and False is False
+                    #else expressionList or False is expressionList
+                else:
+                    if expressionList is False:
+                        if not allOf:
+                            expressionList = addedExpressions  # False or addedExpressions is addedExpressions
+                        #else False and addedExpressions is False
+                    elif expressionList is True:
+                        if allOf:
+                            expressionList = addedExpressions  # False or addedExpressions is addedExpressions
+                        #else False and addedExpressions is False
+                    else:
+                        expressionList.extend(addedExpressions)
+            return expressionList
+
+
+        def propFilterExpression(filterAllOf, propFilter):
+            &quot;&quot;&quot;
+            Create an expression for a single prop-filter element.
+
+            @param propFilter: the L{PropertyFilter} element.
+            @return: (filterProperyNames, expressions) tuple.  expression==True means list all results, expression==False means no results
+            &quot;&quot;&quot;
+
+            def matchExpression(fieldName, matchString, matchType, matchFlags):
+                # special case recordType field
+                if fieldName == FieldName.recordType:
+                    # change kind to record type
+                    matchValue = vCardKindToRecordTypeMap.get(matchString.lower())
+                    if matchValue is None:
+                        matchValue = NamedConstant()
+                        matchValue.description = u&quot;&quot;
+
+                    # change types and flags
+                    matchFlags &amp;= ~MatchFlags.caseInsensitive
+                    matchType = MatchType.equals
+                else:
+                    matchValue = matchString.decode(&quot;utf-8&quot;)
+
+                return MatchExpression(fieldName, matchValue, matchType, matchFlags)
+
+
+            def definedExpression(defined, allOf):
+                if constant or propFilter.filter_name in (&quot;N&quot; , &quot;FN&quot;, &quot;UID&quot;, &quot;SOURCE&quot;, &quot;KIND&quot;,):
+                    return defined  # all records have this property so no records do not have it
+                else:
+                    # FIXME: The startsWith expression below, which works with LDAP and OD. is not currently supported
+                    return True
+                    '''
+                    # this may generate inefficient LDAP query string
+                    matchFlags = MatchFlags_none if defined else MatchFlags.NOT
+                    matchList = [matchExpression(fieldName, &quot;&quot;, MatchType.startsWith, matchFlags) for fieldName in searchableFields]
+                    return andOrExpression(allOf, matchList)
+                    '''
+
+
+            def andOrExpression(propFilterAllOf, matchList):
+                matchList = list(set(matchList))
+                if propFilterAllOf and len(matchList) &gt; 1:
+                    # add OR expression because parent will AND
+                    return [CompoundExpression(matchList, Operand.OR), ]
+                else:
+                    return matchList
+
+
+            def paramFilterElementExpression(propFilterAllOf, paramFilterElement): #@UnusedVariable
+
+                params = vCardPropToParamMap.get(propFilter.filter_name.upper())
+                defined = params and paramFilterElement.filter_name.upper() in params
+
+                #defined test
+                if defined != paramFilterElement.defined:
+                    return False
+
+                #parameter value text match
+                if defined and paramFilterElement.filters:
+                    paramValues = params[paramFilterElement.filter_name.upper()]
+                    if paramValues and paramFilterElement.filters[0].text.upper() not in paramValues:
+                        return False
+
+                return True
+
+
+            def textMatchElementExpression(propFilterAllOf, textMatchElement):
+
+                # pre process text match strings for ds query
+                def getMatchStrings(propFilter, matchString):
+
+                    if propFilter.filter_name in (&quot;REV&quot; , &quot;BDAY&quot;,):
+                        rawString = matchString
+                        matchString = &quot;&quot;
+                        for c in rawString:
+                            if not c in &quot;TZ-:&quot;:
+                                matchString += c
+                    elif propFilter.filter_name == &quot;GEO&quot;:
+                        matchString = &quot;,&quot;.join(matchString.split(&quot;;&quot;))
+
+                    if propFilter.filter_name in (&quot;N&quot; , &quot;ADR&quot;, &quot;ORG&quot;,):
+                        # for structured properties, change into multiple strings for ds query
+                        if propFilter.filter_name == &quot;ADR&quot;:
+                            #split by newline and comma
+                            rawStrings = &quot;,&quot;.join(matchString.split(&quot;\n&quot;)).split(&quot;,&quot;)
+                        else:
+                            #split by space
+                            rawStrings = matchString.split(&quot; &quot;)
+
+                        # remove empty strings
+                        matchStrings = []
+                        for oneString in rawStrings:
+                            if len(oneString):
+                                matchStrings += [oneString, ]
+                        return matchStrings
+
+                    elif len(matchString):
+                        return [matchString, ]
+                    else:
+                        return []
+                    # end getMatchStrings
+
+                if constant:
+                    #FIXME: match is not implemented in twisteddaldav.query.Filter.TextMatch so use _match for now
+                    return textMatchElement._match([constant, ])
+                else:
+
+                    matchStrings = getMatchStrings(propFilter, textMatchElement.text)
+
+                    if not len(matchStrings):
+                        # no searching text in binary ds attributes, so change to defined/not defined case
+                        if textMatchElement.negate:
+                            return definedExpression(False, propFilterAllOf)
+                        # else fall through to attribute exists case below
+                    else:
+
+                        # use match_type where possible depending on property/attribute mapping
+                        # FIXME: case-sensitive negate will not work.  This should return all all records in that case
+                        matchType = MatchType.contains
+                        if propFilter.filter_name in (&quot;NICKNAME&quot; , &quot;TITLE&quot; , &quot;NOTE&quot; , &quot;UID&quot;, &quot;URL&quot;, &quot;N&quot;, &quot;ADR&quot;, &quot;ORG&quot;, &quot;REV&quot;, &quot;LABEL&quot;,):
+                            if textMatchElement.match_type == &quot;equals&quot;:
+                                matchType = MatchType.equals
+                            elif textMatchElement.match_type == &quot;starts-with&quot;:
+                                matchType = MatchType.startsWith
+                            elif textMatchElement.match_type == &quot;ends-with&quot;:
+                                matchType = MatchType.endsWith
+
+                        matchList = []
+                        for matchString in matchStrings:
+                            matchFlags = None
+                            if textMatchElement.collation == &quot;i;unicode-casemap&quot; and textMatchElement.negate:
+                                matchFlags = MatchFlags.caseInsensitive | MatchFlags.NOT
+                            elif textMatchElement.collation == &quot;i;unicode-casemap&quot;:
+                                matchFlags = MatchFlags.caseInsensitive
+                            elif textMatchElement.negate:
+                                matchFlags = MatchFlags.NOT
+                            else:
+                                matchFlags = MatchFlags_none
+
+                            matchList = [matchExpression(fieldName, matchString, matchType, matchFlags) for fieldName in searchableFields]
+                            matchList.extend(matchList)
+                        return andOrExpression(propFilterAllOf, matchList)
+
+                # attribute exists search
+                return definedExpression(True, propFilterAllOf)
+                #end textMatchElementExpression()
+
+            # searchablePropFilterAttrNames are attributes to be used by this propfilter's expression
+            searchableFields = vcardPropToSearchableFieldMap.get(propFilter.filter_name, [])
+            if isinstance(searchableFields, NamedConstant):
+                searchableFields = (searchableFields,)
+
+            constant = constantProperties.get(propFilter.filter_name)
+            if not searchableFields and not constant:
+                # not allAttrNames means propFilter.filter_name is not mapped
+                # return None to try to match all items if this is the only property filter
+                return None
+
+            #create a textMatchElement for the IsNotDefined qualifier
+            if isinstance(propFilter.qualifier, IsNotDefined):
+                textMatchElement = TextMatch(carddavxml.TextMatch.fromString(&quot;&quot;))
+                textMatchElement.negate = True
+                propFilter.filters.append(textMatchElement)
+
+            # if only one propFilter, then use filterAllOf as propFilterAllOf to reduce subexpressions and simplify generated query string
+            if len(propFilter.filters) == 1:
+                propFilterAllOf = filterAllOf
</ins><span class="cx">             else:
</span><del>-                updateLock = self.directory.updateLock()
-                yield updateLock.acquire()
-                try:
-                    response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
</del><ins>+                propFilterAllOf = propFilter.propfilter_test == &quot;allof&quot;
</ins><span class="cx"> 
</span><del>-                finally:
-                    yield updateLock.release()
</del><ins>+            propFilterExpressions = None
+            for propFilterElement in propFilter.filters:
+                propFilterExpression = None
+                if isinstance(propFilterElement, ParameterFilter):
+                    propFilterExpression = paramFilterElementExpression(propFilterAllOf, propFilterElement)
+                elif isinstance(propFilterElement, TextMatch):
+                    propFilterExpression = textMatchElementExpression(propFilterAllOf, propFilterElement)
+                propFilterExpressions = combineExpressionLists(propFilterExpressions, propFilterAllOf, propFilterExpression)
+                if isinstance(propFilterExpressions, bool) and propFilterAllOf != propFilterExpression:
+                    break
</ins><span class="cx"> 
</span><del>-                returnValue(response)
</del><ins>+            if isinstance(propFilterExpressions, list):
+                propFilterExpressions = list(set(propFilterExpressions))
+                if propFilterExpressions and (filterAllOf != propFilterAllOf):
+                    propFilterExpressions = [CompoundExpression(propFilterExpressions, Operand.AND if propFilterAllOf else Operand.OR)]
+
+            return propFilterExpressions
+            #end propFilterExpression
+
+        expressions = None
+        for propFilter in propFilters:
+
+            propExpressions = propFilterExpression(filterAllOf, propFilter)
+            expressions = combineExpressionLists(expressions, filterAllOf, propExpressions)
+
+            # early loop exit
+            if isinstance(expressions, bool) and filterAllOf != expressions:
+                break
+
+        # convert to needsAllRecords to return
+        # log.debug(&quot;expressionFromABFilter: expressions={q!r}&quot;, q=expressions,)
+        if isinstance(expressions, list):
+            expressions = list(set(expressions))
+            if len(expressions) &gt; 1:
+                expr = CompoundExpression(expressions, Operand.AND if filterAllOf else Operand.OR)
+            elif len(expressions):
+                expr = expressions[0]
+            else:
+                expr = not filterAllOf  # empty expression list. should not happen
+        elif expressions is None:
+            expr = not filterAllOf
+        else:
+            # True or False
+            expr = expressions
+
+        properties = [propFilter.filter_name for propFilter in propFilters]
+
+        return (tuple(set(properties)), expr)
+
+    # Top-level filter contains zero or more prop-filters
+    properties = tuple()
+    expression = None
+    if addressBookFilter:
+        filterAllOf = addressBookFilter.filter_test == &quot;allof&quot;
+        if len(addressBookFilter.children):
+            properties, expression = propFilterListQuery(filterAllOf, addressBookFilter.children)
+        else:
+            expression = not filterAllOf
+
+    #log.debug(&quot;expressionFromABFilter: expression={q!r}, properties={pn}&quot;, q=expression, pn=properties)
+    return((properties, expression))
+
+
+
+class ABDirectoryQueryResult(DAVPropertyMixIn):
+    &quot;&quot;&quot;
+    Result from ab query report or multiget on directory
+    &quot;&quot;&quot;
+
+    def __init__(self, directoryBackedAddressBook,):
+
+        self._directoryBackedAddressBook = directoryBackedAddressBook
+        #self._vCard = None
+
+
+    def __repr__(self):
+        return &quot;&lt;{self.__class__.__name__}[{rn}({uid})]&gt;&quot;.format(
+            self=self,
+            fn=self.vCard().propertyValue(&quot;FN&quot;),
+            uid=self.vCard().propertyValue(&quot;UID&quot;)
+        )
+
+    '''
+    def __hash__(self):
+        s = &quot;&quot;.join([
+              &quot;{attr}:{values}&quot;.format(attr=attribute, values=self.valuesForAttribute(attribute),)
+              for attribute in self.attributes
+              ])
+        return hash(s)
+    '''
+
+    @inlineCallbacks
+    def generate(self, record, forceKind=None, addProps=None,):
+        self._vCard = yield vCardFromRecord(record, forceKind, addProps, None)
+        returnValue(self)
+
+
+    def vCard(self):
+        return self._vCard
+
+
+    def vCardText(self):
+        return str(self._vCard)
+
+
+    def uri(self):
+        return self.vCard().propertyValue(&quot;UID&quot;) + &quot;.vcf&quot;
+
+
+    def hRef(self, parentURI=None):
+        return davxml.HRef.fromString(joinURL(parentURI if parentURI else  self._directoryBackedAddressBook.uri, self.uri()))
+
+
+    def readProperty(self, property, request):
+
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+        namespace, name = qname
+
+        if namespace == dav_namespace:
+            if name == &quot;resourcetype&quot;:
+                result = davxml.ResourceType.empty #@UndefinedVariable
+                return result
+            elif name == &quot;getetag&quot;:
+                result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
+                return result
+            elif name == &quot;getcontenttype&quot;:
+                mimeType = MimeType('text', 'vcard', {})
+                result = davxml.GETContentType(generateContentType(mimeType))
+                return result
+            elif name == &quot;getcontentlength&quot;:
+                result = davxml.GETContentLength.fromString(str(len(self.vCardText())))
+                return result
+            elif name == &quot;getlastmodified&quot;:
+                if self.vCard().hasProperty(&quot;REV&quot;):
+                    modDatetime = parse_date(self.vCard().propertyValue(&quot;REV&quot;))
+                else:
+                    modDatetime = datetime.datetime.utcnow()
+
+                #strip time zone because time zones are unimplemented in davxml.GETLastModified.fromDate
+                d = modDatetime.date()
+                t = modDatetime.time()
+                modDatetimeNoTZ = datetime.datetime(d.year, d.month, d.day, t.hour, t.minute, t.second, t.microsecond, None)
+                result = davxml.GETLastModified.fromDate(modDatetimeNoTZ)
+                return result
+            elif name == &quot;creationdate&quot;:
+                if self.vCard().hasProperty(&quot;REV&quot;):  # use modification date property if it exists
+                    creationDatetime = parse_date(self.vCard().propertyValue(&quot;REV&quot;))
+                else:
+                    creationDatetime = datetime.datetime.utcnow()
+                result = davxml.CreationDate.fromDate(creationDatetime)
+                return result
+            elif name == &quot;displayname&quot;:
+                # AddressBook.app uses N. Use FN or UID instead?
+                result = davxml.DisplayName.fromString(self.vCard().propertyValue(&quot;N&quot;))
+                return result
+
+        elif namespace == twisted_dav_namespace:
+            return super(ABDirectoryQueryResult, self).readProperty(property, request)
+
+        return self._directoryBackedAddressBook.readProperty(property, request)
+
+
+    def listProperties(self, request):  # @UnusedVariable
+        qnames = set(self.liveProperties())
+
+        # Add dynamic live properties that exist
+        dynamicLiveProperties = (
+            (dav_namespace, &quot;quota-available-bytes&quot;),
+            (dav_namespace, &quot;quota-used-bytes&quot;),
+        )
+        for dqname in dynamicLiveProperties:
+            qnames.remove(dqname)
+
+        for qname in self.deadProperties().list():
+            if (qname not in qnames) and (qname[0] != twisted_private_namespace):
+                qnames.add(qname)
+
+        yield qnames
+
+    listProperties = deferredGenerator(listProperties)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavextensionspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/extensions.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/extensions.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/extensions.py        2014-04-04 17:20:27 UTC (rev 13158)
</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 class="lines">@@ -299,7 +307,7 @@
</span><span class="cx">         records = (yield dir.recordsMatchingTokens(tokens, context=context))
</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 class="lines">@@ -420,9 +428,9 @@
</span><span class="cx">                 f.trap(HTTPError)
</span><span class="cx">                 code = f.value.response.code
</span><span class="cx">                 if code == responsecode.NOT_FOUND:
</span><del>-                    log.error(&quot;Property %s was returned by listProperties() &quot;
-                              &quot;but does not exist for resource %s.&quot;
-                              % (name, self.resource))
</del><ins>+                    log.error(&quot;Property {p} was returned by listProperties() &quot;
+                              &quot;but does not exist for resource {r}.&quot;,
+                              p=name, r=self.resource)
</ins><span class="cx">                     return (name, None)
</span><span class="cx">                 if code == responsecode.UNAUTHORIZED:
</span><span class="cx">                     return (name, accessDeniedValue)
</span><span class="lines">@@ -721,7 +729,13 @@
</span><span class="cx"> 
</span><span class="cx">             elif name == &quot;record-type&quot;:
</span><span class="cx">                 if hasattr(self, &quot;record&quot;):
</span><del>-                    returnValue(customxml.RecordType(self.record.recordType))
</del><ins>+                    returnValue(
+                        customxml.RecordType(
+                            self.record.service.recordTypeToOldName(
+                                self.record.recordType
+                            )
+                        )
+                    )
</ins><span class="cx">                 else:
</span><span class="cx">                     raise HTTPError(StatusResponse(
</span><span class="cx">                         responsecode.NOT_FOUND,
</span><span class="lines">@@ -848,7 +862,7 @@
</span><span class="cx">     ):
</span><span class="cx">         # Permissions here are fixed, and are not subject to
</span><span class="cx">         # inheritance rules, etc.
</span><del>-        return succeed(self.defaultAccessControlList())
</del><ins>+        return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -990,7 +1004,7 @@
</span><span class="cx">             applyTo = True
</span><span class="cx"> 
</span><span class="cx">         elif child.qname() == (calendarserver_namespace, &quot;search-token&quot;):
</span><del>-            tokens.append(str(child))
</del><ins>+            tokens.append(child.toString())
</ins><span class="cx"> 
</span><span class="cx">         elif child.qname() == (calendarserver_namespace, &quot;limit&quot;):
</span><span class="cx">             try:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavfreebusyurlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/freebusyurl.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/freebusyurl.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/freebusyurl.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -102,7 +102,7 @@
</span><span class="cx">                     davxml.Protected(),
</span><span class="cx">                 ),
</span><span class="cx">             )
</span><del>-        return davxml.ACL(*aces)
</del><ins>+        return succeed(davxml.ACL(*aces))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def resourceType(self):
</span><span class="lines">@@ -243,7 +243,7 @@
</span><span class="cx">         # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long)
</span><span class="cx"> 
</span><span class="cx">         # Now lookup the principal details for the targeted user
</span><del>-        principal = self.parent.principalForRecord()
</del><ins>+        principal = (yield self.parent.principalForRecord())
</ins><span class="cx"> 
</span><span class="cx">         # Pick the first mailto cu address or the first other type
</span><span class="cx">         cuaddr = None
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/ical.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/ical.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/ical.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -34,6 +34,7 @@
</span><span class="cx"> import itertools
</span><span class="cx"> import uuid
</span><span class="cx"> 
</span><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from txweb2.stream import IStream
</span><span class="cx"> from txweb2.dav.util import allDataFromStream
</span><span class="lines">@@ -3239,7 +3240,8 @@
</span><span class="cx">                         self.removeProperty(attachment)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def normalizeCalendarUserAddresses(self, lookupFunction, principalFunction,
</del><ins>+    @inlineCallbacks
+    def normalizeCalendarUserAddresses(self, lookupFunction, recordFunction,
</ins><span class="cx">         toUUID=True):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Do the ORGANIZER/ATTENDEE property normalization.
</span><span class="lines">@@ -3247,6 +3249,7 @@
</span><span class="cx">         @param lookupFunction: function returning full name, guid, CUAs for a given CUA
</span><span class="cx">         @type lookupFunction: L{Function}
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">         for component in self.subcomponents():
</span><span class="cx">             if component.name() in ignoredComponents:
</span><span class="cx">                 continue
</span><span class="lines">@@ -3259,7 +3262,7 @@
</span><span class="cx">                 # Check that we can lookup this calendar user address - if not
</span><span class="cx">                 # we cannot do anything with it
</span><span class="cx">                 cuaddr = normalizeCUAddr(prop.value())
</span><del>-                name, guid, cuaddrs = lookupFunction(cuaddr, principalFunction, config)
</del><ins>+                name, guid, cuaddrs = yield lookupFunction(cuaddr, recordFunction, config)
</ins><span class="cx">                 if guid is None:
</span><span class="cx">                     continue
</span><span class="cx"> 
</span><span class="lines">@@ -3275,7 +3278,9 @@
</span><span class="cx"> 
</span><span class="cx">                 if toUUID:
</span><span class="cx">                     # Always re-write value to urn:uuid
</span><del>-                    prop.setValue(&quot;urn:uuid:%s&quot; % (guid,))
</del><ins>+                    if isinstance(guid, uuid.UUID):
+                        guid = unicode(guid).upper()
+                    prop.setValue(&quot;urn:uuid:{guid}&quot;.format(guid=guid))
</ins><span class="cx"> 
</span><span class="cx">                 # If it is already a non-UUID address leave it be
</span><span class="cx">                 elif cuaddr.startswith(&quot;urn:uuid:&quot;):
</span><span class="lines">@@ -3353,7 +3358,7 @@
</span><span class="cx"> 
</span><span class="cx">             # For VPOLL also do immediate children
</span><span class="cx">             if component.name() == &quot;VPOLL&quot;:
</span><del>-                component.normalizeCalendarUserAddresses(lookupFunction, principalFunction, toUUID)
</del><ins>+                yield component.normalizeCalendarUserAddresses(lookupFunction, recordFunction, toUUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def allPerUserUIDs(self):
</span><span class="lines">@@ -3563,15 +3568,16 @@
</span><span class="cx"> # Utilities
</span><span class="cx"> # #
</span><span class="cx"> 
</span><del>-def normalizeCUAddress(cuaddr, lookupFunction, principalFunction, toUUID=True):
</del><ins>+@inlineCallbacks
+def normalizeCUAddress(cuaddr, lookupFunction, recordFunction, toUUID=True):
</ins><span class="cx">     # Check that we can lookup this calendar user address - if not
</span><span class="cx">     # we cannot do anything with it
</span><del>-    _ignore_name, guid, cuaddrs = lookupFunction(normalizeCUAddr(cuaddr), principalFunction, config)
</del><ins>+    _ignore_name, guid, cuaddrs = (yield lookupFunction(normalizeCUAddr(cuaddr), recordFunction, config))
</ins><span class="cx"> 
</span><span class="cx">     if toUUID:
</span><span class="cx">         # Always re-write value to urn:uuid
</span><span class="cx">         if guid:
</span><del>-            return &quot;urn:uuid:%s&quot; % (guid,)
</del><ins>+            returnValue(&quot;urn:uuid:%s&quot; % (guid,))
</ins><span class="cx"> 
</span><span class="cx">     # If it is already a non-UUID address leave it be
</span><span class="cx">     elif cuaddr.startswith(&quot;urn:uuid:&quot;):
</span><span class="lines">@@ -3610,9 +3616,9 @@
</span><span class="cx"> 
</span><span class="cx">         # Make the change
</span><span class="cx">         if newaddr:
</span><del>-            return newaddr
</del><ins>+            returnValue(newaddr)
</ins><span class="cx"> 
</span><del>-    return cuaddr
</del><ins>+    returnValue(cuaddr)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreportpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -43,6 +43,7 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def http_REPORT(self, request):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -58,7 +59,7 @@
</span><span class="cx">     try:
</span><span class="cx">         doc = (yield davXMLFromStream(request.stream))
</span><span class="cx">     except ValueError, e:
</span><del>-        log.error(&quot;Error while handling REPORT body: %s&quot; % (e,))
</del><ins>+        log.error(&quot;Error while handling REPORT body: {err}&quot;, err=str(e))
</ins><span class="cx">         raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
</span><span class="cx"> 
</span><span class="cx">     if doc is None:
</span><span class="lines">@@ -116,8 +117,8 @@
</span><span class="cx">         #
</span><span class="cx">         # Requested report is not supported.
</span><span class="cx">         #
</span><del>-        log.error(&quot;Unsupported REPORT %s for resource %s (no method %s)&quot;
-                  % (encodeXMLName(namespace, name), self, method_name))
</del><ins>+        log.error(&quot;Unsupported REPORT {name} for resource {resource} (no method {method})&quot;,
+                  name=encodeXMLName(namespace, name), resource=self, method=method_name)
</ins><span class="cx"> 
</span><span class="cx">         raise HTTPError(ErrorResponse(
</span><span class="cx">             responsecode.FORBIDDEN,
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_addressbook_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_addressbook_query.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_addressbook_query.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_addressbook_query.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -21,26 +21,22 @@
</span><span class="cx"> 
</span><span class="cx"> __all__ = [&quot;report_urn_ietf_params_xml_ns_carddav_addressbook_query&quot;]
</span><span class="cx"> 
</span><del>-import urllib
-
-from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
-
</del><span class="cx"> from twext.python.log import Logger
</span><del>-from txweb2 import responsecode
-from txweb2.dav.http import ErrorResponse, MultiStatusResponse
-from txweb2.dav.method.report import NumberOfMatchesWithinLimits
-from txweb2.dav.util import joinURL
-from txweb2.http import HTTPError, StatusResponse
-
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
</ins><span class="cx"> from twistedcaldav import carddavxml
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NResults
</span><ins>+from twistedcaldav.config import config
</ins><span class="cx"> from twistedcaldav.method import report_common
</span><del>-
</del><span class="cx"> from txdav.carddav.datastore.query.filter import Filter
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification, \
</span><span class="cx">     IndexedSearchException
</span><span class="cx"> from txdav.xml import element as davxml
</span><ins>+from txweb2 import responsecode
+from txweb2.dav.http import ErrorResponse, MultiStatusResponse
+from txweb2.dav.method.report import NumberOfMatchesWithinLimits
+from txweb2.dav.util import joinURL
+from txweb2.http import HTTPError, StatusResponse
+import urllib
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -52,12 +48,12 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     # Verify root element
</span><span class="cx">     if addressbook_query.qname() != (carddav_namespace, &quot;addressbook-query&quot;):
</span><del>-        raise ValueError(&quot;{CardDAV:}addressbook-query expected as root element, not %s.&quot; % (addressbook_query.sname(),))
</del><ins>+        raise ValueError(&quot;{CardDAV:}addressbook-query expected as root element, not {elementName}.&quot;.format(elementName=addressbook_query.sname()))
</ins><span class="cx"> 
</span><span class="cx">     if not self.isCollection():
</span><span class="cx">         parent = (yield self.locateParent(request, request.uri))
</span><span class="cx">         if not parent.isAddressBookCollection():
</span><del>-            log.error(&quot;addressbook-query report is not allowed on a resource outside of an address book collection %s&quot; % (self,))
</del><ins>+            log.error(&quot;addressbook-query report is not allowed on a resource outside of an address book collection {parent}&quot;, parent=self)
</ins><span class="cx">             raise HTTPError(StatusResponse(responsecode.FORBIDDEN, &quot;Must be address book collection or address book resource&quot;))
</span><span class="cx"> 
</span><span class="cx">     responses = []
</span><span class="lines">@@ -154,160 +150,127 @@
</span><span class="cx">                     # of one of these resources in another request.  In this
</span><span class="cx">                     # case, we ignore the now missing resource rather
</span><span class="cx">                     # than raise an error for the entire report.
</span><del>-                    log.error(&quot;Missing resource during sync: %s&quot; % (href,))
</del><ins>+                    log.error(&quot;Missing resource during sync: {href}&quot;, href=href)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter):
</span><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">             &quot;&quot;&quot;
</span><del>-            records, limited[0] = (yield directoryBackedAddressBook.directory.vCardRecordsForAddressBookQuery(addressBookFilter, query, max_number_of_results[0]))
-            for vCardRecord in records:
</del><ins>+            results, limited[0] = yield directoryBackedAddressBook.doAddressBookDirectoryQuery(addressBookFilter, query, max_number_of_results[0], defaultKind=&quot;individual&quot;)
+            for vCardResult in results:
</ins><span class="cx"> 
</span><del>-                # match against original filter
-                if filter.match((yield vCardRecord.vCard())):
</del><ins>+                # match against original filter if different from addressBookFilter
+                if addressBookFilter is filter or filter.match((yield vCardResult.vCard())):
</ins><span class="cx"> 
</span><span class="cx">                     # Check size of results is within limit
</span><span class="cx">                     checkMaxResults()
</span><span class="cx"> 
</span><span class="cx">                     try:
</span><del>-                        yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=(yield vCardRecord.vCard()))
</del><ins>+                        yield report_common.responseForHref(request, responses, vCardResult.hRef(), vCardResult, propertiesForResource, query, vcard=(yield vCardResult.vCard()))
</ins><span class="cx">                     except ConcurrentModification:
</span><span class="cx">                         # This can happen because of a race-condition between the
</span><span class="cx">                         # time we determine which resources exist and the deletion
</span><span class="cx">                         # of one of these resources in another request.  In this
</span><span class="cx">                         # case, we ignore the now missing resource rather
</span><span class="cx">                         # than raise an error for the entire report.
</span><del>-                        log.error(&quot;Missing resource during sync: %s&quot; % (vCardRecord.hRef(),))
</del><ins>+                        log.error(&quot;Missing resource during sync: {href}&quot;, href=vCardResult.hRef())
</ins><span class="cx"> 
</span><del>-        directoryAddressBookLock = None
-        try:
</del><ins>+        if not addrresource.isAddressBookCollection():
</ins><span class="cx"> 
</span><del>-            if addrresource.isDirectoryBackedAddressBookCollection() and addrresource.directory.cacheQuery:
</del><ins>+            #do UID lookup on last part of uri
+            resource_name = urllib.unquote(uri[uri.rfind(&quot;/&quot;) + 1:])
+            if resource_name.endswith(&quot;.vcf&quot;) and len(resource_name) &gt; 4:
</ins><span class="cx"> 
</span><del>-                directory = addrresource.directory
-                if directory.liveQuery:
-                    # if liveQuery and cacheQuery, get vCards into the directory address book on disk
-                    directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery(filter, query, max_number_of_results[0]))
</del><ins>+                # see if parent is directory backed address book
+                parent = (yield  addrresource.locateParent(request, uri))
</ins><span class="cx"> 
</span><del>-                elif directory.maxDSQueryRecords and directory.maxDSQueryRecords &lt; max_number_of_results[0]:
-                    max_number_of_results[0] = directory.maxDSQueryRecords
</del><ins>+        # Check whether supplied resource is an address book or an address book object resource
+        if addrresource.isAddressBookCollection():
</ins><span class="cx"> 
</span><del>-            elif not addrresource.isAddressBookCollection():
</del><ins>+            if addrresource.isDirectoryBackedAddressBookCollection():
+                yield  maybeDeferred(queryDirectoryBackedAddressBook, addrresource, filter)
</ins><span class="cx"> 
</span><del>-                #do UID lookup on last part of uri
-                resource_name = urllib.unquote(uri[uri.rfind(&quot;/&quot;) + 1:])
-                if resource_name.endswith(&quot;.vcf&quot;) and len(resource_name) &gt; 4:
</del><ins>+            else:
</ins><span class="cx"> 
</span><del>-                    # see if parent is directory backed address book
-                    parent = (yield  addrresource.locateParent(request, uri))
</del><ins>+                # Do some optimisation of access control calculation by determining any inherited ACLs outside of
+                # the child resource loop and supply those to the checkPrivileges on each child.
+                filteredaces = (yield addrresource.inheritedACEsforChildren(request))
</ins><span class="cx"> 
</span><del>-                    if parent.isDirectoryBackedAddressBookCollection() and parent.directory.cacheQuery:
</del><ins>+                # Check for disabled access
+                if filteredaces is not None:
+                    index_query_ok = True
+                    try:
+                        # Get list of children that match the search and have read access
+                        names = [name for name, ignore_uid in (yield addrresource.search(filter))] #@UnusedVariable
+                    except IndexedSearchException:
+                        names = yield addrresource.listChildren()
+                        index_query_ok = False
+                    if not names:
+                        return
</ins><span class="cx"> 
</span><del>-                        directory = parent.directory
-                        if directory.liveQuery:
-                            vCardFilter = carddavxml.Filter(*[carddavxml.PropertyFilter(
-                                                        carddavxml.TextMatch.fromString(resource_name[:-4]),
-                                                        name=&quot;UID&quot;, # attributes
-                                                        ), ])
-                            vCardFilter = Filter(vCardFilter)
</del><ins>+                    # Now determine which valid resources are readable and which are not
+                    ok_resources = []
+                    yield addrresource.findChildrenFaster(
+                        &quot;1&quot;,
+                        request,
+                        lambda x, y: ok_resources.append((x, y)),
+                        None,
+                        None,
+                        None,
+                        names,
+                        (davxml.Read(),),
+                        inherited_aces=filteredaces
+                    )
+                    for child, child_uri in ok_resources:
+                        child_uri_name = child_uri[child_uri.rfind(&quot;/&quot;) + 1:]
</ins><span class="cx"> 
</span><del>-                            directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery(vCardFilter, query, max_number_of_results[0]))
</del><ins>+                        if generate_address_data or not index_query_ok:
+                            vcard = yield child.vCard()
+                            assert vcard is not None, &quot;vCard {name} is missing from address book collection {collection!r}&quot;.format(name=child_uri_name, collection=self)
+                        else:
+                            vcard = None
</ins><span class="cx"> 
</span><del>-                        elif directory.maxDSQueryRecords and directory.maxDSQueryRecords &lt; max_number_of_results[0]:
-                            max_number_of_results[0] = directory.maxDSQueryRecords
</del><ins>+                        yield queryAddressBookObjectResource(child, uri, child_uri_name, vcard, query_ok=index_query_ok)
</ins><span class="cx"> 
</span><del>-            # Check whether supplied resource is an address book or an address book object resource
-            if addrresource.isAddressBookCollection():
</del><ins>+        else:
</ins><span class="cx"> 
</span><del>-                if addrresource.isDirectoryBackedAddressBookCollection() and addrresource.directory.liveQuery and not addrresource.directory.cacheQuery:
-                    yield  maybeDeferred(queryDirectoryBackedAddressBook, addrresource, filter)
</del><ins>+            handled = False
+            resource_name = urllib.unquote(uri[uri.rfind(&quot;/&quot;) + 1:])
+            if resource_name.endswith(&quot;.vcf&quot;) and len(resource_name) &gt; 4:
</ins><span class="cx"> 
</span><del>-                else:
</del><ins>+                # see if parent is directory backed address book
+                parent = (yield  addrresource.locateParent(request, uri))
</ins><span class="cx"> 
</span><del>-                    # Do some optimisation of access control calculation by determining any inherited ACLs outside of
-                    # the child resource loop and supply those to the checkPrivileges on each child.
-                    filteredaces = (yield addrresource.inheritedACEsforChildren(request))
</del><ins>+                if parent.isDirectoryBackedAddressBookCollection():
</ins><span class="cx"> 
</span><del>-                    # Check for disabled access
-                    if filteredaces is not None:
-                        index_query_ok = True
-                        try:
-                            # Get list of children that match the search and have read access
-                            names = [name for name, ignore_uid in (yield addrresource.search(filter))] #@UnusedVariable
-                        except IndexedSearchException:
-                            names = yield addrresource.listChildren()
-                            index_query_ok = False
-                        if not names:
-                            return
</del><ins>+                    vCardFilter = carddavxml.Filter(*[carddavxml.PropertyFilter(
+                                                carddavxml.TextMatch.fromString(resource_name[:-4]),
+                                                name=&quot;UID&quot;, # attributes
+                                                ), ])
+                    vCardFilter = Filter(vCardFilter)
</ins><span class="cx"> 
</span><del>-                        # Now determine which valid resources are readable and which are not
-                        ok_resources = []
-                        yield addrresource.findChildrenFaster(
-                            &quot;1&quot;,
-                            request,
-                            lambda x, y: ok_resources.append((x, y)),
-                            None,
-                            None,
-                            None,
-                            names,
-                            (davxml.Read(),),
-                            inherited_aces=filteredaces
-                        )
-                        for child, child_uri in ok_resources:
-                            child_uri_name = child_uri[child_uri.rfind(&quot;/&quot;) + 1:]
</del><ins>+                    yield  maybeDeferred(queryDirectoryBackedAddressBook, parent, vCardFilter)
+                    handled = True
</ins><span class="cx"> 
</span><del>-                            if generate_address_data or not index_query_ok:
-                                vcard = yield child.vCard()
-                                assert vcard is not None, &quot;vCard %s is missing from address book collection %r&quot; % (child_uri_name, self)
-                            else:
-                                vcard = None
</del><ins>+            if not handled:
+                vcard = yield addrresource.vCard()
+                yield queryAddressBookObjectResource(addrresource, uri, None, vcard)
</ins><span class="cx"> 
</span><del>-                            yield queryAddressBookObjectResource(child, uri, child_uri_name, vcard, query_ok=index_query_ok)
</del><ins>+        if limited[0]:
+            raise NumberOfMatchesWithinLimits(matchcount[0])
</ins><span class="cx"> 
</span><del>-            else:
-
-                handled = False
-                resource_name = urllib.unquote(uri[uri.rfind(&quot;/&quot;) + 1:])
-                if resource_name.endswith(&quot;.vcf&quot;) and len(resource_name) &gt; 4:
-
-                    # see if parent is directory backed address book
-                    parent = (yield  addrresource.locateParent(request, uri))
-
-                    if parent.isDirectoryBackedAddressBookCollection() and parent.directory.liveQuery and not parent.directory.cacheQuery:
-
-                        vCardFilter = carddavxml.Filter(*[carddavxml.PropertyFilter(
-                                                    carddavxml.TextMatch.fromString(resource_name[:-4]),
-                                                    name=&quot;UID&quot;, # attributes
-                                                    ), ])
-                        vCardFilter = Filter(vCardFilter)
-
-                        yield  maybeDeferred(queryDirectoryBackedAddressBook, parent, vCardFilter)
-                        handled = True
-
-                if not handled:
-                    vcard = yield addrresource.vCard()
-                    yield queryAddressBookObjectResource(addrresource, uri, None, vcard)
-
-            if limited[0]:
-                raise NumberOfMatchesWithinLimits(matchcount[0])
-
-        finally:
-            if directoryAddressBookLock:
-                yield directoryAddressBookLock.release()
-
</del><span class="cx">     # Run report taking depth into account
</span><span class="cx">     try:
</span><span class="cx">         depth = request.headers.getHeader(&quot;depth&quot;, &quot;0&quot;)
</span><span class="cx">         yield report_common.applyToAddressBookCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
</span><span class="cx">     except NumberOfMatchesWithinLimits, e:
</span><del>-        self.log.info(&quot;Too many matching components in addressbook-query report. Limited to %d items&quot; % e.maxLimit())
</del><ins>+        self.log.info(&quot;Too many matching components in addressbook-query report. Limited to {limit} items&quot;, limit=e.maxLimit())
</ins><span class="cx">         responses.append(davxml.StatusResponse(
</span><span class="cx">                         davxml.HRef.fromString(request.uri),
</span><span class="cx">                         davxml.Status.fromResponseCode(responsecode.INSUFFICIENT_STORAGE_SPACE),
</span><span class="cx">                         davxml.Error(davxml.NumberOfMatchesWithinLimits()),
</span><del>-                        #davxml.ResponseDescription(&quot;Results limited by %s at %d&quot; % resultsWereLimited),
-                        davxml.ResponseDescription(&quot;Results limited to %d items&quot; % e.maxLimit()),
</del><ins>+                        davxml.ResponseDescription(&quot;Results limited to {limit} items&quot;.format(limit=e.maxLimit())),
</ins><span class="cx">                     ))
</span><span class="cx"> 
</span><span class="cx">     if not hasattr(request, &quot;extendedLogItems&quot;):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_common.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_common.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_common.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -503,7 +503,7 @@
</span><span class="cx">             returnValue(matchtotal)
</span><span class="cx"> 
</span><span class="cx">     # May need organizer principal
</span><del>-    organizer_principal = calresource.principalForCalendarUserAddress(organizer) if organizer else None
</del><ins>+    organizer_principal = (yield calresource.principalForCalendarUserAddress(organizer)) if organizer else None
</ins><span class="cx">     organizer_uid = organizer_principal.principalUID() if organizer_principal else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     # Free busy is per-user
</span><span class="lines">@@ -630,7 +630,7 @@
</span><span class="cx">                 if excludeuid:
</span><span class="cx">                     # See if we have a UID match
</span><span class="cx">                     if (excludeuid == uid):
</span><del>-                        test_principal = calresource.principalForCalendarUserAddress(test_organizer) if test_organizer else None
</del><ins>+                        test_principal = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
</ins><span class="cx">                         test_uid = test_principal.principalUID() if test_principal else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">                         # Check that ORGANIZER's match (security requirement)
</span><span class="lines">@@ -688,7 +688,7 @@
</span><span class="cx">                 # See if we have a UID match
</span><span class="cx">                 if (excludeuid == uid):
</span><span class="cx">                     test_organizer = calendar.getOrganizer()
</span><del>-                    test_principal = calresource.principalForCalendarUserAddress(test_organizer) if test_organizer else None
</del><ins>+                    test_principal = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
</ins><span class="cx">                     test_uid = test_principal.principalUID() if test_principal else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">                     # Check that ORGANIZER's match (security requirement)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavmethodreport_multiget_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_multiget_common.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_multiget_common.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/method/report_multiget_common.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,17 +20,8 @@
</span><span class="cx"> 
</span><span class="cx"> __all__ = [&quot;multiget_common&quot;]
</span><span class="cx"> 
</span><del>-from urllib import unquote
-
</del><span class="cx"> from twext.python.log import Logger
</span><del>-
-from txweb2 import responsecode
-from txweb2.dav.http import ErrorResponse, MultiStatusResponse
-from txweb2.dav.resource import AccessDeniedError
-from txweb2.http import HTTPError, StatusResponse
-
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><del>-
</del><span class="cx"> from twistedcaldav import carddavxml
</span><span class="cx"> from twistedcaldav.caldavxml import caldav_namespace
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace
</span><span class="lines">@@ -38,11 +29,15 @@
</span><span class="cx"> from twistedcaldav.method import report_common
</span><span class="cx"> from twistedcaldav.method.report_common import COLLECTION_TYPE_CALENDAR, \
</span><span class="cx">     COLLECTION_TYPE_ADDRESSBOOK
</span><del>-
</del><span class="cx"> from txdav.carddav.datastore.query.filter import Filter
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.base import dav_namespace
</span><ins>+from txweb2 import responsecode
+from txweb2.dav.http import ErrorResponse, MultiStatusResponse
+from txweb2.dav.resource import AccessDeniedError
+from txweb2.http import HTTPError, StatusResponse
+from urllib import unquote
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -58,11 +53,11 @@
</span><span class="cx"> 
</span><span class="cx">         if collection_type == COLLECTION_TYPE_CALENDAR:
</span><span class="cx">             if not parent.isPseudoCalendarCollection():
</span><del>-                log.error(&quot;calendar-multiget report is not allowed on a resource outside of a calendar collection %s&quot; % (self,))
</del><ins>+                log.error(&quot;calendar-multiget report is not allowed on a resource outside of a calendar collection {res}&quot;, res=self)
</ins><span class="cx">                 raise HTTPError(StatusResponse(responsecode.FORBIDDEN, &quot;Must be calendar resource&quot;))
</span><span class="cx">         elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><span class="cx">             if not parent.isAddressBookCollection():
</span><del>-                log.error(&quot;addressbook-multiget report is not allowed on a resource outside of an address book collection %s&quot; % (self,))
</del><ins>+                log.error(&quot;addressbook-multiget report is not allowed on a resource outside of an address book collection {res}&quot;, res=self)
</ins><span class="cx">                 raise HTTPError(StatusResponse(responsecode.FORBIDDEN, &quot;Must be address book resource&quot;))
</span><span class="cx"> 
</span><span class="cx">     responses = []
</span><span class="lines">@@ -106,7 +101,7 @@
</span><span class="cx"> 
</span><span class="cx">     # Check size of results is within limit when data property requested
</span><span class="cx">     if hasData and len(resources) &gt; config.MaxMultigetWithDataHrefs:
</span><del>-        log.error(&quot;Too many results in multiget report returning data: %d&quot; % len(resources))
</del><ins>+        log.error(&quot;Too many resources in multiget report: {count}&quot;, count=len(resources))
</ins><span class="cx">         raise HTTPError(ErrorResponse(
</span><span class="cx">             responsecode.FORBIDDEN,
</span><span class="cx">             davxml.NumberOfMatchesWithinLimits(),
</span><span class="lines">@@ -172,7 +167,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Special for addressbooks
</span><span class="cx">             if collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><del>-                if self.isDirectoryBackedAddressBookCollection() and self.directory.liveQuery:
</del><ins>+                if self.isDirectoryBackedAddressBookCollection():
</ins><span class="cx">                     result = (yield doDirectoryAddressBookResponse())
</span><span class="cx">                     returnValue(result)
</span><span class="cx"> 
</span><span class="lines">@@ -214,8 +209,7 @@
</span><span class="cx">                         isowner=isowner
</span><span class="cx">                     )
</span><span class="cx">                 except ValueError:
</span><del>-                    log.error(&quot;Invalid calendar resource during multiget: %s&quot; %
-                              (href,))
</del><ins>+                    log.error(&quot;Invalid calendar resource during multiget: {href}&quot;, href=href)
</ins><span class="cx">                     responses.append(davxml.StatusResponse(
</span><span class="cx">                         davxml.HRef.fromString(href),
</span><span class="cx">                         davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="lines">@@ -225,7 +219,7 @@
</span><span class="cx">                     # of one of these resources in another request.  In this
</span><span class="cx">                     # case, return a 404 for the now missing resource rather
</span><span class="cx">                     # than raise an error for the entire report.
</span><del>-                    log.error(&quot;Missing resource during multiget: %s&quot; % (href,))
</del><ins>+                    log.error(&quot;Missing resource during multiget: {href}&quot;, href=href)
</ins><span class="cx">                     responses.append(davxml.StatusResponse(
</span><span class="cx">                         davxml.HRef.fromString(href),
</span><span class="cx">                         davxml.Status.fromResponseCode(responsecode.NOT_FOUND)
</span><span class="lines">@@ -255,11 +249,13 @@
</span><span class="cx">                     resource_name = unquote(resource_uri[resource_uri.rfind(&quot;/&quot;) + 1:])
</span><span class="cx">                     if self._isChildURI(request, resource_uri) and resource_name.endswith(&quot;.vcf&quot;) and len(resource_name) &gt; 4:
</span><span class="cx">                         valid_hrefs.append(href)
</span><ins>+                        textMatchElement = carddavxml.TextMatch.fromString(resource_name[:-4])
+                        textMatchElement.attributes[&quot;match-type&quot;] = &quot;equals&quot; # do equals compare. Default is &quot;contains&quot;
</ins><span class="cx">                         vCardFilters.append(carddavxml.PropertyFilter(
</span><del>-                                                carddavxml.TextMatch.fromString(resource_name[:-4]),
</del><ins>+                                                textMatchElement,
</ins><span class="cx">                                                 name=&quot;UID&quot;, # attributes
</span><span class="cx">                                             ))
</span><del>-                    elif not self.directory.cacheQuery:
</del><ins>+                    else:
</ins><span class="cx">                         responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
</span><span class="cx"> 
</span><span class="cx">                 # exit if not valid
</span><span class="lines">@@ -268,40 +264,29 @@
</span><span class="cx"> 
</span><span class="cx">                 addressBookFilter = carddavxml.Filter(*vCardFilters)
</span><span class="cx">                 addressBookFilter = Filter(addressBookFilter)
</span><del>-                if self.directory.cacheQuery:
-                    # add vcards to directory address book and run &quot;normal case&quot; below
-                    limit = config.DirectoryAddressBook.MaxQueryResults
-                    directoryAddressBookLock, limited = (yield  self.directory.cacheVCardsForAddressBookQuery(addressBookFilter, propertyreq, limit))
-                    if limited:
-                        log.error(&quot;Too many results in multiget report: %d&quot; % len(resources))
-                        raise HTTPError(ErrorResponse(
-                            responsecode.FORBIDDEN,
-                            (dav_namespace, &quot;number-of-matches-within-limits&quot;),
-                            &quot;Too many results&quot;,
-                        ))
-                else:
-                    #get vCards and filter
-                    limit = config.DirectoryAddressBook.MaxQueryResults
-                    vCardRecords, limited = (yield self.directory.vCardRecordsForAddressBookQuery(addressBookFilter, propertyreq, limit))
-                    if limited:
-                        log.error(&quot;Too many results in multiget report: %d&quot; % len(resources))
-                        raise HTTPError(ErrorResponse(
-                            responsecode.FORBIDDEN,
-                            (dav_namespace, &quot;number-of-matches-within-limits&quot;),
-                            &quot;Too many results&quot;,
-                        ))
</del><span class="cx"> 
</span><del>-                    for href in valid_hrefs:
-                        matchingRecord = None
-                        for vCardRecord in vCardRecords:
-                            if href == vCardRecord.hRef(): # might need to compare urls instead - also case sens ok?
-                                matchingRecord = vCardRecord
-                                break
</del><ins>+                #get vCards and filter
+                limit = config.DirectoryAddressBook.MaxQueryResults
+                results, limited = (yield self.doAddressBookDirectoryQuery(addressBookFilter, propertyreq, limit))
+                if limited:
+                    log.error(&quot;Too many results in multiget report: {count}&quot;, count=len(resources))
+                    raise HTTPError(ErrorResponse(
+                        responsecode.FORBIDDEN,
+                        (dav_namespace, &quot;number-of-matches-within-limits&quot;),
+                        &quot;Too many results&quot;,
+                    ))
</ins><span class="cx"> 
</span><del>-                        if matchingRecord:
-                            yield report_common.responseForHref(request, responses, href, matchingRecord, propertiesForResource, propertyreq, vcard=matchingRecord.vCard())
-                        else:
-                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
</del><ins>+                for href in valid_hrefs:
+                    matchingResource = None
+                    for vCardResource in results:
+                        if href == vCardResource.hRef(): # might need to compare urls instead - also case sens ok?
+                            matchingResource = vCardResource
+                            break
+
+                    if matchingResource:
+                        yield report_common.responseForHref(request, responses, href, matchingResource, propertiesForResource, propertyreq, vcard=matchingResource.vCard())
+                    else:
+                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
</ins><span class="cx">             finally:
</span><span class="cx">                 if directoryAddressBookLock:
</span><span class="cx">                     yield directoryAddressBookLock.release()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -77,7 +77,7 @@
</span><span class="cx"> from twistedcaldav.linkresource import LinkResource
</span><span class="cx"> from calendarserver.push.notifier import getPubSubAPSConfiguration
</span><span class="cx"> from twistedcaldav.sharing import SharedResourceMixin, SharedHomeMixin
</span><del>-from twistedcaldav.util import normalizationLookup
</del><ins>+from txdav.caldav.datastore.util import normalizationLookup
</ins><span class="cx"> from twistedcaldav.vcard import Component as vComponent
</span><span class="cx"> 
</span><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError, \
</span><span class="lines">@@ -863,7 +863,8 @@
</span><span class="cx">                 home = self._newStoreObject.parentCollection().ownerHome()
</span><span class="cx">             else:
</span><span class="cx">                 home = self._newStoreObject.ownerHome()
</span><del>-            returnValue(element.HRef(self.principalForUID(home.uid()).principalURL()))
</del><ins>+            principal = (yield self.principalForUID(home.uid()))
+            returnValue(element.HRef(principal.principalURL()))
</ins><span class="cx">         else:
</span><span class="cx">             parent = (yield self.locateParent(request, request.urlForResource(self)))
</span><span class="cx">         if parent and isinstance(parent, CalDAVResource):
</span><span class="lines">@@ -883,7 +884,7 @@
</span><span class="cx">                 home = self._newStoreObject.parentCollection().ownerHome()
</span><span class="cx">             else:
</span><span class="cx">                 home = self._newStoreObject.ownerHome()
</span><del>-            returnValue(self.principalForUID(home.uid()))
</del><ins>+            returnValue((yield self.principalForUID(home.uid())))
</ins><span class="cx">         else:
</span><span class="cx">             parent = (yield self.locateParent(request, request.urlForResource(self)))
</span><span class="cx">         if parent and isinstance(parent, CalDAVResource):
</span><span class="lines">@@ -933,8 +934,8 @@
</span><span class="cx">             return None
</span><span class="cx"> 
</span><span class="cx">         if 'record' in dir(self):
</span><del>-            if self.record.fullName:
-                return self.record.fullName
</del><ins>+            if self.record.fullNames:
+                return self.record.fullNames[0]
</ins><span class="cx">             elif self.record.shortNames:
</span><span class="cx">                 return self.record.shortNames[0]
</span><span class="cx">             else:
</span><span class="lines">@@ -1070,25 +1071,30 @@
</span><span class="cx"> 
</span><span class="cx">         @param ical: calendar object to normalize.
</span><span class="cx">         @type ical: L{Component}
</span><ins>+        @return: L{Deferred}
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        ical.normalizeCalendarUserAddresses(normalizationLookup,
-            self.principalForCalendarUserAddress)
</del><ins>+        return ical.normalizeCalendarUserAddresses(
+            normalizationLookup,
+            self.record.service.recordWithCalendarUserAddress
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForCalendarUserAddress(self, address):
</span><span class="cx">         for principalCollection in self.principalCollections():
</span><del>-            principal = principalCollection.principalForCalendarUserAddress(address)
</del><ins>+            principal = (yield principalCollection.principalForCalendarUserAddress(address))
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
-        return None
</del><ins>+                returnValue(principal)
+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForUID(self, principalUID):
</span><span class="cx">         for principalCollection in self.principalCollections():
</span><del>-            principal = principalCollection.principalForUID(principalUID)
</del><ins>+            principal = (yield principalCollection.principalForUID(principalUID))
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
-        return None
</del><ins>+                returnValue(principal)
+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1868,13 +1874,13 @@
</span><span class="cx">                     *[element.HRef(principal.principalURL()) for principal in results]
</span><span class="cx">                 ))
</span><span class="cx"> 
</span><del>-            elif name == &quot;auto-schedule&quot; and self.calendarsEnabled():
-                autoSchedule = self.getAutoSchedule()
-                returnValue(customxml.AutoSchedule(&quot;true&quot; if autoSchedule else &quot;false&quot;))
</del><ins>+            # elif name == &quot;auto-schedule&quot; and self.calendarsEnabled():
+            #     autoSchedule = self.getAutoSchedule()
+            #     returnValue(customxml.AutoSchedule(&quot;true&quot; if autoSchedule else &quot;false&quot;))
</ins><span class="cx"> 
</span><span class="cx">             elif name == &quot;auto-schedule-mode&quot; and self.calendarsEnabled():
</span><del>-                autoScheduleMode = self.getAutoScheduleMode()
-                returnValue(customxml.AutoScheduleMode(autoScheduleMode if autoScheduleMode else &quot;default&quot;))
</del><ins>+                autoScheduleMode = yield self.getAutoScheduleMode()
+                returnValue(customxml.AutoScheduleMode(autoScheduleMode.description if autoScheduleMode else &quot;default&quot;))
</ins><span class="cx"> 
</span><span class="cx">         elif namespace == carddav_namespace and self.addressBooksEnabled():
</span><span class="cx">             if name == &quot;addressbook-home-set&quot;:
</span><span class="lines">@@ -2302,20 +2308,23 @@
</span><span class="cx">     # ACL
</span><span class="cx">     ##
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def owner(self, request):
</span><del>-        return succeed(element.HRef(self.principalForRecord().principalURL()))
</del><ins>+        principal = yield self.principalForRecord()
+        returnValue(element.HRef(principal.principalURL()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def ownerPrincipal(self, request):
</span><del>-        return succeed(self.principalForRecord())
</del><ins>+        return self.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def resourceOwnerPrincipal(self, request):
</span><del>-        return succeed(self.principalForRecord())
</del><ins>+        return self.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def defaultAccessControlList(self):
</span><del>-        myPrincipal = self.principalForRecord()
</del><ins>+        myPrincipal = yield self.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx">         # Server may be read only
</span><span class="cx">         if config.EnableReadOnlyServer:
</span><span class="lines">@@ -2342,12 +2351,12 @@
</span><span class="cx">         # Give all access to config.AdminPrincipals
</span><span class="cx">         aces += config.AdminACEs
</span><span class="cx"> 
</span><del>-        return element.ACL(*aces)
</del><ins>+        returnValue(element.ACL(*aces))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
</span><span class="cx">         # Permissions here are fixed, and are not subject to inheritance rules, etc.
</span><del>-        return succeed(self.defaultAccessControlList())
</del><ins>+        return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def principalCollections(self):
</span><span class="lines">@@ -2555,9 +2564,10 @@
</span><span class="cx">         return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _otherPrincipalHomeURL(self, otherUID):
</span><del>-        ownerPrincipal = self.principalForUID(otherUID)
-        return ownerPrincipal.calendarHomeURLs()[0]
</del><ins>+        ownerPrincipal = (yield self.principalForUID(otherUID))
+        returnValue(ownerPrincipal.calendarHomeURLs()[0])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2584,8 +2594,9 @@
</span><span class="cx">         return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, mode)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def defaultAccessControlList(self):
</span><del>-        myPrincipal = self.principalForRecord()
</del><ins>+        myPrincipal = yield self.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx">         # Server may be read only
</span><span class="cx">         if config.EnableReadOnlyServer:
</span><span class="lines">@@ -2652,7 +2663,7 @@
</span><span class="cx">                 ),
</span><span class="cx">             )
</span><span class="cx"> 
</span><del>-        return element.ACL(*aces)
</del><ins>+        returnValue(element.ACL(*aces))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2808,9 +2819,10 @@
</span><span class="cx">         return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _otherPrincipalHomeURL(self, otherUID):
</span><del>-        ownerPrincipal = self.principalForUID(otherUID)
-        return ownerPrincipal.addressBookHomeURLs()[0]
</del><ins>+        ownerPrincipal = (yield self.principalForUID(otherUID))
+        returnValue(ownerPrincipal.addressBookHomeURLs()[0])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavscheduling_storecaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/scheduling_store/caldav/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/scheduling_store/caldav/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/scheduling_store/caldav/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -373,12 +373,14 @@
</span><span class="cx">         if config.Scheduling.CalDAV.OldDraftCompatibility:
</span><span class="cx">             privs += (davxml.Privilege(caldavxml.Schedule()),)
</span><span class="cx"> 
</span><del>-        return davxml.ACL(
-            # CalDAV:schedule-deliver for any authenticated user
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(*privs),
-            ),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # CalDAV:schedule-deliver for any authenticated user
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(*privs),
+                ),
+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -426,7 +428,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._associatedTransaction._authz_uid = authz.record.guid
</del><ins>+                self._associatedTransaction._authz_uid = authz.record.uid
</ins><span class="cx"> 
</span><span class="cx">         # Log extended item
</span><span class="cx">         if not hasattr(request, &quot;extendedLogItems&quot;):
</span><span class="lines">@@ -532,9 +534,10 @@
</span><span class="cx">         return succeed(sendSchedulePrivilegeSet)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def defaultAccessControlList(self):
</span><span class="cx">         if config.EnableProxyPrincipals:
</span><del>-            myPrincipal = self.parent.principalForRecord()
</del><ins>+            myPrincipal = yield self.parent.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx">             privs = (
</span><span class="cx">                 davxml.Privilege(caldavxml.ScheduleSend()),
</span><span class="lines">@@ -542,16 +545,18 @@
</span><span class="cx">             if config.Scheduling.CalDAV.OldDraftCompatibility:
</span><span class="cx">                 privs += (davxml.Privilege(caldavxml.Schedule()),)
</span><span class="cx"> 
</span><del>-            return davxml.ACL(
-                # CalDAV:schedule for associated write proxies
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), &quot;calendar-proxy-write&quot;))),
-                    davxml.Grant(*privs),
-                    davxml.Protected(),
-                ),
</del><ins>+            returnValue(
+                davxml.ACL(
+                    # CalDAV:schedule for associated write proxies
+                    davxml.ACE(
+                        davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), &quot;calendar-proxy-write&quot;))),
+                        davxml.Grant(*privs),
+                        davxml.Protected(),
+                    ),
+                )
</ins><span class="cx">             )
</span><span class="cx">         else:
</span><del>-            return super(ScheduleOutboxResource, self).defaultAccessControlList()
</del><ins>+            returnValue(super(ScheduleOutboxResource, self).defaultAccessControlList())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavsharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/sharing.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/sharing.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/sharing.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -44,7 +44,7 @@
</span><span class="cx"> from twistedcaldav import customxml, caldavxml
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><del>-from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
</del><ins>+from txdav.who.wiki import RecordType as WikiRecordType, WikiAccessLevel
</ins><span class="cx"> from twistedcaldav.linkresource import LinkFollowerMixIn
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -62,24 +62,25 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if config.Sharing.Enabled:
</span><span class="cx"> 
</span><ins>+            @inlineCallbacks
</ins><span class="cx">             def invitePropertyElement(invitation, includeUID=True):
</span><span class="cx"> 
</span><span class="cx">                 userid = &quot;urn:uuid:&quot; + invitation.shareeUID
</span><del>-                principal = self.principalForUID(invitation.shareeUID)
</del><ins>+                principal = yield self.principalForUID(invitation.shareeUID)
</ins><span class="cx">                 cn = principal.displayName() if principal else invitation.shareeUID
</span><del>-                return customxml.InviteUser(
</del><ins>+                returnValue(customxml.InviteUser(
</ins><span class="cx">                     customxml.UID.fromString(invitation.uid) if includeUID else None,
</span><span class="cx">                     element.HRef.fromString(userid),
</span><span class="cx">                     customxml.CommonName.fromString(cn),
</span><span class="cx">                     customxml.InviteAccess(invitationBindModeToXMLMap[invitation.mode]()),
</span><span class="cx">                     invitationBindStatusToXMLMap[invitation.status](),
</span><del>-                )
</del><ins>+                ))
</ins><span class="cx"> 
</span><span class="cx">             # See if this property is on the shared calendar
</span><span class="cx">             if self.isShared():
</span><span class="cx">                 invitations = yield self.validateInvites(request)
</span><span class="cx">                 returnValue(customxml.Invite(
</span><del>-                    *[invitePropertyElement(invitation) for invitation in invitations]
</del><ins>+                    *[(yield invitePropertyElement(invitation)) for invitation in invitations]
</ins><span class="cx">                 ))
</span><span class="cx"> 
</span><span class="cx">             # See if it is on the sharee calendar
</span><span class="lines">@@ -89,7 +90,7 @@
</span><span class="cx">                     invitations = yield original.allInvitations()
</span><span class="cx">                     invitations = yield self.validateInvites(request, invitations)
</span><span class="cx"> 
</span><del>-                    ownerPrincipal = self.principalForUID(self._newStoreObject.ownerHome().uid())
</del><ins>+                    ownerPrincipal = yield self.principalForUID(self._newStoreObject.ownerHome().uid())
</ins><span class="cx">                     # FIXME:  use urn:uuid in all cases
</span><span class="cx">                     if self.isCalendarCollection():
</span><span class="cx">                         owner = ownerPrincipal.principalURL()
</span><span class="lines">@@ -102,7 +103,7 @@
</span><span class="cx">                             element.HRef.fromString(owner),
</span><span class="cx">                             customxml.CommonName.fromString(ownerCN),
</span><span class="cx">                         ),
</span><del>-                        *[invitePropertyElement(invitation, includeUID=False) for invitation in invitations]
</del><ins>+                        *[(yield invitePropertyElement(invitation, includeUID=False)) for invitation in invitations]
</ins><span class="cx">                     ))
</span><span class="cx"> 
</span><span class="cx">         returnValue(None)
</span><span class="lines">@@ -266,17 +267,15 @@
</span><span class="cx">             any access at all.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if self._newStoreObject.direct():
</span><del>-            owner = self.principalForUID(self._newStoreObject.ownerHome().uid())
-            sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
-            if owner.record.recordType == WikiDirectoryService.recordType_wikis:
</del><ins>+            owner = yield self.principalForUID(self._newStoreObject.ownerHome().uid())
+            sharee = yield self.principalForUID(self._newStoreObject.viewerHome().uid())
+            if owner.record.recordType == WikiRecordType.macOSXServerWiki:
</ins><span class="cx">                 # Access level comes from what the wiki has granted to the
</span><span class="cx">                 # sharee
</span><del>-                userID = sharee.record.guid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
-                if access == &quot;read&quot;:
</del><ins>+                access = (yield owner.record.accessForRecord(sharee.record))
+                if access == WikiAccessLevel.read:
</ins><span class="cx">                     returnValue(&quot;read-only&quot;)
</span><del>-                elif access in (&quot;write&quot;, &quot;admin&quot;):
</del><ins>+                elif access == WikiAccessLevel.write:
</ins><span class="cx">                     returnValue(&quot;read-write&quot;)
</span><span class="cx">                 else:
</span><span class="cx">                     returnValue(None)
</span><span class="lines">@@ -320,7 +319,7 @@
</span><span class="cx">         assert self._isShareeResource, &quot;Only call this for a sharee resource&quot;
</span><span class="cx">         assert self.isCalendarCollection() or self.isAddressBookCollection(), &quot;Only call this for a address book or calendar resource&quot;
</span><span class="cx"> 
</span><del>-        sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
</del><ins>+        sharee = yield self.principalForUID(self._newStoreObject.viewerHome().uid())
</ins><span class="cx">         access = yield self._checkAccessControl()
</span><span class="cx"> 
</span><span class="cx">         if access == &quot;original&quot; and not self._newStoreObject.ownerHome().external():
</span><span class="lines">@@ -411,7 +410,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # First try to resolve as a principal
</span><del>-        principal = self.principalForCalendarUserAddress(userid)
</del><ins>+        principal = yield self.principalForCalendarUserAddress(userid)
</ins><span class="cx">         if principal:
</span><span class="cx">             if request:
</span><span class="cx">                 ownerPrincipal = (yield self.ownerPrincipal(request))
</span><span class="lines">@@ -501,10 +500,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def inviteSingleUserToShare(self, userid, cn, ace, summary, request): #@UnusedVariable
</del><ins>+    def inviteSingleUserToShare(self, userid, cn, ace, summary, request):  #@UnusedVariable
</ins><span class="cx"> 
</span><span class="cx">         # We currently only handle local users
</span><del>-        sharee = self.principalForCalendarUserAddress(userid)
</del><ins>+        sharee = yield self.principalForCalendarUserAddress(userid)
</ins><span class="cx">         if not sharee:
</span><span class="cx">             returnValue(False)
</span><span class="cx"> 
</span><span class="lines">@@ -521,7 +520,7 @@
</span><span class="cx">     def uninviteSingleUserFromShare(self, userid, aces, request): #@UnusedVariable
</span><span class="cx"> 
</span><span class="cx">         # Cancel invites - we'll just use whatever userid we are given
</span><del>-        sharee = self.principalForCalendarUserAddress(userid)
</del><ins>+        sharee = yield self.principalForCalendarUserAddress(userid)
</ins><span class="cx">         if not sharee:
</span><span class="cx">             returnValue(False)
</span><span class="cx"> 
</span><span class="lines">@@ -792,7 +791,7 @@
</span><span class="cx">         Set shared state and check access control.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if child._newStoreObject is not None and not child._newStoreObject.owned():
</span><del>-            ownerHomeURL = self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid())
</del><ins>+            ownerHomeURL = (yield self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid()))
</ins><span class="cx">             ownerView = yield child._newStoreObject.ownerView()
</span><span class="cx">             child.setShare(joinURL(ownerHomeURL, ownerView.name()))
</span><span class="cx">             access = yield child._checkAccessControl()
</span><span class="lines">@@ -802,6 +801,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _otherPrincipalHomeURL(self, otherUID):
</span><ins>+        # Is this only meant to be overridden?
</ins><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/stdconfig.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/stdconfig.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/stdconfig.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -374,10 +374,13 @@
</span><span class="cx">     #    users, groups, locations and resources) to the server.
</span><span class="cx">     #
</span><span class="cx">     &quot;DirectoryService&quot;: {
</span><ins>+        &quot;Enabled&quot;: True,
</ins><span class="cx">         &quot;type&quot;: &quot;twistedcaldav.directory.xmlfile.XMLDirectoryService&quot;,
</span><span class="cx">         &quot;params&quot;: DEFAULT_SERVICE_PARAMS[&quot;twistedcaldav.directory.xmlfile.XMLDirectoryService&quot;],
</span><span class="cx">     },
</span><span class="cx"> 
</span><ins>+    &quot;DirectoryRealmName&quot;: &quot;&quot;,
+
</ins><span class="cx">     #
</span><span class="cx">     # Locations and Resources service
</span><span class="cx">     #
</span><span class="lines">@@ -385,7 +388,7 @@
</span><span class="cx">     #    and resources.
</span><span class="cx">     #
</span><span class="cx">     &quot;ResourceService&quot;: {
</span><del>-        &quot;Enabled&quot; : True,
</del><ins>+        &quot;Enabled&quot;: True,
</ins><span class="cx">         &quot;type&quot;: &quot;twistedcaldav.directory.xmlfile.XMLDirectoryService&quot;,
</span><span class="cx">         &quot;params&quot;: DEFAULT_RESOURCE_PARAMS[&quot;twistedcaldav.directory.xmlfile.XMLDirectoryService&quot;],
</span><span class="cx">     },
</span><span class="lines">@@ -451,10 +454,6 @@
</span><span class="cx">         &quot;Wiki&quot;: {
</span><span class="cx">             &quot;Enabled&quot;: False,
</span><span class="cx">             &quot;Cookie&quot;: &quot;cc.collabd_session_guid&quot;,
</span><del>-            &quot;URL&quot;: &quot;http://127.0.0.1:8089/RPC2&quot;,
-            &quot;UserMethod&quot;: &quot;userForSession&quot;,
-            &quot;WikiMethod&quot;: &quot;accessLevelForUserWikiCalendar&quot;,
-            &quot;LionCompatibility&quot;: False,
</del><span class="cx">             &quot;CollabHost&quot;: &quot;localhost&quot;,
</span><span class="cx">             &quot;CollabPort&quot;: 4444,
</span><span class="cx">         },
</span><span class="lines">@@ -1016,8 +1015,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 class="lines">@@ -1254,8 +1251,18 @@
</span><span class="cx">             hostname = &quot;localhost&quot;
</span><span class="cx">         configDict.ServerHostName = hostname
</span><span class="cx"> 
</span><ins>+    # Default DirectoryRealmName from ServerHostName
+    if not configDict.DirectoryRealmName:
+        # Use system-wide realm on OSX
+        try:
+            import ServerFoundation
+            realmName = ServerFoundation.XSAuthenticator.defaultRealm()
+            configDict.DirectoryRealmName = realmName
+        except ImportError:
+            configDict.DirectoryRealmName = configDict.ServerHostName
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def _updateMultiProcess(configDict, reloading=False):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Dynamically compute ProcessCount if it's set to 0.  Always compute
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/storebridge.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/storebridge.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/storebridge.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,77 +15,89 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+import collections
+import hashlib
+import time
+from urlparse import urlsplit, urljoin
+import uuid
+
</ins><span class="cx"> from pycalendar.datetime import DateTime
</span><del>-
</del><span class="cx"> from twext.enterprise.locking import LockTimeout
</span><span class="cx"> from twext.python.log import Logger
</span><del>-from txweb2 import responsecode, http_headers, http
-from txweb2.dav.http import ErrorResponse, ResponseQueue, MultiStatusResponse
-from txweb2.dav.noneprops import NonePropertyStore
-from txweb2.dav.resource import TwistedACLInheritable, AccessDeniedError, \
-    davPrivilegeSet
-from txweb2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
-from txweb2.filter.location import addLocation
-from txweb2.http import HTTPError, StatusResponse, Response
-from txweb2.http_headers import ETag, MimeType, MimeDisposition
-from txweb2.iweb import IResponse
-from txweb2.responsecode import \
-    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED, \
-    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
-from txweb2.stream import ProducerStream, readStream, MemoryStream
-
</del><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
</span><span class="cx"> from twisted.internet.protocol import Protocol
</span><span class="cx"> from twisted.python.hashlib import md5
</span><span class="cx"> from twisted.python.util import FancyEqMixin
</span><del>-
</del><span class="cx"> from twistedcaldav import customxml, carddavxml, caldavxml, ical
</span><del>-from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance, \
-    MaxInstances, NoUIDConflict
</del><ins>+from twistedcaldav.caldavxml import (
+    caldav_namespace, MaxAttendeesPerInstance, MaxInstances, NoUIDConflict
+)
</ins><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NoUIDConflict as NovCardUIDConflict
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><del>-from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
-from twistedcaldav.ical import Component as VCalendar, Property as VProperty, \
-    InvalidICalendarDataError, iCalendarProductID, Component
-from twistedcaldav.instance import InvalidOverriddenInstanceError, \
-    TooManyInstancesError
</del><ins>+from twistedcaldav.ical import (
+    Component as VCalendar, Property as VProperty, InvalidICalendarDataError,
+    iCalendarProductID, Component
+)
+from twistedcaldav.instance import (
+    InvalidOverriddenInstanceError, TooManyInstancesError
+)
</ins><span class="cx"> from twistedcaldav.memcachelock import MemcacheLockTimeoutError
</span><span class="cx"> from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
</span><span class="cx"> from twistedcaldav.resource import CalDAVResource, DefaultAlarmPropertyMixin
</span><span class="cx"> from twistedcaldav.scheduling_store.caldav.resource import ScheduleInboxResource
</span><del>-from twistedcaldav.sharing import invitationBindStatusToXMLMap, \
-    invitationBindModeToXMLMap
</del><ins>+from twistedcaldav.sharing import (
+    invitationBindStatusToXMLMap, invitationBindModeToXMLMap
+)
</ins><span class="cx"> from twistedcaldav.util import bestAcceptType
</span><span class="cx"> from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
</span><del>-
</del><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><del>-from txdav.caldav.icalendarstore import QuotaExceeded, AttachmentStoreFailed, \
-    AttachmentStoreValidManagedID, AttachmentRemoveFailed, \
-    AttachmentDropboxNotAllowed, InvalidComponentTypeError, \
-    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError, \
-    InvalidPerUserDataMerge, \
-    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation, \
</del><ins>+from txdav.caldav.icalendarstore import (
+    QuotaExceeded, AttachmentStoreFailed,
+    AttachmentStoreValidManagedID, AttachmentRemoveFailed,
+    AttachmentDropboxNotAllowed, InvalidComponentTypeError,
+    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError,
+    InvalidPerUserDataMerge,
+    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation,
</ins><span class="cx">     ShareeAllowedError, DuplicatePrivateCommentsError, InvalidSplit
</span><del>-from txdav.carddav.iaddressbookstore import KindChangeNotAllowedError, \
-    GroupWithUnsharedAddressNotAllowedError
-from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
</del><ins>+)
+from txdav.carddav.iaddressbookstore import (
+    KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
+)
+from txdav.common.datastore.sql_tables import (
+    _BIND_MODE_READ, _BIND_MODE_WRITE,
</ins><span class="cx">     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
</span><del>-from txdav.common.icommondatastore import NoSuchObjectResourceError, \
-    TooManyObjectResourcesError, ObjectResourceTooBigError, \
-    InvalidObjectResourceError, ObjectResourceNameNotAllowedError, \
-    ObjectResourceNameAlreadyExistsError, UIDExistsError, \
-    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove, \
</del><ins>+)
+from txdav.common.icommondatastore import (
+    NoSuchObjectResourceError,
+    TooManyObjectResourcesError, ObjectResourceTooBigError,
+    InvalidObjectResourceError, ObjectResourceNameNotAllowedError,
+    ObjectResourceNameAlreadyExistsError, UIDExistsError,
+    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove,
</ins><span class="cx">     InvalidComponentForStoreError
</span><ins>+)
</ins><span class="cx"> from txdav.idav import PropertyChangeNotAllowedError
</span><ins>+from txdav.who.wiki import RecordType as WikiRecordType
</ins><span class="cx"> from txdav.xml import element as davxml, element
</span><span class="cx"> from txdav.xml.base import dav_namespace, WebDAVUnknownElement, encodeXMLName
</span><ins>+from txweb2 import responsecode, http_headers, http
+from txweb2.dav.http import ErrorResponse, ResponseQueue, MultiStatusResponse
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.resource import (
+    TwistedACLInheritable, AccessDeniedError, davPrivilegeSet
+)
+from txweb2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
+from txweb2.filter.location import addLocation
+from txweb2.http import HTTPError, StatusResponse, Response
+from txweb2.http_headers import ETag, MimeType, MimeDisposition
+from txweb2.iweb import IResponse
+from txweb2.responsecode import (
+    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
+    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
+)
+from txweb2.stream import ProducerStream, readStream, MemoryStream
</ins><span class="cx"> 
</span><del>-from urlparse import urlsplit, urljoin
-import collections
-import hashlib
-import time
-import uuid
</del><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
</span><span class="cx"> L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
</span><span class="lines">@@ -93,6 +105,7 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class _NewStorePropertiesWrapper(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Wrap a new-style property store (a L{txdav.idav.IPropertyStore}) in the old-
</span><span class="lines">@@ -1600,7 +1613,7 @@
</span><span class="cx">         cuas = (yield self._newStoreCalendarObject.component()).getAttendees()
</span><span class="cx">         newACEs = []
</span><span class="cx">         for calendarUserAddress in cuas:
</span><del>-            principal = self.principalForCalendarUserAddress(
</del><ins>+            principal = yield self.principalForCalendarUserAddress(
</ins><span class="cx">                 calendarUserAddress
</span><span class="cx">             )
</span><span class="cx">             if principal is None:
</span><span class="lines">@@ -1670,7 +1683,7 @@
</span><span class="cx">             proxyprivs = list(userprivs)
</span><span class="cx">             proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
</span><span class="cx"> 
</span><del>-            principal = self.principalForUID(invite.shareeUID)
</del><ins>+            principal = yield self.principalForUID(invite.shareeUID)
</ins><span class="cx">             aces += (
</span><span class="cx">                 # Inheritable specific access for the resource's associated principal.
</span><span class="cx">                 davxml.ACE(
</span><span class="lines">@@ -1763,11 +1776,12 @@
</span><span class="cx">         return succeed(davPrivilegeSet)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def defaultAccessControlList(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Only read privileges allowed for managed attachments.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        myPrincipal = self.parent.principalForRecord()
</del><ins>+        myPrincipal = yield self.parent.principalForRecord()
</ins><span class="cx"> 
</span><span class="cx">         read_privs = (
</span><span class="cx">             davxml.Privilege(davxml.Read()),
</span><span class="lines">@@ -1808,12 +1822,12 @@
</span><span class="cx">                 ),
</span><span class="cx">             )
</span><span class="cx"> 
</span><del>-        return davxml.ACL(*aces)
</del><ins>+        returnValue(davxml.ACL(*aces))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
</span><span class="cx">         # Permissions here are fixed, and are not subject to inheritance rules, etc.
</span><del>-        return succeed(self.defaultAccessControlList())
</del><ins>+        return self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1927,7 +1941,7 @@
</span><span class="cx">         cuas = (yield self._newStoreCalendarObject.component()).getAttendees()
</span><span class="cx">         newACEs = []
</span><span class="cx">         for calendarUserAddress in cuas:
</span><del>-            principal = self.principalForCalendarUserAddress(
</del><ins>+            principal = yield self.principalForCalendarUserAddress(
</ins><span class="cx">                 calendarUserAddress
</span><span class="cx">             )
</span><span class="cx">             if principal is None:
</span><span class="lines">@@ -1982,15 +1996,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if invite.mode in (_BIND_MODE_DIRECT,):
</span><span class="cx">             ownerUID = invite.ownerUID
</span><del>-            owner = self.principalForUID(ownerUID)
</del><ins>+            owner = yield self.principalForUID(ownerUID)
</ins><span class="cx">             shareeUID = invite.shareeUID
</span><del>-            if owner.record.recordType == WikiDirectoryService.recordType_wikis:
</del><ins>+            if owner.record.recordType == WikiRecordType.macOSXServerWiki:
</ins><span class="cx">                 # Access level comes from what the wiki has granted to the
</span><span class="cx">                 # sharee
</span><del>-                sharee = self.principalForUID(shareeUID)
-                userID = sharee.record.guid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
</del><ins>+                sharee = yield self.principalForUID(shareeUID)
+                access = (yield owner.record.accessForRecord(sharee.record))
</ins><span class="cx">                 if access == &quot;read&quot;:
</span><span class="cx">                     returnValue(&quot;read-only&quot;)
</span><span class="cx">                 elif access in (&quot;write&quot;, &quot;admin&quot;):
</span><span class="lines">@@ -2026,7 +2038,7 @@
</span><span class="cx">             if access in (&quot;read-only&quot;, &quot;read-write&quot;,):
</span><span class="cx">                 userprivs.extend(privileges)
</span><span class="cx"> 
</span><del>-            principal = self.principalForUID(invite.shareeUID)
</del><ins>+            principal = yield self.principalForUID(invite.shareeUID)
</ins><span class="cx">             aces += (
</span><span class="cx">                 # Inheritable specific access for the resource's associated principal.
</span><span class="cx">                 davxml.ACE(
</span><span class="lines">@@ -2865,7 +2877,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">@@ -3586,7 +3598,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 class="lines">@@ -3695,7 +3707,7 @@
</span><span class="cx">         else:
</span><span class="cx">             userprivs.append(davxml.Privilege(davxml.WriteProperties()))
</span><span class="cx"> 
</span><del>-        sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
</del><ins>+        sharee = yield self.principalForUID(self._newStoreObject.viewerHome().uid())
</ins><span class="cx">         aces = (
</span><span class="cx">             # Inheritable specific access for the resource's associated principal.
</span><span class="cx">             davxml.ACE(
</span><span class="lines">@@ -3897,7 +3909,7 @@
</span><span class="cx">         assert ignored is None, &quot;This is a notification object, not a notification&quot;
</span><span class="cx">         jsondata = (yield self._newStoreObject.notificationData())
</span><span class="cx">         if jsondata[&quot;notification-type&quot;] == &quot;invite-notification&quot;:
</span><del>-            ownerPrincipal = self.principalForUID(jsondata[&quot;owner&quot;])
</del><ins>+            ownerPrincipal = yield self.principalForUID(jsondata[&quot;owner&quot;])
</ins><span class="cx">             ownerCN = ownerPrincipal.displayName()
</span><span class="cx">             ownerHomeURL = ownerPrincipal.calendarHomeURLs()[0] if jsondata[&quot;shared-type&quot;] == &quot;calendar&quot; else ownerPrincipal.addressBookHomeURLs()[0]
</span><span class="cx"> 
</span><span class="lines">@@ -3907,7 +3919,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 owner = &quot;urn:uuid:&quot; + ownerPrincipal.principalUID()
</span><span class="cx"> 
</span><del>-            shareePrincipal = self.principalForUID(jsondata[&quot;sharee&quot;])
</del><ins>+            shareePrincipal = yield self.principalForUID(jsondata[&quot;sharee&quot;])
</ins><span class="cx"> 
</span><span class="cx">             if &quot;supported-components&quot; in jsondata:
</span><span class="cx">                 comps = jsondata[&quot;supported-components&quot;]
</span><span class="lines">@@ -3942,10 +3954,10 @@
</span><span class="cx">                 ),
</span><span class="cx">             )
</span><span class="cx">         elif jsondata[&quot;notification-type&quot;] == &quot;invite-reply&quot;:
</span><del>-            ownerPrincipal = self.principalForUID(jsondata[&quot;owner&quot;])
</del><ins>+            ownerPrincipal = yield self.principalForUID(jsondata[&quot;owner&quot;])
</ins><span class="cx">             ownerHomeURL = ownerPrincipal.calendarHomeURLs()[0] if jsondata[&quot;shared-type&quot;] == &quot;calendar&quot; else ownerPrincipal.addressBookHomeURLs()[0]
</span><span class="cx"> 
</span><del>-            shareePrincipal = self.principalForUID(jsondata[&quot;sharee&quot;])
</del><ins>+            shareePrincipal = yield self.principalForUID(jsondata[&quot;sharee&quot;])
</ins><span class="cx"> 
</span><span class="cx">             # FIXME:  use urn:uuid always?
</span><span class="cx">             if jsondata[&quot;shared-type&quot;] == &quot;calendar&quot;:
</span><span class="lines">@@ -3959,7 +3971,7 @@
</span><span class="cx">                 cua = &quot;urn:uuid:&quot; + shareePrincipal.principalUID()
</span><span class="cx"> 
</span><span class="cx">             commonName = shareePrincipal.displayName()
</span><del>-            record = shareePrincipal.record
</del><ins>+            # record = shareePrincipal.record
</ins><span class="cx"> 
</span><span class="cx">             typeAttr = {&quot;shared-type&quot;: jsondata[&quot;shared-type&quot;]}
</span><span class="cx">             xmldata = customxml.Notification(
</span><span class="lines">@@ -3973,9 +3985,9 @@
</span><span class="cx">                     customxml.InReplyTo.fromString(jsondata[&quot;in-reply-to&quot;]),
</span><span class="cx">                     customxml.InviteSummary.fromString(jsondata[&quot;summary&quot;]) if jsondata[&quot;summary&quot;] else None,
</span><span class="cx">                     customxml.CommonName.fromString(commonName) if commonName else None,
</span><del>-                    customxml.FirstNameProperty(record.firstName) if record.firstName else None,
-                    customxml.LastNameProperty(record.lastName) if record.lastName else None,
-                    #**typeAttr
</del><ins>+                    # customxml.FirstNameProperty(record.firstName) if record.firstName else None,
+                    # customxml.LastNameProperty(record.lastName) if record.lastName else None,
+                    **typeAttr
</ins><span class="cx">                 ),
</span><span class="cx">             )
</span><span class="cx">         else:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_addressbookmultigetpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookmultiget.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookmultiget.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookmultiget.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_addressbookquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookquery.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookquery.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_addressbookquery.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_cachepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_cache.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_cache.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_cache.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -63,6 +63,9 @@
</span><span class="cx"> 
</span><span class="cx"> class StubDirectory(object):
</span><span class="cx"> 
</span><ins>+    def oldNameToRecordType(self, oldName):
+        return oldName
+
</ins><span class="cx">     def recordWithShortName(self, recordType, recordName):
</span><span class="cx">         return StubDirectoryRecord(recordName)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_calendarquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_calendarquery.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_calendarquery.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_calendarquery.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_collectioncontentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_collectioncontents.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_collectioncontents.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_collectioncontents.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_configpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_config.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_config.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_config.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -92,9 +92,10 @@
</span><span class="cx"> 
</span><span class="cx">     def testDefaults(self):
</span><span class="cx">         for key, value in DEFAULT_CONFIG.iteritems():
</span><del>-            if key in (&quot;ServerHostName&quot;, &quot;Notifications&quot;, &quot;MultiProcess&quot;,
-                &quot;Postgres&quot;):
-                # Value is calculated and may vary
</del><ins>+            if key in (
+                &quot;ServerHostName&quot;, &quot;Notifications&quot;, &quot;MultiProcess&quot;,
+                &quot;Postgres&quot;, &quot;DirectoryRealmName&quot;
+            ):  # Value is calculated and may vary
</ins><span class="cx">                 continue
</span><span class="cx">             for item in RELATIVE_PATHS:
</span><span class="cx">                 item = item[1]
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_icalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_icalendar.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_icalendar.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_icalendar.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> import itertools
</span><span class="cx"> 
</span><span class="cx"> from twisted.trial.unittest import SkipTest
</span><ins>+from twisted.internet.defer import inlineCallbacks, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav.ical import Component, Property, InvalidICalendarDataError, \
</span><span class="cx">     normalizeCUAddress, normalize_iCalStr
</span><span class="lines">@@ -32,6 +33,8 @@
</span><span class="cx"> from twistedcaldav.dateops import normalizeForExpand
</span><span class="cx"> from pycalendar.value import Value
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class iCalendar (twistedcaldav.test.util.TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     iCalendar support tests
</span><span class="lines">@@ -7497,6 +7500,7 @@
</span><span class="cx">             self.assertEquals(expected, ical.hasInstancesAfter(cutoff))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCalendarUserAddressesFromUUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure mailto is preferred, followed by path form, then http form.
</span><span class="lines">@@ -7520,25 +7524,27 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;urn:uuid:foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;, &quot;http://example.com/foo&quot;, &quot;/foo&quot;)
-                ),
-                &quot;urn:uuid:bar&quot; : (
-                    &quot;Bar&quot;,
-                    &quot;bar&quot;,
-                    (&quot;urn:uuid:bar&quot;, &quot;mailto:bar@example.com&quot;, &quot;http://example.com/bar&quot;, &quot;/bar&quot;)
-                ),
-                &quot;urn:uuid:baz&quot; : (
-                    &quot;Baz&quot;,
-                    &quot;baz&quot;,
-                    (&quot;urn:uuid:baz&quot;, &quot;http://example.com/baz&quot;)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;urn:uuid:foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;, &quot;http://example.com/foo&quot;, &quot;/foo&quot;)
+                    ),
+                    &quot;urn:uuid:bar&quot; : (
+                        &quot;Bar&quot;,
+                        &quot;bar&quot;,
+                        (&quot;urn:uuid:bar&quot;, &quot;mailto:bar@example.com&quot;, &quot;http://example.com/bar&quot;, &quot;/bar&quot;)
+                    ),
+                    &quot;urn:uuid:baz&quot; : (
+                        &quot;Baz&quot;,
+                        &quot;baz&quot;,
+                        (&quot;urn:uuid:baz&quot;, &quot;http://example.com/baz&quot;)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><del>-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
</del><ins>+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
</ins><span class="cx"> 
</span><span class="cx">         self.assertEquals(&quot;mailto:bar@example.com&quot;,
</span><span class="cx">             component.getAttendeeProperty((&quot;mailto:bar@example.com&quot;,)).value())
</span><span class="lines">@@ -7548,6 +7554,7 @@
</span><span class="cx">             component.getAttendeeProperty((&quot;http://example.com/baz&quot;,)).value())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCalendarUserAddressesAndLocationChange(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure http(s) and /path CUA values are tucked away into the property
</span><span class="lines">@@ -7573,25 +7580,27 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;/principals/users/foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;,)
-                ),
-                &quot;http://example.com/principals/users/bar&quot; : (
-                    &quot;Bar&quot;,
-                    &quot;bar&quot;,
-                    (&quot;urn:uuid:bar&quot;,)
-                ),
-                &quot;http://example.com/principals/locations/buzz&quot; : (
-                    &quot;{Restricted} Buzz&quot;,
-                    &quot;buzz&quot;,
-                    (&quot;urn:uuid:buzz&quot;,)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;/principals/users/foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;,)
+                    ),
+                    &quot;http://example.com/principals/users/bar&quot; : (
+                        &quot;Bar&quot;,
+                        &quot;bar&quot;,
+                        (&quot;urn:uuid:bar&quot;,)
+                    ),
+                    &quot;http://example.com/principals/locations/buzz&quot; : (
+                        &quot;{Restricted} Buzz&quot;,
+                        &quot;buzz&quot;,
+                        (&quot;urn:uuid:buzz&quot;,)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><del>-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</del><ins>+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</ins><span class="cx"> 
</span><span class="cx">         # Location value changed
</span><span class="cx">         prop = component.mainComponent().getProperty(&quot;LOCATION&quot;)
</span><span class="lines">@@ -7601,6 +7610,7 @@
</span><span class="cx">         self.assertEquals(prop.parameterValue(&quot;CN&quot;), &quot;{Restricted} Buzz&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCalendarUserAddressesAndLocationNoChange(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure http(s) and /path CUA values are tucked away into the property
</span><span class="lines">@@ -7626,25 +7636,27 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;/principals/users/foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;,)
-                ),
-                &quot;http://example.com/principals/users/bar&quot; : (
-                    &quot;Bar&quot;,
-                    &quot;bar&quot;,
-                    (&quot;urn:uuid:bar&quot;,)
-                ),
-                &quot;http://example.com/principals/locations/buzz&quot; : (
-                    &quot;{Restricted} Buzz&quot;,
-                    &quot;buzz&quot;,
-                    (&quot;urn:uuid:buzz&quot;,)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;/principals/users/foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;,)
+                    ),
+                    &quot;http://example.com/principals/users/bar&quot; : (
+                        &quot;Bar&quot;,
+                        &quot;bar&quot;,
+                        (&quot;urn:uuid:bar&quot;,)
+                    ),
+                    &quot;http://example.com/principals/locations/buzz&quot; : (
+                        &quot;{Restricted} Buzz&quot;,
+                        &quot;buzz&quot;,
+                        (&quot;urn:uuid:buzz&quot;,)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><del>-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</del><ins>+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</ins><span class="cx"> 
</span><span class="cx">         # Location value changed
</span><span class="cx">         prop = component.mainComponent().getProperty(&quot;LOCATION&quot;)
</span><span class="lines">@@ -7654,6 +7666,7 @@
</span><span class="cx">         self.assertEquals(prop.parameterValue(&quot;CN&quot;), &quot;{Restricted} Buzz&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCalendarUserAddressesAndLocationNoChangeOtherCUType(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure http(s) and /path CUA values are tucked away into the property
</span><span class="lines">@@ -7679,25 +7692,27 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;/principals/users/foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;,)
-                ),
-                &quot;http://example.com/principals/users/bar&quot; : (
-                    &quot;Bar&quot;,
-                    &quot;bar&quot;,
-                    (&quot;urn:uuid:bar&quot;,)
-                ),
-                &quot;http://example.com/principals/locations/buzz&quot; : (
-                    &quot;{Restricted} Buzz&quot;,
-                    &quot;buzz&quot;,
-                    (&quot;urn:uuid:buzz&quot;,)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;/principals/users/foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;,)
+                    ),
+                    &quot;http://example.com/principals/users/bar&quot; : (
+                        &quot;Bar&quot;,
+                        &quot;bar&quot;,
+                        (&quot;urn:uuid:bar&quot;,)
+                    ),
+                    &quot;http://example.com/principals/locations/buzz&quot; : (
+                        &quot;{Restricted} Buzz&quot;,
+                        &quot;buzz&quot;,
+                        (&quot;urn:uuid:buzz&quot;,)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><del>-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</del><ins>+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
</ins><span class="cx"> 
</span><span class="cx">         # Location value changed
</span><span class="cx">         prop = component.mainComponent().getProperty(&quot;LOCATION&quot;)
</span><span class="lines">@@ -8404,6 +8419,7 @@
</span><span class="cx">             self.assertEqual(changed, result_changed)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCUAddressFromUUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure mailto is preferred, followed by path form, then http form.
</span><span class="lines">@@ -8418,34 +8434,37 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;urn:uuid:foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;, &quot;http://example.com/foo&quot;, &quot;/foo&quot;)
-                ),
-                &quot;urn:uuid:bar&quot; : (
-                    &quot;Bar&quot;,
-                    &quot;bar&quot;,
-                    (&quot;urn:uuid:bar&quot;, &quot;mailto:bar@example.com&quot;, &quot;http://example.com/bar&quot;, &quot;/bar&quot;)
-                ),
-                &quot;urn:uuid:baz&quot; : (
-                    &quot;Baz&quot;,
-                    &quot;baz&quot;,
-                    (&quot;urn:uuid:baz&quot;, &quot;http://example.com/baz&quot;)
-                ),
-                &quot;urn:uuid:buz&quot; : (
-                    &quot;Buz&quot;,
-                    &quot;buz&quot;,
-                    (&quot;urn:uuid:buz&quot;,)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;urn:uuid:foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;, &quot;http://example.com/foo&quot;, &quot;/foo&quot;)
+                    ),
+                    &quot;urn:uuid:bar&quot; : (
+                        &quot;Bar&quot;,
+                        &quot;bar&quot;,
+                        (&quot;urn:uuid:bar&quot;, &quot;mailto:bar@example.com&quot;, &quot;http://example.com/bar&quot;, &quot;/bar&quot;)
+                    ),
+                    &quot;urn:uuid:baz&quot; : (
+                        &quot;Baz&quot;,
+                        &quot;baz&quot;,
+                        (&quot;urn:uuid:baz&quot;, &quot;http://example.com/baz&quot;)
+                    ),
+                    &quot;urn:uuid:buz&quot; : (
+                        &quot;Buz&quot;,
+                        &quot;buz&quot;,
+                        (&quot;urn:uuid:buz&quot;,)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><span class="cx">         for cuaddr, result in data:
</span><del>-            new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
</del><ins>+            new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
</ins><span class="cx">             self.assertEquals(new_cuaddr, result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCUAddressToUUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure http(s) and /path CUA values are tucked away into the property
</span><span class="lines">@@ -8459,21 +8478,23 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">         def lookupFunction(cuaddr, ignored1, ignored2):
</span><del>-            return {
-                &quot;/principals/users/foo&quot; : (
-                    &quot;Foo&quot;,
-                    &quot;foo&quot;,
-                    (&quot;urn:uuid:foo&quot;,)
-                ),
-                &quot;http://example.com/principals/users/buz&quot; : (
-                    &quot;Buz&quot;,
-                    &quot;buz&quot;,
-                    (&quot;urn:uuid:buz&quot;,)
-                ),
-            }[cuaddr]
</del><ins>+            return succeed(
+                {
+                    &quot;/principals/users/foo&quot; : (
+                        &quot;Foo&quot;,
+                        &quot;foo&quot;,
+                        (&quot;urn:uuid:foo&quot;,)
+                    ),
+                    &quot;http://example.com/principals/users/buz&quot; : (
+                        &quot;Buz&quot;,
+                        &quot;buz&quot;,
+                        (&quot;urn:uuid:buz&quot;,)
+                    ),
+                }[cuaddr]
+            )
</ins><span class="cx"> 
</span><span class="cx">         for cuaddr, result in data:
</span><del>-            new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
</del><ins>+            new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
</ins><span class="cx">             self.assertEquals(new_cuaddr, result)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_mkcalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_mkcalendar.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_mkcalendar.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_mkcalendar.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_multigetpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_multiget.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_multiget.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_multiget.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_propspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_props.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_props.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_props.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_resourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_sharing.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_sharing.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_sharing.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,30 +19,31 @@
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.iweb import IResponse
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav import customxml
</span><del>-from twistedcaldav import sharing
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
</span><del>-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.sharing import WikiDirectoryService
</del><span class="cx"> from twistedcaldav.test.test_cache import StubResponseCacheResource
</span><span class="cx"> from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><del>-from txdav.caldav.datastore.test.util import buildDirectory
</del><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><span class="cx"> 
</span><span class="cx"> from xml.etree.cElementTree import XML
</span><ins>+from txdav.who.wiki import (
+    DirectoryRecord as WikiDirectoryRecord,
+    DirectoryService as WikiDirectoryService,
+    RecordType as WikiRecordType,
+    WikiAccessLevel
+)
</ins><span class="cx"> 
</span><ins>+sharedOwnerType = davxml.ResourceType.sharedownercalendar  # @UndefinedVariable
+regularCalendarType = davxml.ResourceType.calendar  # @UndefinedVariable
</ins><span class="cx"> 
</span><del>-sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
-regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> def normalize(x):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Normalize some XML by parsing it, collapsing whitespace, and
</span><span class="lines">@@ -63,8 +64,8 @@
</span><span class="cx">         self.fullName = name
</span><span class="cx">         self.guid = name
</span><span class="cx">         self.calendarUserAddresses = set((cuaddr,))
</span><del>-        if name.startswith(&quot;wiki-&quot;):
-            recordType = WikiDirectoryService.recordType_wikis
</del><ins>+        if name.startswith(WikiDirectoryService.uidPrefix):
+            recordType = WikiRecordType.macOSXServerWiki
</ins><span class="cx">         else:
</span><span class="cx">             recordType = None
</span><span class="cx">         self.recordType = recordType
</span><span class="lines">@@ -131,42 +132,45 @@
</span><span class="cx">         super(SharingTests, self).configure()
</span><span class="cx">         self.patch(config.Sharing, &quot;Enabled&quot;, True)
</span><span class="cx">         self.patch(config.Sharing.Calendars, &quot;Enabled&quot;, True)
</span><ins>+        self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(SharingTests, self).setUp()
</span><span class="cx"> 
</span><del>-        def patched(c):
-            &quot;&quot;&quot;
-            The decorated method is patched on L{CalDAVResource} for the
-            duration of the test.
-            &quot;&quot;&quot;
-            self.patch(CalDAVResource, c.__name__, c)
-            return c
</del><ins>+        # FIXME: not sure what these were for:
</ins><span class="cx"> 
</span><del>-        @patched
-        def principalForCalendarUserAddress(resourceSelf, cuaddr):
-            if &quot;bogus&quot; in cuaddr:
-                return None
-            else:
-                return FakePrincipal(cuaddr, self)
</del><ins>+        #     def patched(c):
+        #         &quot;&quot;&quot;
+        #         The decorated method is patched on L{CalDAVResource} for the
+        #         duration of the test.
+        #         &quot;&quot;&quot;
+        #         self.patch(CalDAVResource, c.__name__, c)
+        #         return c
</ins><span class="cx"> 
</span><del>-        @patched
-        def validUserIDForShare(resourceSelf, userid, request):
-            &quot;&quot;&quot;
-            Temporary replacement for L{CalDAVResource.validUserIDForShare}
-            that marks any principal without 'bogus' in its name.
-            &quot;&quot;&quot;
-            result = principalForCalendarUserAddress(resourceSelf, userid)
-            if result is None:
-                return result
-            return result.principalURL()
</del><ins>+        #     @patched
+        #     def principalForCalendarUserAddress(resourceSelf, cuaddr):
+        #         if &quot;bogus&quot; in cuaddr:
+        #             return None
+        #         else:
+        #             return FakePrincipal(cuaddr, self)
</ins><span class="cx"> 
</span><del>-        @patched
-        def principalForUID(resourceSelf, principalUID):
-            return FakePrincipal(&quot;urn:uuid:&quot; + principalUID, self)
</del><ins>+        #     @patched
+        #     def validUserIDForShare(resourceSelf, userid, request):
+        #         &quot;&quot;&quot;
+        #         Temporary replacement for L{CalDAVResource.validUserIDForShare}
+        #         that marks any principal without 'bogus' in its name.
+        #         &quot;&quot;&quot;
+        #         result = principalForCalendarUserAddress(resourceSelf, userid)
+        #         if result is None:
+        #             return result
+        #         return result.principalURL()
</ins><span class="cx"> 
</span><ins>+        #     @patched
+        #     def principalForUID(resourceSelf, principalUID):
+        #         return FakePrincipal(&quot;urn:uuid:&quot; + principalUID, self)
+
</ins><span class="cx">         self.resource = yield self._getResource()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -185,7 +189,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 +215,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">@@ -319,7 +325,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -349,7 +355,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -398,7 +404,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -473,21 +479,21 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user03&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER03&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 03&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user04&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER04&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 04&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -531,14 +537,14 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user04&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER04&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 04&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -582,14 +588,14 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user03&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER03&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 03&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -699,7 +705,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -735,23 +741,23 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def wikiSetup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Create a wiki called C{wiki-testing}, and share it with the user whose
</del><ins>+        Create a wiki called C{[wiki]testing}, and share it with the user whose
</ins><span class="cx">         home is at /.  Return the name of the newly shared calendar in the
</span><span class="cx">         sharee's home.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self._sqlCalendarStore._directoryService = buildDirectory(homes=(&quot;wiki-testing&quot;,))
</del><span class="cx">         wcreate = self._sqlCalendarStore.newTransaction(&quot;create wiki&quot;)
</span><del>-        yield wcreate.calendarHomeWithUID(&quot;wiki-testing&quot;, create=True)
</del><ins>+        yield wcreate.calendarHomeWithUID(
+            u&quot;{prefix}testing&quot;.format(prefix=WikiDirectoryService.uidPrefix),
+            create=True
+        )
</ins><span class="cx">         yield wcreate.commit()
</span><span class="cx"> 
</span><del>-        newService = WikiDirectoryService()
-        newService.realmName = self.directory.realmName
-        self.directory.addService(newService)
-
</del><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        sharee = yield self.homeUnderTest(name=&quot;user01&quot;)
-        sharer = yield txn.calendarHomeWithUID(&quot;wiki-testing&quot;)
</del><ins>+        sharee = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
+        sharer = yield txn.calendarHomeWithUID(
+            u&quot;{prefix}testing&quot;.format(prefix=WikiDirectoryService.uidPrefix),
+        )
</ins><span class="cx">         cal = yield sharer.calendarWithName(&quot;calendar&quot;)
</span><span class="cx">         sharedName = yield cal.shareWith(sharee, _BIND_MODE_DIRECT)
</span><span class="cx">         returnValue(sharedName)
</span><span class="lines">@@ -764,13 +770,14 @@
</span><span class="cx">         to the sharee, so that delegates of the sharee get the same level of
</span><span class="cx">         access.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        sharedName = yield self.wikiSetup()
+        access = WikiAccessLevel.read
</ins><span class="cx"> 
</span><del>-        access = &quot;read&quot;
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, &quot;getWikiAccess&quot;, stubWikiAccessMethod)
</del><ins>+        def stubAccessForRecord(*args):
+            return succeed(access)
</ins><span class="cx"> 
</span><del>-        sharedName = yield self.wikiSetup()
</del><ins>+        self.patch(WikiDirectoryRecord, &quot;accessForRecord&quot;, stubAccessForRecord)
+
</ins><span class="cx">         request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/calendars/__uids__/user01/&quot;)
</span><span class="cx">         collection = yield request.locateResource(&quot;/calendars/__uids__/user01/&quot; + sharedName)
</span><span class="cx"> 
</span><span class="lines">@@ -779,7 +786,7 @@
</span><span class="cx">         self.assertFalse(&quot;&lt;write/&gt;&quot; in acl.toxml())
</span><span class="cx"> 
</span><span class="cx">         # Simulate the wiki server granting Read-Write access
</span><del>-        access = &quot;write&quot;
</del><ins>+        access = WikiAccessLevel.write
</ins><span class="cx">         acl = (yield collection.shareeAccessControlList(request))
</span><span class="cx">         self.assertTrue(&quot;&lt;write/&gt;&quot; in acl.toxml())
</span><span class="cx"> 
</span><span class="lines">@@ -792,13 +799,17 @@
</span><span class="cx">         un-share that collection.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         sharedName = yield self.wikiSetup()
</span><del>-        access = &quot;write&quot;
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, &quot;getWikiAccess&quot;, stubWikiAccessMethod)
</del><ins>+        access = WikiAccessLevel.write
+
+        def stubAccessForRecord(*args):
+            return succeed(access)
+
+        self.patch(WikiDirectoryRecord, &quot;accessForRecord&quot;, stubAccessForRecord)
+
</ins><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 class="lines">@@ -810,9 +821,10 @@
</span><span class="cx">             seq.remove(shortest)
</span><span class="cx">             filtered = [elem[len(shortest):].rstrip(&quot;/&quot;) for elem in seq]
</span><span class="cx">             returnValue(filtered)
</span><ins>+
</ins><span class="cx">         childNames = yield listChildrenViaPropfind()
</span><span class="cx">         self.assertIn(sharedName, childNames)
</span><del>-        access = &quot;no-access&quot;
</del><ins>+        access = WikiAccessLevel.none
</ins><span class="cx">         childNames = yield listChildrenViaPropfind()
</span><span class="cx">         self.assertNotIn(sharedName, childNames)
</span><span class="cx"> 
</span><span class="lines">@@ -837,7 +849,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -867,7 +879,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -911,7 +923,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_upgradepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_upgrade.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_upgrade.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_upgrade.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -20,21 +20,22 @@
</span><span class="cx"> import cPickle
</span><span class="cx"> 
</span><span class="cx"> from twisted.python.reflect import namedClass
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><ins>+from twisted.internet.defer import inlineCallbacks, succeed
</ins><span class="cx"> 
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><span class="cx"> from txdav.caldav.datastore.index_file import db_basename
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><del>-from twistedcaldav.directory.xmlfile import XMLDirectoryService
</del><span class="cx"> from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.mailgateway import MailGatewayTokensDatabase
</span><span class="cx"> from twistedcaldav.upgrade import (
</span><span class="cx">     xattrname, upgradeData, updateFreeBusySet,
</span><del>-    removeIllegalCharacters, normalizeCUAddrs
</del><ins>+    removeIllegalCharacters, normalizeCUAddrs,
+    loadDelegatesFromXML, migrateDelegatesToStore
</ins><span class="cx"> )
</span><del>-from twistedcaldav.test.util import TestCase
-from calendarserver.tools.util import getDirectory
</del><ins>+from twistedcaldav.test.util import StoreTestCase
+from txdav.who.delegates import delegatesOf
+from twistedcaldav.directory.calendaruserproxy import ProxySqliteDB
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -51,33 +52,18 @@
</span><span class="cx"> OLDPROXYFILE = &quot;.db.calendaruserproxy&quot;
</span><span class="cx"> NEWPROXYFILE = &quot;proxies.sqlite&quot;
</span><span class="cx"> 
</span><del>-class UpgradeTests(TestCase):
</del><span class="cx"> 
</span><ins>+class UpgradeTests(StoreTestCase):
</ins><span class="cx"> 
</span><del>-    def setUpXMLDirectory(self):
-        xmlFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
-            &quot;directory&quot;, &quot;test&quot;, &quot;accounts.xml&quot;)
-        config.DirectoryService.params.xmlFile = xmlFile
</del><span class="cx"> 
</span><del>-        xmlAugmentsFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
-            &quot;directory&quot;, &quot;test&quot;, &quot;augments.xml&quot;)
-        config.AugmentService.type = &quot;twistedcaldav.directory.augment.AugmentXMLDB&quot;
-        config.AugmentService.params.xmlFiles = (xmlAugmentsFile,)
-
-        resourceFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
-            &quot;directory&quot;, &quot;test&quot;, &quot;resources.xml&quot;)
-        config.ResourceService.params.xmlFile = resourceFile
-
-
</del><span class="cx">     def doUpgrade(self, config):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Perform the actual upgrade.  (Hook for parallel tests.)
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return upgradeData(config)
</del><ins>+        return upgradeData(config, self.directory)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def setUpInitialStates(self):
</span><del>-        self.setUpXMLDirectory()
</del><span class="cx"> 
</span><span class="cx">         self.setUpOldDocRoot()
</span><span class="cx">         self.setUpOldDocRootWithoutDB()
</span><span class="lines">@@ -202,7 +188,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.setUpInitialStates()
</span><del>-        directory = getDirectory()
</del><ins>+        directory = self.directory
</ins><span class="cx"> 
</span><span class="cx">         #
</span><span class="cx">         # Verify these values require no updating:
</span><span class="lines">@@ -210,18 +196,18 @@
</span><span class="cx"> 
</span><span class="cx">         # Uncompressed XML
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><del>-        self.assertEquals(updateFreeBusySet(value, directory), None)
</del><ins>+        self.assertEquals((yield updateFreeBusySet(value, directory)), None)
</ins><span class="cx"> 
</span><span class="cx">         # Zlib compressed XML
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><span class="cx">         value = zlib.compress(value)
</span><del>-        self.assertEquals(updateFreeBusySet(value, directory), None)
</del><ins>+        self.assertEquals((yield updateFreeBusySet(value, directory)), None)
</ins><span class="cx"> 
</span><span class="cx">         # Pickled XML
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><span class="cx">         doc = WebDAVDocument.fromString(value)
</span><span class="cx">         value = cPickle.dumps(doc.root_element)
</span><del>-        self.assertEquals(updateFreeBusySet(value, directory), None)
</del><ins>+        self.assertEquals((yield updateFreeBusySet(value, directory)), None)
</ins><span class="cx"> 
</span><span class="cx">         #
</span><span class="cx">         # Verify these values do require updating:
</span><span class="lines">@@ -230,14 +216,14 @@
</span><span class="cx"> 
</span><span class="cx">         # Uncompressed XML
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/users/wsanchez/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><del>-        newValue = updateFreeBusySet(value, directory)
</del><ins>+        newValue = yield updateFreeBusySet(value, directory)
</ins><span class="cx">         newValue = zlib.decompress(newValue)
</span><span class="cx">         self.assertEquals(newValue, expected)
</span><span class="cx"> 
</span><span class="cx">         # Zlib compressed XML
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/users/wsanchez/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><span class="cx">         value = zlib.compress(value)
</span><del>-        newValue = updateFreeBusySet(value, directory)
</del><ins>+        newValue = yield updateFreeBusySet(value, directory)
</ins><span class="cx">         newValue = zlib.decompress(newValue)
</span><span class="cx">         self.assertEquals(newValue, expected)
</span><span class="cx"> 
</span><span class="lines">@@ -245,7 +231,7 @@
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/users/wsanchez/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><span class="cx">         doc = WebDAVDocument.fromString(value)
</span><span class="cx">         value = cPickle.dumps(doc.root_element)
</span><del>-        newValue = updateFreeBusySet(value, directory)
</del><ins>+        newValue = yield updateFreeBusySet(value, directory)
</ins><span class="cx">         newValue = zlib.decompress(newValue)
</span><span class="cx">         self.assertEquals(newValue, expected)
</span><span class="cx"> 
</span><span class="lines">@@ -254,7 +240,7 @@
</span><span class="cx">         #
</span><span class="cx">         expected = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;&quot;
</span><span class="cx">         value = &quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;\r\n&lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;\r\n  &lt;href xmlns='DAV:'&gt;/calendars/users/nonexistent/calendar&lt;/href&gt;\r\n&lt;/calendar-free-busy-set&gt;\r\n&quot;
</span><del>-        newValue = updateFreeBusySet(value, directory)
</del><ins>+        newValue = yield updateFreeBusySet(value, directory)
</ins><span class="cx">         newValue = zlib.decompress(newValue)
</span><span class="cx">         self.assertEquals(newValue, expected)
</span><span class="cx"> 
</span><span class="lines">@@ -296,7 +282,6 @@
</span><span class="cx">         The upgrade process should remove unused notification directories in
</span><span class="cx">         users' calendar homes, as well as the XML files found therein.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.setUpXMLDirectory()
</del><span class="cx"> 
</span><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot;: {
</span><span class="lines">@@ -306,7 +291,7 @@
</span><span class="cx">                             db_basename : {
</span><span class="cx">                                 &quot;@contents&quot;: &quot;&quot;,
</span><span class="cx">                             },
</span><del>-                         },
</del><ins>+                        },
</ins><span class="cx">                         &quot;notifications&quot;: {
</span><span class="cx">                             &quot;sample-notification.xml&quot;: {
</span><span class="cx">                                 &quot;@contents&quot;: &quot;&lt;?xml version='1.0'&gt;\n&lt;should-be-ignored /&gt;&quot;
</span><span class="lines">@@ -353,8 +338,6 @@
</span><span class="cx">         are upgraded to /calendars/__uids__/XX/YY/&lt;guid&gt; form
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -503,8 +486,6 @@
</span><span class="cx">         whose records don't exist are moved into dataroot/archived/
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -575,8 +556,6 @@
</span><span class="cx">         whose records don't exist are moved into dataroot/archived/
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;archived&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -663,8 +642,6 @@
</span><span class="cx">         interrupt an upgrade.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         ignoredUIDContents = {
</span><span class="cx">             &quot;64&quot; : {
</span><span class="cx">                 &quot;23&quot; : {
</span><span class="lines">@@ -757,8 +734,6 @@
</span><span class="cx">         interrupt an upgrade.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         beforeUIDContents = {
</span><span class="cx">             &quot;64&quot; : {
</span><span class="cx">                 &quot;23&quot; : {
</span><span class="lines">@@ -867,8 +842,6 @@
</span><span class="cx">         are upgraded to /calendars/__uids__/XX/YY/&lt;guid&gt;/ form
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -978,8 +951,6 @@
</span><span class="cx">         form are upgraded correctly in place
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -1106,8 +1077,6 @@
</span><span class="cx">         form which require no changes are untouched
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -1233,8 +1202,6 @@
</span><span class="cx">         Verify that inbox items older than 60 days are deleted
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -1337,8 +1304,6 @@
</span><span class="cx">         also doesn't write the new version file
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.setUpXMLDirectory()
-
</del><span class="cx">         before = {
</span><span class="cx">             &quot;calendars&quot; :
</span><span class="cx">             {
</span><span class="lines">@@ -1454,7 +1419,7 @@
</span><span class="cx">         self.setUpInitialStates()
</span><span class="cx">         # Override the normal getResourceInfo method with our own:
</span><span class="cx">         # XMLDirectoryService.getResourceInfo = _getResourceInfo
</span><del>-        self.patch(XMLDirectoryService, &quot;getResourceInfo&quot;, _getResourceInfo)
</del><ins>+        # self.patch(XMLDirectoryService, &quot;getResourceInfo&quot;, _getResourceInfo)
</ins><span class="cx"> 
</span><span class="cx">         before = {
</span><span class="cx">             &quot;trigger_resource_migration&quot; : {
</span><span class="lines">@@ -1520,7 +1485,9 @@
</span><span class="cx">             autoSchedule = autoSchedule == 1
</span><span class="cx">             self.assertEquals(info[0], autoSchedule)
</span><span class="cx"> 
</span><ins>+    test_migrateResourceInfo.todo = &quot;Need to port to twext.who&quot;
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_removeIllegalCharacters(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Control characters aside from NL and CR are removed.
</span><span class="lines">@@ -1536,55 +1503,96 @@
</span><span class="cx">         self.assertFalse(changed)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def test_normalizeCUAddrs(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Ensure that calendar user addresses (CUAs) are cached so we can
</span><span class="cx">         reduce the number of principal lookup calls during upgrade.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        class StubPrincipal(object):
-            def __init__(self, record):
-                self.record = record
-
</del><span class="cx">         class StubRecord(object):
</span><del>-            def __init__(self, fullName, guid, cuas):
-                self.fullName = fullName
-                self.guid = guid
</del><ins>+            def __init__(self, fullNames, uid, cuas):
+                self.fullNames = fullNames
+                self.uid = uid
</ins><span class="cx">                 self.calendarUserAddresses = cuas
</span><span class="cx"> 
</span><ins>+            @property
+            def displayName(self):
+                return self.fullNames[0]
+
</ins><span class="cx">         class StubDirectory(object):
</span><span class="cx">             def __init__(self):
</span><span class="cx">                 self.count = 0
</span><span class="cx"> 
</span><del>-            def principalForCalendarUserAddress(self, cuaddr):
</del><ins>+            def recordWithCalendarUserAddress(self, cuaddr):
</ins><span class="cx">                 self.count += 1
</span><span class="cx">                 record = records.get(cuaddr, None)
</span><span class="cx">                 if record is not None:
</span><del>-                    return StubPrincipal(record)
</del><ins>+                    return succeed(record)
</ins><span class="cx">                 else:
</span><span class="cx">                     raise Exception
</span><span class="cx"> 
</span><span class="cx">         records = {
</span><del>-            &quot;mailto:a@example.com&quot; :
-                StubRecord(&quot;User A&quot;, 123, (&quot;mailto:a@example.com&quot;, &quot;urn:uuid:123&quot;)),
-            &quot;mailto:b@example.com&quot; :
-                StubRecord(&quot;User B&quot;, 234, (&quot;mailto:b@example.com&quot;, &quot;urn:uuid:234&quot;)),
-            &quot;/principals/users/a&quot; :
-                StubRecord(&quot;User A&quot;, 123, (&quot;mailto:a@example.com&quot;, &quot;urn:uuid:123&quot;)),
-            &quot;/principals/users/b&quot; :
-                StubRecord(&quot;User B&quot;, 234, (&quot;mailto:b@example.com&quot;, &quot;urn:uuid:234&quot;)),
</del><ins>+            &quot;mailto:a@example.com&quot;:
+                StubRecord((&quot;User A&quot;,), u&quot;123&quot;, (&quot;mailto:a@example.com&quot;, &quot;urn:uuid:123&quot;)),
+            &quot;mailto:b@example.com&quot;:
+                StubRecord((&quot;User B&quot;,), u&quot;234&quot;, (&quot;mailto:b@example.com&quot;, &quot;urn:uuid:234&quot;)),
+            &quot;/principals/users/a&quot;:
+                StubRecord((&quot;User A&quot;,), u&quot;123&quot;, (&quot;mailto:a@example.com&quot;, &quot;urn:uuid:123&quot;)),
+            &quot;/principals/users/b&quot;:
+                StubRecord((&quot;User B&quot;,), u&quot;234&quot;, (&quot;mailto:b@example.com&quot;, &quot;urn:uuid:234&quot;)),
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         directory = StubDirectory()
</span><span class="cx">         cuaCache = {}
</span><del>-        normalizeCUAddrs(normalizeEvent, directory, cuaCache)
-        normalizeCUAddrs(normalizeEvent, directory, cuaCache)
</del><ins>+        yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
+        yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
</ins><span class="cx"> 
</span><span class="cx">         # Ensure we only called principalForCalendarUserAddress 3 times.  It
</span><span class="cx">         # would have been 8 times without the cuaCache.
</span><span class="cx">         self.assertEquals(directory.count, 3)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_migrateDelegates(self):
+        store = self.storeUnderTest()
+        record = yield self.directory.recordWithUID(u&quot;mercury&quot;)
+        txn = store.newTransaction()
+        writeDelegates = yield delegatesOf(txn, record, True)
+        self.assertEquals(len(writeDelegates), 0)
+        yield txn.commit()
+
+        # Load delegates from xml into sqlite
+        sqliteProxyService = ProxySqliteDB(&quot;proxies.sqlite&quot;)
+        proxyFile = os.path.join(config.DataRoot, &quot;proxies.xml&quot;)
+        yield loadDelegatesFromXML(proxyFile, sqliteProxyService)
+
+        # Load delegates from sqlite into store
+        yield migrateDelegatesToStore(sqliteProxyService, store)
+
+        # Check delegates in store
+        txn = store.newTransaction()
+        writeDelegates = yield delegatesOf(txn, record, True)
+        self.assertEquals(len(writeDelegates), 1)
+        self.assertEquals(
+            set([d.uid for d in writeDelegates]),
+            set([u&quot;left_coast&quot;])
+        )
+
+        record = yield self.directory.recordWithUID(u&quot;non_calendar_proxy&quot;)
+
+        readDelegates = yield delegatesOf(txn, record, False)
+        self.assertEquals(len(readDelegates), 1)
+        self.assertEquals(
+            set([d.uid for d in readDelegates]),
+            set([u&quot;recursive2_coasts&quot;])
+        )
+
+        yield txn.commit()
+
+
+
+
</ins><span class="cx"> normalizeEvent = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> BEGIN:VEVENT
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtesttest_wrappingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_wrapping.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_wrapping.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/test_wrapping.py        2014-04-04 17:20:27 UTC (rev 13158)
</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">@@ -201,8 +206,10 @@
</span><span class="cx">         Verify that the C{_principalCollections} attribute of the given
</span><span class="cx">         L{Resource} is accurately set.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.assertEquals(resource._principalCollections,
-                          frozenset([self.principalsResource]))
</del><ins>+        self.assertEquals(
+            resource._principalCollections,
+            frozenset([self.actualRoot.getChild(&quot;principals&quot;)])
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -271,7 +278,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 +292,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 +351,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">@@ -415,7 +425,7 @@
</span><span class="cx">         Creating a AddressBookHomeProvisioningFile will create a paired
</span><span class="cx">         AddressBookStore.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        assertProvides(self, IDataStore, self.addressbookCollection._newStore)
</del><ins>+        assertProvides(self, IDataStore, self.actualRoot.getChild(&quot;addressbooks&quot;)._newStore)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -575,12 +585,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 +617,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 +647,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="CalendarServerbranchesuserssagenmove2who5twistedcaldavtestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/test/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -17,45 +17,36 @@
</span><span class="cx"> from __future__ import with_statement
</span><span class="cx"> 
</span><span class="cx"> import os
</span><del>-import xattr
</del><span class="cx"> 
</span><del>-from twistedcaldav.stdconfig import config
-
-from twisted.python.failure import Failure
</del><ins>+from calendarserver.provision.root import RootResource
+from calendarserver.tap.util import getRootResource
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.python.log import Logger
</ins><span class="cx"> from twisted.internet.base import DelayedCall
</span><span class="cx"> from twisted.internet.defer import succeed, fail, inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.protocol import ProcessProtocol
</span><del>-
-from twext.python.filepath import CachingFilePath as FilePath
-import txweb2.dav.test.util
-from txdav.xml import element as davxml, element
-from txweb2.http import HTTPError, StatusResponse
-
</del><ins>+from twisted.python.failure import Failure
</ins><span class="cx"> from twistedcaldav import memcacher
</span><del>-from twistedcaldav.memcacheclient import ClientFactory
</del><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><span class="cx"> from twistedcaldav.directory.calendar import (
</span><span class="cx">     DirectoryCalendarHomeProvisioningResource
</span><span class="cx"> )
</span><del>-from twistedcaldav.directory.principal import (
-    DirectoryPrincipalProvisioningResource)
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
-
-from txdav.common.datastore.test.util import deriveQuota, CommonCommonTests
-from txdav.common.datastore.file import CommonDataStore
-
-from calendarserver.provision.root import RootResource
-
-from twext.python.log import Logger
</del><ins>+from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.memcacheclient import ClientFactory
+from twistedcaldav.stdconfig import config
</ins><span class="cx"> from txdav.caldav.datastore.test.util import buildCalendarStore
</span><del>-from calendarserver.tap.util import getRootResource, directoryFromConfig
</del><ins>+from txdav.common.datastore.file import CommonDataStore
+from txdav.common.datastore.test.util import deriveQuota, CommonCommonTests
+from txdav.who.util import directoryFromConfig
+from txdav.xml import element as davxml, element
</ins><span class="cx"> from txweb2.dav.test.util import SimpleRequest
</span><del>-from twistedcaldav.directory.util import transactionFromRequest
-from twistedcaldav.directory.directory import DirectoryService
</del><ins>+import txweb2.dav.test.util
+from txweb2.http import HTTPError, StatusResponse
+import xattr
+from txweb2.server import Site
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -67,10 +58,12 @@
</span><span class="cx"> ]
</span><span class="cx"> DelayedCall.debug = True
</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"> 
</span><ins>+
</ins><span class="cx"> featureUnimplemented = lambda f: _todo(f, &quot;Feature unimplemented&quot;)
</span><span class="cx"> testUnimplemented = lambda f: _todo(f, &quot;Test unimplemented&quot;)
</span><span class="cx"> todo = lambda why: lambda f: _todo(f, why)
</span><span class="lines">@@ -78,97 +71,27 @@
</span><span class="cx"> dirTest = FilePath(__file__).parent().sibling(&quot;directory&quot;).child(&quot;test&quot;)
</span><span class="cx"> 
</span><span class="cx"> xmlFile = dirTest.child(&quot;accounts.xml&quot;)
</span><ins>+resourcesFile = dirTest.child(&quot;resources.xml&quot;)
</ins><span class="cx"> augmentsFile = dirTest.child(&quot;augments.xml&quot;)
</span><span class="cx"> proxiesFile = dirTest.child(&quot;proxies.xml&quot;)
</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">@@ -198,17 +121,16 @@
</span><span class="cx"> 
</span><span class="cx">         self.configure()
</span><span class="cx"> 
</span><del>-        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory, directoryFromConfig(config))
</del><ins>+        self._sqlCalendarStore = yield buildCalendarStore(
+            self, self.notifierFactory, None
+        )
+        self.directory = directoryFromConfig(config, self._sqlCalendarStore)
+        self._sqlCalendarStore.setDirectoryService(self.directory)
+
</ins><span class="cx">         self.rootResource = getRootResource(config, self._sqlCalendarStore)
</span><del>-        self.directory = self._sqlCalendarStore.directoryService()
</del><ins>+        self.actualRoot = self.rootResource.resource.resource
+        self.site = Site(self.actualRoot)
</ins><span class="cx"> 
</span><del>-        self.principalsResource = DirectoryPrincipalProvisioningResource(&quot;/principals/&quot;, self.directory)
-        self.site.resource.putChild(&quot;principals&quot;, self.principalsResource)
-        self.calendarCollection = DirectoryCalendarHomeProvisioningResource(self.directory, &quot;/calendars/&quot;, self._sqlCalendarStore)
-        self.site.resource.putChild(&quot;calendars&quot;, self.calendarCollection)
-        self.addressbookCollection = DirectoryAddressBookHomeProvisioningResource(self.directory, &quot;/addressbooks/&quot;, self._sqlCalendarStore)
-        self.site.resource.putChild(&quot;addressbooks&quot;, self.addressbookCollection)
-
</del><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -258,123 +180,17 @@
</span><span class="cx">         accounts = FilePath(config.DataRoot).child(&quot;accounts.xml&quot;)
</span><span class="cx">         accounts.setContent(xmlFile.getContent())
</span><span class="cx"> 
</span><ins>+        resources = FilePath(config.DataRoot).child(&quot;resources.xml&quot;)
+        resources.setContent(resourcesFile.getContent())
</ins><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><ins>+        augments = FilePath(config.DataRoot).child(&quot;augments.xml&quot;)
+        augments.setContent(augmentsFile.getContent())
</ins><span class="cx"> 
</span><ins>+        proxies = FilePath(config.DataRoot).child(&quot;proxies.xml&quot;)
+        proxies.setContent(proxiesFile.getContent())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-class TestCase(txweb2.dav.test.util.TestCase):
-    resource_class = RootResource
</del><span class="cx"> 
</span><del>-    def createDataStore(self):
-        &quot;&quot;&quot;
-        Create an L{IDataStore} that can store calendars (but not
-        addressbooks.)  By default returns a L{CommonDataStore}, but this is a
-        hook for subclasses to override to provide different data stores.
-        &quot;&quot;&quot;
-        return CommonDataStore(FilePath(config.DocumentRoot), None, None, True, False,
-                               quota=deriveQuota(self))
-
-
-    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,)),
-        }))
-
-
-    def setupCalendars(self):
-        &quot;&quot;&quot;
-        When a directory service exists, set up the resources at C{/calendars}
-        and C{/addressbooks} (a L{DirectoryCalendarHomeProvisioningResource}
-        and L{DirectoryAddressBookHomeProvisioningResource} respectively), and
-        assign them to the C{self.calendarCollection} and
-        C{self.addressbookCollection} attributes.
-
-        A directory service may be associated with this L{TestCase} with
-        L{TestCase.createStockDirectoryService} or
-        L{TestCase.directoryFixture.addDirectoryService}.
-        &quot;&quot;&quot;
-        newStore = self.createDataStore()
-        @self.directoryFixture.whenDirectoryServiceChanges
-        def putAllChildren(ds):
-            self.calendarCollection = (
-                DirectoryCalendarHomeProvisioningResource(
-                    ds, &quot;/calendars/&quot;, newStore
-                ))
-            self.site.resource.putChild(&quot;calendars&quot;, self.calendarCollection)
-            self.addressbookCollection = (
-                DirectoryAddressBookHomeProvisioningResource(
-                    ds, &quot;/addressbooks/&quot;, newStore
-                ))
-            self.site.resource.putChild(&quot;addressbooks&quot;,
-                                        self.addressbookCollection)
-
-
-    def configure(self):
-        &quot;&quot;&quot;
-        Adjust the global configuration for this test.
-        &quot;&quot;&quot;
-        config.reset()
-
-        config.ServerRoot = os.path.abspath(self.serverRoot)
-        config.ConfigRoot = &quot;config&quot;
-        config.LogRoot = &quot;logs&quot;
-        config.RunRoot = &quot;logs&quot;
-
-        config.Memcached.Pools.Default.ClientEnabled = False
-        config.Memcached.Pools.Default.ServerEnabled = False
-        ClientFactory.allowTestCache = True
-        memcacher.Memcacher.allowTestCache = True
-        memcacher.Memcacher.memoryCacheInstance = None
-        config.DirectoryAddressBook.Enabled = False
-        config.UsePackageTimezones = True
-
-
-    @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
-
-
-    def setUp(self):
-        super(TestCase, self).setUp()
-
-        self.directoryFixture = DirectoryFixture()
-
-        # FIXME: this is only here to workaround circular imports
-        doBind()
-
-        self.serverRoot = self.mktemp()
-        os.mkdir(self.serverRoot)
-
-        self.configure()
-
-        if not os.path.exists(config.DataRoot):
-            os.makedirs(config.DataRoot)
-        if not os.path.exists(config.DocumentRoot):
-            os.makedirs(config.DocumentRoot)
-        if not os.path.exists(config.ConfigRoot):
-            os.makedirs(config.ConfigRoot)
-        if not os.path.exists(config.LogRoot):
-            os.makedirs(config.LogRoot)
-
-
</del><span class="cx">     def createHierarchy(self, structure, root=None):
</span><span class="cx">         if root is None:
</span><span class="cx">             root = os.path.abspath(self.mktemp())
</span><span class="lines">@@ -506,7 +322,7 @@
</span><span class="cx">                                     print(&quot;Xattr mismatch:&quot;, childPath, attr)
</span><span class="cx">                                     print((xattr.getxattr(childPath, attr), &quot; != &quot;, value))
</span><span class="cx">                                     return False
</span><del>-                            else: # method
</del><ins>+                            else:  # method
</ins><span class="cx">                                 if not value(xattr.getxattr(childPath, attr)):
</span><span class="cx">                                     return False
</span><span class="cx"> 
</span><span class="lines">@@ -528,6 +344,93 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class TestCase(txweb2.dav.test.util.TestCase):
+    resource_class = RootResource
+
+    def createDataStore(self):
+        &quot;&quot;&quot;
+        Create an L{IDataStore} that can store calendars (but not
+        addressbooks.)  By default returns a L{CommonDataStore}, but this is a
+        hook for subclasses to override to provide different data stores.
+        &quot;&quot;&quot;
+        return CommonDataStore(FilePath(config.DocumentRoot), None, None, True, False,
+                               quota=deriveQuota(self))
+
+
+    def setupCalendars(self):
+        &quot;&quot;&quot;
+        When a directory service exists, set up the resources at C{/calendars}
+        and C{/addressbooks} (a L{DirectoryCalendarHomeProvisioningResource}
+        and L{DirectoryAddressBookHomeProvisioningResource} respectively), and
+        assign them to the C{self.calendarCollection} and
+        C{self.addressbookCollection} attributes.
+
+        A directory service may be associated with this L{TestCase} with
+        L{TestCase.createStockDirectoryService} or
+        L{TestCase.directoryFixture.addDirectoryService}.
+        &quot;&quot;&quot;
+        newStore = self.createDataStore()
+
+
+        @self.directoryFixture.whenDirectoryServiceChanges
+        def putAllChildren(ds):
+            self.calendarCollection = (
+                DirectoryCalendarHomeProvisioningResource(
+                    ds, &quot;/calendars/&quot;, newStore
+                ))
+            self.site.resource.putChild(&quot;calendars&quot;, self.calendarCollection)
+            self.addressbookCollection = (
+                DirectoryAddressBookHomeProvisioningResource(
+                    ds, &quot;/addressbooks/&quot;, newStore
+                ))
+            self.site.resource.putChild(&quot;addressbooks&quot;,
+                                        self.addressbookCollection)
+
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Adjust the global configuration for this test.
+        &quot;&quot;&quot;
+        config.reset()
+
+        config.ServerRoot = os.path.abspath(self.serverRoot)
+        config.ConfigRoot = &quot;config&quot;
+        config.LogRoot = &quot;logs&quot;
+        config.RunRoot = &quot;logs&quot;
+
+        config.Memcached.Pools.Default.ClientEnabled = False
+        config.Memcached.Pools.Default.ServerEnabled = False
+        ClientFactory.allowTestCache = True
+        memcacher.Memcacher.allowTestCache = True
+        memcacher.Memcacher.memoryCacheInstance = None
+        config.DirectoryAddressBook.Enabled = False
+        config.UsePackageTimezones = True
+
+
+
+    def setUp(self):
+        super(TestCase, self).setUp()
+
+        # FIXME: this is only here to workaround circular imports
+        doBind()
+
+        self.serverRoot = self.mktemp()
+        os.mkdir(self.serverRoot)
+
+        self.configure()
+
+        if not os.path.exists(config.DataRoot):
+            os.makedirs(config.DataRoot)
+        if not os.path.exists(config.DocumentRoot):
+            os.makedirs(config.DocumentRoot)
+        if not os.path.exists(config.ConfigRoot):
+            os.makedirs(config.ConfigRoot)
+        if not os.path.exists(config.LogRoot):
+            os.makedirs(config.LogRoot)
+
+
+
+
</ins><span class="cx"> class norequest(object):
</span><span class="cx">     def addResponseFilter(self, filter):
</span><span class="cx">         &quot;stub; ignore me&quot;
</span><span class="lines">@@ -555,7 +458,8 @@
</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><ins>+
+
</ins><span class="cx">         @self.directoryFixture.whenDirectoryServiceChanges
</span><span class="cx">         def addHomeProvisioner(ds):
</span><span class="cx">             self.homeProvisioner = DirectoryCalendarHomeProvisioningResource(
</span><span class="lines">@@ -639,7 +543,8 @@
</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><ins>+
+
</ins><span class="cx">         @self.directoryFixture.whenDirectoryServiceChanges
</span><span class="cx">         def addHomeProvisioner(ds):
</span><span class="cx">             self.homeProvisioner = DirectoryAddressBookHomeProvisioningResource(
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtimezoneservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezoneservice.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezoneservice.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezoneservice.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -86,15 +86,17 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
</ins><span class="cx">                 ),
</span><del>-                davxml.Protected(),
-            ),
</del><ins>+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavtimezonestdservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezonestdservice.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezonestdservice.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/timezonestdservice.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -146,15 +146,17 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def defaultAccessControlList(self):
</span><del>-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
</ins><span class="cx">                 ),
</span><del>-                davxml.Protected(),
-            ),
</del><ins>+            )
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavupgradepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/upgrade.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/upgrade.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/upgrade.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -26,7 +26,6 @@
</span><span class="cx"> import grp
</span><span class="cx"> import shutil
</span><span class="cx"> import errno
</span><del>-import operator
</del><span class="cx"> import time
</span><span class="cx"> from zlib import compress
</span><span class="cx"> from cPickle import loads as unpickle, UnpicklingError
</span><span class="lines">@@ -37,18 +36,14 @@
</span><span class="cx"> from txweb2.dav.fileop import rmdir
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml
</span><del>-from twistedcaldav.directory import calendaruserproxy
</del><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="cx"> from txdav.caldav.datastore.scheduling.scheduler import DirectScheduler
</span><del>-from twistedcaldav.util import normalizationLookup
</del><ins>+from txdav.caldav.datastore.util import normalizationLookup
</ins><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import (
</span><span class="cx">     inlineCallbacks, succeed, returnValue
</span><span class="lines">@@ -58,13 +53,18 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.index_file import db_basename
</span><span class="cx"> 
</span><del>-from twisted.protocols.amp import AMP, Command, String, Boolean
</del><ins>+# from twisted.protocols.amp import AMP, Command, String, Boolean
</ins><span class="cx"> 
</span><del>-from calendarserver.tap.util import getRootResource, FakeRequest, directoryFromConfig
-from calendarserver.tools.util import getDirectory
</del><ins>+from calendarserver.tap.util import getRootResource, FakeRequest
</ins><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.mailgateway import migrateTokensToStore
</span><span class="cx"> 
</span><ins>+from twext.who.idirectory import RecordType
+from txdav.who.idirectory import RecordType as CalRecordType
+from txdav.who.delegates import addDelegate
+from twistedcaldav.directory.calendaruserproxy import ProxySqliteDB
+
+
</ins><span class="cx"> deadPropertyXattrPrefix = namedAny(
</span><span class="cx">     &quot;txdav.base.propertystore.xattr.PropertyStore.deadPropertyXattrPrefix&quot;
</span><span class="cx"> )
</span><span class="lines">@@ -74,6 +74,7 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def xattrname(n):
</span><span class="cx">     return deadPropertyXattrPrefix + n
</span><span class="cx"> 
</span><span class="lines">@@ -121,6 +122,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def upgradeCalendarCollection(calPath, directory, cuaCache):
</span><span class="cx">     errorOccurred = False
</span><span class="cx">     collectionUpdated = False
</span><span class="lines">@@ -147,8 +149,10 @@
</span><span class="cx">                     log.warn(&quot;Fixing bad quotes in %s&quot; % (resPath,))
</span><span class="cx">                     needsRewrite = True
</span><span class="cx">             except Exception, e:
</span><del>-                log.error(&quot;Error while fixing bad quotes in %s: %s&quot; %
-                    (resPath, e))
</del><ins>+                log.error(
+                    &quot;Error while fixing bad quotes in %s: %s&quot; %
+                    (resPath, e)
+                )
</ins><span class="cx">                 errorOccurred = True
</span><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="lines">@@ -158,19 +162,23 @@
</span><span class="cx">                     log.warn(&quot;Removing illegal characters in %s&quot; % (resPath,))
</span><span class="cx">                     needsRewrite = True
</span><span class="cx">             except Exception, e:
</span><del>-                log.error(&quot;Error while removing illegal characters in %s: %s&quot; %
-                    (resPath, e))
</del><ins>+                log.error(
+                    &quot;Error while removing illegal characters in %s: %s&quot; %
+                    (resPath, e)
+                )
</ins><span class="cx">                 errorOccurred = True
</span><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="cx">             try:
</span><del>-                data, fixed = normalizeCUAddrs(data, directory, cuaCache)
</del><ins>+                data, fixed = (yield normalizeCUAddrs(data, directory, cuaCache))
</ins><span class="cx">                 if fixed:
</span><span class="cx">                     log.debug(&quot;Normalized CUAddrs in %s&quot; % (resPath,))
</span><span class="cx">                     needsRewrite = True
</span><span class="cx">             except Exception, e:
</span><del>-                log.error(&quot;Error while normalizing %s: %s&quot; %
-                    (resPath, e))
</del><ins>+                log.error(
+                    &quot;Error while normalizing %s: %s&quot; %
+                    (resPath, e)
+                )
</ins><span class="cx">                 errorOccurred = True
</span><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="lines">@@ -207,10 +215,11 @@
</span><span class="cx">         except:
</span><span class="cx">             raise
</span><span class="cx"> 
</span><del>-    return errorOccurred
</del><ins>+    returnValue(errorOccurred)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def upgradeCalendarHome(homePath, directory, cuaCache):
</span><span class="cx"> 
</span><span class="cx">     errorOccurred = False
</span><span class="lines">@@ -229,7 +238,7 @@
</span><span class="cx">                 rmdir(calPath)
</span><span class="cx">                 continue
</span><span class="cx">             log.debug(&quot;Upgrading calendar: %s&quot; % (calPath,))
</span><del>-            if not upgradeCalendarCollection(calPath, directory, cuaCache):
</del><ins>+            if not (yield upgradeCalendarCollection(calPath, directory, cuaCache)):
</ins><span class="cx">                 errorOccurred = True
</span><span class="cx"> 
</span><span class="cx">             # Change the calendar-free-busy-set xattrs of the inbox to the
</span><span class="lines">@@ -238,7 +247,7 @@
</span><span class="cx">                 try:
</span><span class="cx">                     for attr, value in xattr.xattr(calPath).iteritems():
</span><span class="cx">                         if attr == xattrname(&quot;{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set&quot;):
</span><del>-                            value = updateFreeBusySet(value, directory)
</del><ins>+                            value = yield updateFreeBusySet(value, directory)
</ins><span class="cx">                             if value is not None:
</span><span class="cx">                                 # Need to write the xattr back to disk
</span><span class="cx">                                 xattr.setxattr(calPath, attr, value)
</span><span class="lines">@@ -254,43 +263,44 @@
</span><span class="cx">         log.error(&quot;Failed to upgrade calendar home %s: %s&quot; % (homePath, e))
</span><span class="cx">         raise
</span><span class="cx"> 
</span><del>-    return errorOccurred
</del><ins>+    returnValue(errorOccurred)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class UpgradeOneHome(Command):
-    arguments = [('path', String())]
-    response = [('succeeded', Boolean())]
</del><ins>+# class UpgradeOneHome(Command):
+#     arguments = [('path', String())]
+#     response = [('succeeded', Boolean())]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class To1Driver(AMP):
-    &quot;&quot;&quot;
-    Upgrade driver which runs in the parent process.
-    &quot;&quot;&quot;
</del><ins>+# class To1Driver(AMP):
+#     &quot;&quot;&quot;
+#     Upgrade driver which runs in the parent process.
+#     &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-    def upgradeHomeInHelper(self, path):
-        return self.callRemote(UpgradeOneHome, path=path).addCallback(
-            operator.itemgetter(&quot;succeeded&quot;)
-        )
</del><ins>+#     def upgradeHomeInHelper(self, path):
+#         return self.callRemote(UpgradeOneHome, path=path).addCallback(
+#             operator.itemgetter(&quot;succeeded&quot;)
+#         )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class To1Home(AMP):
-    &quot;&quot;&quot;
-    Upgrade worker which runs in dedicated subprocesses.
-    &quot;&quot;&quot;
</del><ins>+# class To1Home(AMP):
+#     &quot;&quot;&quot;
+#     Upgrade worker which runs in dedicated subprocesses.
+#     &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-    def __init__(self, config):
-        super(To1Home, self).__init__()
-        self.directory = getDirectory(config)
-        self.cuaCache = {}
</del><ins>+#     def __init__(self, config):
+#         super(To1Home, self).__init__()
+#         self.directory = getDirectory(config)
+#         self.cuaCache = {}
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @UpgradeOneHome.responder
-    def upgradeOne(self, path):
-        result = upgradeCalendarHome(path, self.directory, self.cuaCache)
-        return dict(succeeded=result)
</del><ins>+#     @UpgradeOneHome.responder
+#     @inlineCallbacks
+#     def upgradeOne(self, path):
+#         result = yield upgradeCalendarHome(path, self.directory, self.cuaCache)
+#         returnValue(dict(succeeded=result))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -300,13 +310,14 @@
</span><span class="cx">     Upconvert data from any calendar server version prior to data format 1.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     errorOccurred = []
</span><ins>+
</ins><span class="cx">     def setError(f=None):
</span><span class="cx">         if f is not None:
</span><span class="cx">             log.error(f)
</span><span class="cx">         errorOccurred.append(True)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def doProxyDatabaseMoveUpgrade(config, uid= -1, gid= -1):
</del><ins>+    def doProxyDatabaseMoveUpgrade(config, uid=-1, gid=-1):
</ins><span class="cx">         # See if the new one is already present
</span><span class="cx">         oldFilename = &quot;.db.calendaruserproxy&quot;
</span><span class="cx">         newFilename = &quot;proxies.sqlite&quot;
</span><span class="lines">@@ -345,7 +356,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def moveCalendarHome(oldHome, newHome, uid= -1, gid= -1):
</del><ins>+    def moveCalendarHome(oldHome, newHome, uid=-1, gid=-1):
</ins><span class="cx">         if os.path.exists(newHome):
</span><span class="cx">             # Both old and new homes exist; stop immediately to let the
</span><span class="cx">             # administrator fix it
</span><span class="lines">@@ -354,8 +365,9 @@
</span><span class="cx">                 % (oldHome, newHome)
</span><span class="cx">             )
</span><span class="cx"> 
</span><del>-        makeDirsUserGroup(os.path.dirname(newHome.rstrip(&quot;/&quot;)), uid=uid,
-            gid=gid)
</del><ins>+        makeDirsUserGroup(
+            os.path.dirname(newHome.rstrip(&quot;/&quot;)), uid=uid, gid=gid
+        )
</ins><span class="cx">         os.rename(oldHome, newHome)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -366,12 +378,15 @@
</span><span class="cx">         service, because in &quot;v1&quot; that's where this info lived.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        print(&quot;FIXME, need to port migrateResourceInfo to twext.who&quot;)
+        returnValue(None)
+
</ins><span class="cx">         log.warn(&quot;Fetching delegate assignments and auto-schedule settings from directory&quot;)
</span><span class="cx">         resourceInfo = directory.getResourceInfo()
</span><span class="cx">         if len(resourceInfo) == 0:
</span><span class="cx">             # Nothing to migrate, or else not appleopendirectory
</span><span class="cx">             log.warn(&quot;No resource info found in directory&quot;)
</span><del>-            return
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">         log.warn(&quot;Found info for %d resources and locations in directory; applying settings&quot; % (len(resourceInfo),))
</span><span class="cx"> 
</span><span class="lines">@@ -467,20 +482,23 @@
</span><span class="cx">                 os.chown(uidHomes, uid, gid)
</span><span class="cx"> 
</span><span class="cx">             for recordType, dirName in (
</span><del>-                (DirectoryService.recordType_users, &quot;users&quot;),
-                (DirectoryService.recordType_groups, &quot;groups&quot;),
-                (DirectoryService.recordType_locations, &quot;locations&quot;),
-                (DirectoryService.recordType_resources, &quot;resources&quot;),
</del><ins>+                (RecordType.user, u&quot;users&quot;),
+                (RecordType.group, u&quot;groups&quot;),
+                (CalRecordType.location, u&quot;locations&quot;),
+                (CalRecordType.resource, u&quot;resources&quot;),
</ins><span class="cx">             ):
</span><span class="cx">                 dirPath = os.path.join(calRoot, dirName)
</span><span class="cx">                 if os.path.exists(dirPath):
</span><span class="cx">                     for shortName in os.listdir(dirPath):
</span><del>-                        record = directory.recordWithShortName(recordType,
-                            shortName)
</del><ins>+                        record = yield directory.recordWithShortName(
+                            recordType, shortName
+                        )
</ins><span class="cx">                         oldHome = os.path.join(dirPath, shortName)
</span><span class="cx">                         if record is not None:
</span><del>-                            newHome = os.path.join(uidHomes, record.uid[0:2],
-                                record.uid[2:4], record.uid)
</del><ins>+                            newHome = os.path.join(
+                                uidHomes, record.uid[0:2],
+                                record.uid[2:4], record.uid
+                            )
</ins><span class="cx">                             moveCalendarHome(oldHome, newHome, uid=uid, gid=gid)
</span><span class="cx">                         else:
</span><span class="cx">                             # an orphaned calendar home (principal no longer
</span><span class="lines">@@ -543,15 +561,17 @@
</span><span class="cx">                                         # Skip non-directories
</span><span class="cx">                                         continue
</span><span class="cx"> 
</span><del>-                                    if not upgradeCalendarHome(
</del><ins>+                                    if not (yield upgradeCalendarHome(
</ins><span class="cx">                                         homePath, directory, cuaCache
</span><del>-                                    ):
</del><ins>+                                    )):
</ins><span class="cx">                                         setError()
</span><span class="cx"> 
</span><span class="cx">                                     count += 1
</span><span class="cx">                                     if count % 10 == 0:
</span><del>-                                        log.warn(&quot;Processed calendar home %d of %d&quot;
-                                            % (count, total))
</del><ins>+                                        log.warn(
+                                            &quot;Processed calendar home %d of %d&quot;
+                                            % (count, total)
+                                        )
</ins><span class="cx">                 log.warn(&quot;Done processing calendar homes&quot;)
</span><span class="cx"> 
</span><span class="cx">     triggerPath = os.path.join(config.ServerRoot, TRIGGER_FILE)
</span><span class="lines">@@ -564,6 +584,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def normalizeCUAddrs(data, directory, cuaCache):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Normalize calendar user addresses to urn:uuid: form.
</span><span class="lines">@@ -583,23 +604,26 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     cal = Component.fromString(data)
</span><span class="cx"> 
</span><del>-    def lookupFunction(cuaddr, principalFunction, config):
</del><ins>+    @inlineCallbacks
+    def lookupFunction(cuaddr, recordFunction, config):
</ins><span class="cx"> 
</span><span class="cx">         # Return cached results, if any.
</span><span class="cx">         if cuaddr in cuaCache:
</span><del>-            return cuaCache[cuaddr]
</del><ins>+            returnValue(cuaCache[cuaddr])
</ins><span class="cx"> 
</span><del>-        result = normalizationLookup(cuaddr, principalFunction, config)
</del><ins>+        result = yield normalizationLookup(cuaddr, recordFunction, config)
</ins><span class="cx"> 
</span><span class="cx">         # Cache the result
</span><span class="cx">         cuaCache[cuaddr] = result
</span><del>-        return result
</del><ins>+        returnValue(result)
</ins><span class="cx"> 
</span><del>-    cal.normalizeCalendarUserAddresses(lookupFunction,
-        directory.principalForCalendarUserAddress)
</del><ins>+    yield cal.normalizeCalendarUserAddresses(
+        lookupFunction,
+        directory.recordWithCalendarUserAddress
+    )
</ins><span class="cx"> 
</span><span class="cx">     newData = str(cal)
</span><del>-    return newData, not newData == data
</del><ins>+    returnValue((newData, not newData == data))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -708,6 +732,7 @@
</span><span class="cx">         raise UpgradeError(&quot;Data upgrade failed, see error.log for details&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> # The on-disk version number (which defaults to zero if .calendarserver_version
</span><span class="cx"> # doesn't exist), is compared with each of the numbers in the upgradeMethods
</span><span class="cx"> # array.  If it is less than the number, the associated method is called.
</span><span class="lines">@@ -717,17 +742,17 @@
</span><span class="cx">     (2, upgrade_to_2),
</span><span class="cx"> ]
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def upgradeData(config):
</del><ins>+def upgradeData(config, directory):
</ins><span class="cx"> 
</span><del>-    directory = getDirectory()
</del><span class="cx"> 
</span><span class="cx">     triggerPath = os.path.join(config.ServerRoot, TRIGGER_FILE)
</span><span class="cx">     if os.path.exists(triggerPath):
</span><span class="cx">         try:
</span><span class="cx">             # Migrate locations/resources now because upgrade_to_1 depends
</span><span class="cx">             # on them being in resources.xml
</span><del>-            (yield migrateFromOD(config, directory))
</del><ins>+            yield migrateFromOD(config, directory)
</ins><span class="cx">         except Exception, e:
</span><span class="cx">             raise UpgradeError(&quot;Unable to migrate locations and resources from OD: %s&quot; % (e,))
</span><span class="cx"> 
</span><span class="lines">@@ -745,11 +770,15 @@
</span><span class="cx">             with open(versionFilePath) as versionFile:
</span><span class="cx">                 onDiskVersion = int(versionFile.read().strip())
</span><span class="cx">         except IOError:
</span><del>-            log.error(&quot;Cannot open %s; skipping migration&quot; %
-                (versionFilePath,))
</del><ins>+            log.error(
+                &quot;Cannot open %s; skipping migration&quot; %
+                (versionFilePath,)
+            )
</ins><span class="cx">         except ValueError:
</span><del>-            log.error(&quot;Invalid version number in %s; skipping migration&quot; %
-                (versionFilePath,))
</del><ins>+            log.error(
+                &quot;Invalid version number in %s; skipping migration&quot; %
+                (versionFilePath,)
+            )
</ins><span class="cx"> 
</span><span class="cx">     uid, gid = getCalendarServerIDs(config)
</span><span class="cx"> 
</span><span class="lines">@@ -779,26 +808,28 @@
</span><span class="cx"> #
</span><span class="cx"> # Utility functions
</span><span class="cx"> #
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def updateFreeBusyHref(href, directory):
</span><span class="cx">     pieces = href.split(&quot;/&quot;)
</span><span class="cx">     if pieces[2] == &quot;__uids__&quot;:
</span><span class="cx">         # Already updated
</span><del>-        return None
</del><ins>+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">     recordType = pieces[2]
</span><span class="cx">     shortName = pieces[3]
</span><del>-    record = directory.recordWithShortName(recordType, shortName)
</del><ins>+    record = yield directory.recordWithShortName(recordType, shortName)
</ins><span class="cx">     if record is None:
</span><span class="cx">         # We will simply ignore this and not write out an fb-set entry
</span><span class="cx">         log.error(&quot;Can't update free-busy href; %s is not in the directory&quot; % shortName)
</span><del>-        return &quot;&quot;
</del><ins>+        returnValue(&quot;&quot;)
</ins><span class="cx"> 
</span><span class="cx">     uid = record.uid
</span><span class="cx">     newHref = &quot;/calendars/__uids__/%s/%s/&quot; % (uid, pieces[4])
</span><del>-    return newHref
</del><ins>+    returnValue(newHref)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def updateFreeBusySet(value, directory):
</span><span class="cx"> 
</span><span class="cx">     try:
</span><span class="lines">@@ -815,14 +846,13 @@
</span><span class="cx">             freeBusySet = unpickle(value)
</span><span class="cx">         except UnpicklingError:
</span><span class="cx">             log.error(&quot;Invalid free/busy property value&quot;)
</span><del>-            # MOR: continue on?
-            return None
</del><ins>+            returnValue(None)
</ins><span class="cx"> 
</span><span class="cx">     fbset = set()
</span><span class="cx">     didUpdate = False
</span><span class="cx">     for href in freeBusySet.children:
</span><span class="cx">         href = str(href)
</span><del>-        newHref = updateFreeBusyHref(href, directory)
</del><ins>+        newHref = yield updateFreeBusyHref(href, directory)
</ins><span class="cx">         if newHref is None:
</span><span class="cx">             fbset.add(href)
</span><span class="cx">         else:
</span><span class="lines">@@ -831,18 +861,19 @@
</span><span class="cx">                 fbset.add(newHref)
</span><span class="cx"> 
</span><span class="cx">     if didUpdate:
</span><del>-        property = caldavxml.CalendarFreeBusySet(*[element.HRef(href)
-            for href in fbset])
</del><ins>+        property = caldavxml.CalendarFreeBusySet(
+            *[element.HRef(href) for href in fbset]
+        )
</ins><span class="cx">         value = compress(property.toxml())
</span><del>-        return value
</del><ins>+        returnValue(value)
</ins><span class="cx"> 
</span><del>-    return None # no update required
</del><ins>+    returnValue(None)  # no update required
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def makeDirsUserGroup(path, uid= -1, gid= -1):
</del><ins>+def makeDirsUserGroup(path, uid=-1, gid=-1):
</ins><span class="cx">     parts = path.split(&quot;/&quot;)
</span><del>-    if parts[0] == &quot;&quot;: # absolute path
</del><ins>+    if parts[0] == &quot;&quot;:  # absolute path
</ins><span class="cx">         parts[0] = &quot;/&quot;
</span><span class="cx"> 
</span><span class="cx">     path = &quot;&quot;
</span><span class="lines">@@ -887,6 +918,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> DELETECHARS = ''.join(chr(i) for i in xrange(32) if i not in (9, 10, 13))
</span><ins>+
+
</ins><span class="cx"> def removeIllegalCharacters(data):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Remove all characters below ASCII 32 except HTAB, LF and CR
</span><span class="lines">@@ -904,31 +937,34 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-# Deferred
</del><ins>+# # Deferred
</ins><span class="cx"> def migrateFromOD(config, directory):
</span><del>-    #
-    # 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>+    # FIXME:
+    print(&quot;STILL NEED TO IMPLEMENT migrateFromOD&quot;)
+    return succeed(None)
+#     #
+#     # 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">@@ -936,7 +972,14 @@
</span><span class="cx"> def migrateAutoSchedule(config, directory):
</span><span class="cx">     # Fetch the autoSchedule assignments from resourceinfo.sqlite and store
</span><span class="cx">     # the values in augments
</span><del>-    augmentService = directory.augmentService
</del><ins>+    augmentService = None
+    if config.AugmentService.type:
+        augmentClass = namedClass(config.AugmentService.type)
+        try:
+            augmentService = augmentClass(**config.AugmentService.params)
+        except:
+            log.error(&quot;Could not start augment service&quot;)
+
</ins><span class="cx">     if augmentService:
</span><span class="cx">         augmentRecords = []
</span><span class="cx">         dbPath = os.path.join(config.DataRoot, ResourceInfoDatabase.dbFilename)
</span><span class="lines">@@ -946,11 +989,18 @@
</span><span class="cx">             results = resourceInfoDatabase._db_execute(
</span><span class="cx">                 &quot;select GUID, AUTOSCHEDULE from RESOURCEINFO&quot;
</span><span class="cx">             )
</span><del>-            for guid, autoSchedule in results:
-                record = directory.recordWithGUID(guid)
</del><ins>+            for uid, autoSchedule in results:
+                record = yield directory.recordWithUID(uid)
</ins><span class="cx">                 if record is not None:
</span><del>-                    augmentRecord = (yield augmentService.getAugmentRecord(guid, record.recordType))
-                    augmentRecord.autoSchedule = autoSchedule
</del><ins>+                    augmentRecord = (
+                        yield augmentService.getAugmentRecord(
+                            uid,
+                            directory.recordTypeToOldName(record.recordType)
+                        )
+                    )
+                    augmentRecord.autoScheduleMode = (
+                        &quot;automatic&quot; if autoSchedule else &quot;default&quot;
+                    )
</ins><span class="cx">                     augmentRecords.append(augmentRecord)
</span><span class="cx"> 
</span><span class="cx">             if augmentRecords:
</span><span class="lines">@@ -958,17 +1008,50 @@
</span><span class="cx">             log.warn(&quot;Migrated %d auto-schedule settings&quot; % (len(augmentRecords),))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def loadDelegatesFromXML(xmlFile, service):
+    loader = XMLCalendarUserProxyLoader(xmlFile, service)
+    return loader.updateProxyDB()
</ins><span class="cx"> 
</span><ins>+
+@inlineCallbacks
+def migrateDelegatesToStore(service, store):
+    directory = store.directoryService()
+    txn = store.newTransaction()
+    for groupName, memberUID in (
+        yield service.query(
+            &quot;select GROUPNAME, MEMBER from GROUPS&quot;
+        )
+    ):
+        if &quot;#&quot; not in groupName:
+            continue
+
+        delegatorUID, groupType = groupName.split(&quot;#&quot;)
+        delegatorRecord = yield directory.recordWithUID(delegatorUID)
+        if delegatorRecord is None:
+            continue
+
+        delegateRecord = yield directory.recordWithUID(memberUID)
+        if delegateRecord is None:
+            continue
+
+        readWrite = (groupType == &quot;calendar-proxy-write&quot;)
+        yield addDelegate(txn, delegatorRecord, delegateRecord, readWrite)
+
+    yield txn.commit()
+
+
+
</ins><span class="cx"> class UpgradeFileSystemFormatStep(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Upgrade filesystem from previous versions.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def __init__(self, config):
</del><ins>+    def __init__(self, config, store):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Initialize the service.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.config = config
</span><ins>+        self.store = store
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -985,7 +1068,7 @@
</span><span class="cx">         memcacheEnabled = self.config.Memcached.Pools.Default.ClientEnabled
</span><span class="cx">         self.config.Memcached.Pools.Default.ClientEnabled = False
</span><span class="cx"> 
</span><del>-        yield upgradeData(self.config)
</del><ins>+        yield upgradeData(self.config, self.store.directoryService())
</ins><span class="cx"> 
</span><span class="cx">         # Restore memcached client setting
</span><span class="cx">         self.config.Memcached.Pools.Default.ClientEnabled = memcacheEnabled
</span><span class="lines">@@ -1010,6 +1093,8 @@
</span><span class="cx"> 
</span><span class="cx">         1. Populating the group-membership cache
</span><span class="cx">         2. Processing non-implicit inbox items
</span><ins>+        3. Migrate IMIP tokens into the store
+        4. Migrating delegate assignments into the store
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, store, config, doPostImport):
</span><span class="lines">@@ -1025,38 +1110,48 @@
</span><span class="cx">     def stepWithResult(self, result):
</span><span class="cx">         if self.doPostImport:
</span><span class="cx"> 
</span><del>-            directory = directoryFromConfig(self.config)
-
</del><span class="cx">             # Load proxy assignments from XML if specified
</span><del>-            if self.config.ProxyLoadFromFile:
-                proxydbClass = namedClass(self.config.ProxyDBService.type)
-                calendaruserproxy.ProxyDBService = proxydbClass(
-                    **self.config.ProxyDBService.params)
-                loader = XMLCalendarUserProxyLoader(self.config.ProxyLoadFromFile)
-                yield loader.updateProxyDB()
</del><ins>+            if (
+                self.config.ProxyLoadFromFile and
+                os.path.exists(self.config.ProxyLoadFromFile) and
+                not os.path.exists(
+                    os.path.join(
+                        self.config.DataRoot, &quot;proxies.sqlite&quot;
+                    )
+                )
+            ):
+                sqliteProxyService = ProxySqliteDB(&quot;proxies.sqlite&quot;)
+                yield loadDelegatesFromXML(
+                    self.config.ProxyLoadFromFile,
+                    sqliteProxyService
+                )
+            else:
+                sqliteProxyService = None
</ins><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><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>+            # # 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>-                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>+            #     # 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><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">             # Process old inbox items
</span><span class="cx">             self.store.setMigrating(True)
</span><span class="cx">             yield self.processInboxItems()
</span><span class="lines">@@ -1065,7 +1160,12 @@
</span><span class="cx">             # Migrate mail tokens from sqlite to store
</span><span class="cx">             yield migrateTokensToStore(self.config.DataRoot, self.store)
</span><span class="cx"> 
</span><ins>+            # Migrate delegate assignments from sqlite to store
+            if sqliteProxyService is None:
+                sqliteProxyService = ProxySqliteDB(&quot;proxies.sqlite&quot;)
+            yield migrateDelegatesToStore(sqliteProxyService, self.store)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def processInboxItems(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1102,14 +1202,14 @@
</span><span class="cx">                         inboxItems.remove(inboxItem)
</span><span class="cx">                         continue
</span><span class="cx"> 
</span><del>-                    record = directory.recordWithUID(uuid)
</del><ins>+                    record = yield directory.recordWithUID(uuid)
</ins><span class="cx">                     if record is None:
</span><span class="cx">                         log.debug(&quot;Ignored inbox item - no record: %s&quot; % (inboxItem,))
</span><span class="cx">                         inboxItems.remove(inboxItem)
</span><span class="cx">                         ignoreUUIDs.add(uuid)
</span><span class="cx">                         continue
</span><span class="cx"> 
</span><del>-                    principal = principalCollection.principalForRecord(record)
</del><ins>+                    principal = yield principalCollection.principalForRecord(record)
</ins><span class="cx">                     if principal is None or not isinstance(principal, DirectoryCalendarPrincipalResource):
</span><span class="cx">                         log.debug(&quot;Ignored inbox item - no principal: %s&quot; % (inboxItem,))
</span><span class="cx">                         inboxItems.remove(inboxItem)
</span><span class="lines">@@ -1117,7 +1217,7 @@
</span><span class="cx">                         continue
</span><span class="cx"> 
</span><span class="cx">                     request = FakeRequest(root, &quot;PUT&quot;, None)
</span><del>-                    request.noAttendeeRefresh = True # tell scheduling to skip refresh
</del><ins>+                    request.noAttendeeRefresh = True  # tell scheduling to skip refresh
</ins><span class="cx">                     request.checkedSACL = True
</span><span class="cx">                     request.authnUser = request.authzUser = element.Principal(
</span><span class="cx">                         element.HRef.fromString(&quot;/principals/__uids__/%s/&quot; % (uuid,))
</span><span class="lines">@@ -1156,8 +1256,10 @@
</span><span class="cx">                                         uri
</span><span class="cx">                                     )
</span><span class="cx">                                 except Exception, e:
</span><del>-                                    log.error(&quot;Error processing inbox item: %s (%s)&quot;
-                                        % (inboxItem, e))
</del><ins>+                                    log.error(
+                                        &quot;Error processing inbox item: %s (%s)&quot;
+                                        % (inboxItem, e)
+                                    )
</ins><span class="cx">                             else:
</span><span class="cx">                                 log.debug(&quot;Ignored inbox item - no resource: %s&quot; % (inboxItem,))
</span><span class="cx">                         else:
</span><span class="lines">@@ -1194,8 +1296,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def processInboxItem(self, root, directory, principal, request, inbox,
-        inboxItem, uuid, uri):
</del><ins>+    def processInboxItem(
+        self, root, directory, principal, request, inbox,
+        inboxItem, uuid, uri
+    ):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Run an individual inbox item through implicit scheduling and remove
</span><span class="cx">         the inbox item.
</span><span class="lines">@@ -1207,8 +1311,10 @@
</span><span class="cx"> 
</span><span class="cx">         ownerPrincipal = principal
</span><span class="cx">         cua = &quot;urn:uuid:%s&quot; % (uuid,)
</span><del>-        owner = LocalCalendarUser(cua, ownerPrincipal,
-            inbox, ownerPrincipal.scheduleInboxURL())
</del><ins>+        owner = LocalCalendarUser(
+            cua, ownerPrincipal,
+            inbox, ownerPrincipal.scheduleInboxURL()
+        )
</ins><span class="cx"> 
</span><span class="cx">         calendar = yield inboxItem.iCalendar()
</span><span class="cx">         if calendar.mainType() is not None:
</span><span class="lines">@@ -1226,14 +1332,16 @@
</span><span class="cx">                 originator = calendar.getOrganizer()
</span><span class="cx"> 
</span><span class="cx">             principalCollection = directory.principalCollection
</span><del>-            originatorPrincipal = principalCollection.principalForCalendarUserAddress(originator)
</del><ins>+            originatorPrincipal = yield principalCollection.principalForCalendarUserAddress(originator)
</ins><span class="cx">             originator = LocalCalendarUser(originator, originatorPrincipal)
</span><span class="cx">             recipients = (owner,)
</span><span class="cx"> 
</span><span class="cx">             scheduler = DirectScheduler(request, inboxItem)
</span><span class="cx">             # Process inbox item
</span><del>-            yield scheduler.doSchedulingViaPUT(originator, recipients, calendar,
-                internal_request=False, noAttendeeRefresh=True)
</del><ins>+            yield scheduler.doSchedulingViaPUT(
+                originator, recipients, calendar,
+                internal_request=False, noAttendeeRefresh=True
+            )
</ins><span class="cx">         else:
</span><span class="cx">             log.warn(&quot;Removing invalid inbox item: %s&quot; % (uri,))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5twistedcaldavutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/twistedcaldav/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -495,37 +495,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def normalizationLookup(cuaddr, principalFunction, config):
-    &quot;&quot;&quot;
-    Lookup function to be passed to ical.normalizeCalendarUserAddresses.
-    Returns a tuple of (Full name, guid, and calendar user address list)
-    for the given cuaddr.  The principalFunction is called to retrieve the
-    principal for the cuaddr.
-    &quot;&quot;&quot;
-    try:
-        principal = principalFunction(cuaddr)
-    except Exception, e:
-        log.debug(&quot;Lookup of %s failed: %s&quot; % (cuaddr, e))
-        principal = None
</del><span class="cx"> 
</span><del>-    if principal is None:
-        return (None, None, None)
-    else:
-        rec = principal.record
-
-        # RFC5545 syntax does not allow backslash escaping in
-        # parameter values. A double-quote is thus not allowed
-        # in a parameter value except as the start/end delimiters.
-        # Single quotes are allowed, so we convert any double-quotes
-        # to single-quotes.
-        fullName = rec.fullName.replace('&quot;', &quot;'&quot;)
-
-        cuas = principal.record.calendarUserAddresses
-
-        return (fullName, rec.guid, cuas)
-
-
-
</del><span class="cx"> def bestAcceptType(accepts, allowedTypes):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Given a set of Accept headers and the set of types the server can return, determine the best choice
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingcaldavschedulerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/caldav/scheduler.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/caldav/scheduler.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/caldav/scheduler.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -85,13 +85,14 @@
</span><span class="cx">             ))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def checkOriginator(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Check the validity of the Originator header. Extract the corresponding principal.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Verify that Originator is a valid calendar user
</span><del>-        originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
</del><ins>+        originatorPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
</ins><span class="cx">         if originatorPrincipal is None:
</span><span class="cx">             # Local requests MUST have a principal.
</span><span class="cx">             log.error(&quot;Could not find principal for originator: %s&quot; % (self.originator,))
</span><span class="lines">@@ -122,7 +123,7 @@
</span><span class="cx">         results = []
</span><span class="cx">         for recipient in self.recipients:
</span><span class="cx">             # Get the principal resource for this recipient
</span><del>-            principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
</del><ins>+            principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
</ins><span class="cx"> 
</span><span class="cx">             # If no principal we may have a remote recipient but we should check whether
</span><span class="cx">             # the address is one that ought to be on our server and treat that as a missing
</span><span class="lines">@@ -161,7 +162,7 @@
</span><span class="cx">         # Verify that the ORGANIZER's cu address maps to a valid user
</span><span class="cx">         organizer = self.calendar.getOrganizer()
</span><span class="cx">         if organizer:
</span><del>-            organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
</del><ins>+            organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
</ins><span class="cx">             if organizerPrincipal:
</span><span class="cx">                 if organizerPrincipal.calendarsEnabled():
</span><span class="cx"> 
</span><span class="lines">@@ -225,6 +226,7 @@
</span><span class="cx">             ))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def checkAttendeeAsOriginator(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
</span><span class="lines">@@ -232,7 +234,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Attendee's Outbox MUST be the request URI
</span><del>-        attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
</del><ins>+        attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
</ins><span class="cx">         if attendeePrincipal:
</span><span class="cx">             if self.doingPOST is not None and attendeePrincipal.uid != self.originator_uid:
</span><span class="cx">                 log.error(&quot;ATTENDEE in calendar data does not match owner of Outbox: %s&quot; % (self.calendar,))
</span><span class="lines">@@ -257,11 +259,11 @@
</span><span class="cx"> 
</span><span class="cx">         # Prevent spoofing of ORGANIZER with specific METHODs when local
</span><span class="cx">         if self.isiTIPRequest:
</span><del>-            self.checkOrganizerAsOriginator()
</del><ins>+            return self.checkOrganizerAsOriginator()
</ins><span class="cx"> 
</span><span class="cx">         # Prevent spoofing when doing reply-like METHODs
</span><span class="cx">         else:
</span><del>-            self.checkAttendeeAsOriginator()
</del><ins>+            return self.checkAttendeeAsOriginator()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def finalChecks(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingfreebusypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/freebusy.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/freebusy.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/freebusy.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -47,6 +47,8 @@
</span><span class="cx"> 
</span><span class="cx"> fbcacher = Memcacher(&quot;FBCache&quot;, pickle=True)
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class FBCacheEntry(object):
</span><span class="cx"> 
</span><span class="cx">     CACHE_DAYS_FLOATING_ADJUST = 1
</span><span class="lines">@@ -212,12 +214,12 @@
</span><span class="cx">     # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy
</span><span class="cx"> 
</span><span class="cx">     # May need organizer principal
</span><del>-    organizer_record = calresource.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
</del><ins>+    organizer_record = (yield calresource.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
</ins><span class="cx">     organizer_uid = organizer_record.uid if organizer_record else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     # Free busy is per-user
</span><span class="cx">     attendee_uid = calresource.viewerHome().uid()
</span><del>-    attendee_record = calresource.directoryService().recordWithUID(attendee_uid)
</del><ins>+    attendee_record = yield calresource.directoryService().recordWithUID(attendee_uid.decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">     # Get the timezone property from the collection.
</span><span class="cx">     tz = calresource.getTimezone()
</span><span class="lines">@@ -237,7 +239,7 @@
</span><span class="cx">         authz_record = organizer_record
</span><span class="cx">         if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid:
</span><span class="cx">             authz_uid = calresource._txn._authz_uid
</span><del>-            authz_record = calresource.directoryService().recordWithUID(authz_uid)
</del><ins>+            authz_record = yield calresource.directoryService().recordWithUID(authz_uid.decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">         # Check if attendee is also the organizer or the delegate doing the request
</span><span class="cx">         if attendee_uid in (organizer_uid, authz_uid):
</span><span class="lines">@@ -335,7 +337,7 @@
</span><span class="cx">                 if excludeuid:
</span><span class="cx">                     # See if we have a UID match
</span><span class="cx">                     if (excludeuid == uid):
</span><del>-                        test_record = calresource.directoryService().recordWithCalendarUserAddress(test_organizer) if test_organizer else None
</del><ins>+                        test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer)) if test_organizer else None
</ins><span class="cx">                         test_uid = test_record.uid if test_record else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">                         # Check that ORGANIZER's match (security requirement)
</span><span class="lines">@@ -395,7 +397,7 @@
</span><span class="cx">                 # See if we have a UID match
</span><span class="cx">                 if (excludeuid == uid):
</span><span class="cx">                     test_organizer = calendar.getOrganizer()
</span><del>-                    test_record = calresource.principalForCalendarUserAddress(test_organizer) if test_organizer else None
</del><ins>+                    test_record = (yield calresource.principalForCalendarUserAddress(test_organizer)) if test_organizer else None
</ins><span class="cx">                     test_uid = test_record.principalUID() if test_record else &quot;&quot;
</span><span class="cx"> 
</span><span class="cx">                     # Check that ORGANIZER's match (security requirement)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingimipinboundpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/imip/inbound.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/imip/inbound.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/imip/inbound.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -329,9 +329,12 @@
</span><span class="cx">                 toAddr = organizer[7:]
</span><span class="cx">             elif organizer.startswith(&quot;urn:uuid:&quot;):
</span><span class="cx">                 guid = organizer[9:]
</span><del>-                record = self.directory.recordWithGUID(guid)
-                if record and record.emailAddresses:
-                    toAddr = list(record.emailAddresses)[0]
</del><ins>+                record = yield self.directory.recordWithGUID(guid)
+                try:
+                    if record and record.emailAddresses:
+                        toAddr = list(record.emailAddresses)[0]
+                except AttributeError:
+                    pass
</ins><span class="cx"> 
</span><span class="cx">             if toAddr is None:
</span><span class="cx">                 log.error(&quot;Don't have an email address for the organizer; &quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingimplicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/implicit.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/implicit.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/implicit.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -297,7 +297,7 @@
</span><span class="cx">         organizer_scheduling = (yield self.isOrganizerScheduling())
</span><span class="cx">         if organizer_scheduling:
</span><span class="cx">             self.state = &quot;organizer&quot;
</span><del>-        elif self.isAttendeeScheduling():
</del><ins>+        elif (yield self.isAttendeeScheduling()):
</ins><span class="cx">             self.state = &quot;attendee&quot;
</span><span class="cx">         elif self.organizer:
</span><span class="cx">             # There is an ORGANIZER that is not this user but no ATTENDEE property for
</span><span class="lines">@@ -365,7 +365,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get some useful information from the calendar
</span><span class="cx">         yield self.extractCalendarData()
</span><del>-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</del><ins>+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</ins><span class="cx">         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
</span><span class="cx"> 
</span><span class="cx">         # Originator is the organizer in this case
</span><span class="lines">@@ -447,7 +447,7 @@
</span><span class="cx">             self.calendar = calendar_old
</span><span class="cx"> 
</span><span class="cx">         yield self.extractCalendarData()
</span><del>-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</del><ins>+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</ins><span class="cx">         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
</span><span class="cx"> 
</span><span class="cx">         # Originator is the organizer in this case
</span><span class="lines">@@ -479,7 +479,7 @@
</span><span class="cx">         # Get some useful information from the calendar
</span><span class="cx">         yield self.extractCalendarData()
</span><span class="cx"> 
</span><del>-        self.attendeePrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
</del><ins>+        self.attendeePrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx">         self.originator = self.attendee = self.attendeePrincipal.canonicalCalendarUserAddress()
</span><span class="cx"> 
</span><span class="cx">         result = (yield self.scheduleWithOrganizer())
</span><span class="lines">@@ -491,7 +491,7 @@
</span><span class="cx">     def extractCalendarData(self):
</span><span class="cx"> 
</span><span class="cx">         # Get the originator who is the owner of the calendar resource being modified
</span><del>-        self.originatorPrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
</del><ins>+        self.originatorPrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">         # Pick the canonical CUA:
</span><span class="cx">         self.originator = self.originatorPrincipal.canonicalCalendarUserAddress()
</span><span class="lines">@@ -555,7 +555,7 @@
</span><span class="cx">             returnValue(False)
</span><span class="cx"> 
</span><span class="cx">         # Organizer must map to a valid principal
</span><del>-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</del><ins>+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</ins><span class="cx">         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
</span><span class="cx">         if not self.organizerPrincipal:
</span><span class="cx">             returnValue(False)
</span><span class="lines">@@ -567,21 +567,22 @@
</span><span class="cx">         returnValue(True)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def isAttendeeScheduling(self):
</span><span class="cx"> 
</span><span class="cx">         # First must have organizer property
</span><span class="cx">         if not self.organizer:
</span><del>-            return False
</del><ins>+            returnValue(False)
</ins><span class="cx"> 
</span><span class="cx">         # Check to see whether any attendee is the owner
</span><span class="cx">         for attendee in self.attendees:
</span><del>-            attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+            attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">             if attendeePrincipal and attendeePrincipal.uid == self.calendar_home.uid():
</span><span class="cx">                 self.attendee = attendee
</span><span class="cx">                 self.attendeePrincipal = attendeePrincipal
</span><del>-                return True
</del><ins>+                returnValue(True)
</ins><span class="cx"> 
</span><del>-        return False
</del><ins>+        returnValue(False)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def makeScheduler(self):
</span><span class="lines">@@ -632,7 +633,7 @@
</span><span class="cx">                 self.calendar.sequenceInSync(self.oldcalendar)
</span><span class="cx"> 
</span><span class="cx">             # Significant change
</span><del>-            no_change, self.changed_rids, self.needs_action_rids, reinvites, recurrence_reschedule, status_cancelled, only_status = self.isOrganizerChangeInsignificant()
</del><ins>+            no_change, self.changed_rids, self.needs_action_rids, reinvites, recurrence_reschedule, status_cancelled, only_status = yield self.isOrganizerChangeInsignificant()
</ins><span class="cx">             if no_change:
</span><span class="cx">                 if reinvites:
</span><span class="cx">                     log.debug(&quot;Implicit - organizer '{organizer}' is re-inviting UID: '{uid}', attendees: {attendees}&quot;, organizer=self.organizer, uid=self.uid, attendees=&quot;, &quot;.join(reinvites))
</span><span class="lines">@@ -720,6 +721,7 @@
</span><span class="cx">                 pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def isOrganizerChangeInsignificant(self):
</span><span class="cx"> 
</span><span class="cx">         rids = None
</span><span class="lines">@@ -802,15 +804,16 @@
</span><span class="cx">                     only_status = False
</span><span class="cx"> 
</span><span class="cx">             if checkOrganizerValue:
</span><ins>+                @inlineCallbacks
</ins><span class="cx">                 def _normalizeCUAddress(addr):
</span><span class="cx">                     if not addr.startswith(&quot;urn:uuid&quot;):
</span><del>-                        principal = self.calendar_home.directoryService().recordWithCalendarUserAddress(addr)
</del><ins>+                        principal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(addr)
</ins><span class="cx">                         if principal is not None:
</span><span class="cx">                             addr = principal.canonicalCalendarUserAddress()
</span><del>-                    return addr
</del><ins>+                    returnValue(addr)
</ins><span class="cx"> 
</span><del>-                oldOrganizer = _normalizeCUAddress(self.oldcalendar.getOrganizer())
-                newOrganizer = _normalizeCUAddress(self.calendar.getOrganizer())
</del><ins>+                oldOrganizer = yield _normalizeCUAddress(self.oldcalendar.getOrganizer())
+                newOrganizer = yield _normalizeCUAddress(self.calendar.getOrganizer())
</ins><span class="cx">                 if oldOrganizer != newOrganizer:
</span><span class="cx">                     log.error(&quot;Cannot change ORGANIZER: UID:{uid}&quot;, uid=self.uid)
</span><span class="cx">                     raise HTTPError(ErrorResponse(
</span><span class="lines">@@ -828,7 +831,10 @@
</span><span class="cx">                 except KeyError:
</span><span class="cx">                     pass
</span><span class="cx"> 
</span><del>-        return no_change, rids, date_changed_rids, reinvites, recurrence_reschedule, status_cancelled, only_status
</del><ins>+        returnValue((
+            no_change, rids, date_changed_rids, reinvites,
+            recurrence_reschedule, status_cancelled, only_status
+        ))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def findRemovedAttendees(self):
</span><span class="lines">@@ -1043,7 +1049,7 @@
</span><span class="cx">             if attendee.parameterValue(&quot;SCHEDULE-AGENT&quot;, &quot;SERVER&quot;).upper() == &quot;CLIENT&quot;:
</span><span class="cx">                 cuaddr = attendee.value()
</span><span class="cx">                 if cuaddr not in coerced:
</span><del>-                    attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
</del><ins>+                    attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
</ins><span class="cx">                     attendeeAddress = (yield addressmapping.mapper.getCalendarUser(cuaddr, attendeePrincipal))
</span><span class="cx">                     local_attendee = type(attendeeAddress) in (LocalCalendarUser, OtherServerCalendarUser,)
</span><span class="cx">                     coerced[cuaddr] = local_attendee
</span><span class="lines">@@ -1116,7 +1122,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Handle split by not scheduling local attendees
</span><span class="cx">             if self.split_details is not None:
</span><del>-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
</span><span class="cx">                 if type(attendeeAddress) is LocalCalendarUser:
</span><span class="cx">                     continue
</span><span class="lines">@@ -1173,7 +1179,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Handle split by not scheduling local attendees
</span><span class="cx">             if self.split_details is not None:
</span><del>-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
</span><span class="cx">                 if type(attendeeAddress) is LocalCalendarUser:
</span><span class="cx">                     continue
</span><span class="lines">@@ -1233,7 +1239,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Handle split by not scheduling local attendees
</span><span class="cx">             if self.split_details is not None:
</span><del>-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
</span><span class="cx">                 if type(attendeeAddress) is LocalCalendarUser:
</span><span class="cx">                     continue
</span><span class="lines">@@ -1298,7 +1304,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Handle split by not scheduling local attendees
</span><span class="cx">             if self.split_details is not None:
</span><del>-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
</span><span class="cx">                 if type(attendeeAddress) is LocalCalendarUser:
</span><span class="cx">                     continue
</span><span class="lines">@@ -1516,7 +1522,7 @@
</span><span class="cx">                     oldattendess = self.oldcalendar.getAllUniqueAttendees()
</span><span class="cx">                     found_old = False
</span><span class="cx">                     for attendee in oldattendess:
</span><del>-                        attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</del><ins>+                        attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
</ins><span class="cx">                         if attendeePrincipal and attendeePrincipal.uid == self.calendar_home.uid():
</span><span class="cx">                             found_old = True
</span><span class="cx">                             break
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduledeliverypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/delivery.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/delivery.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/delivery.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -228,7 +228,7 @@
</span><span class="cx">             # Loop over at most 3 redirects
</span><span class="cx">             ssl, host, port, path = self.server.details()
</span><span class="cx">             for _ignore in xrange(3):
</span><del>-                self._prepareRequest(host, port)
</del><ins>+                yield self._prepareRequest(host, port)
</ins><span class="cx">                 response = (yield self._processRequest(ssl, host, port, path))
</span><span class="cx">                 if response.code not in (responsecode.MOVED_PERMANENTLY, responsecode.TEMPORARY_REDIRECT,):
</span><span class="cx">                     break
</span><span class="lines">@@ -334,16 +334,18 @@
</span><span class="cx">         returnValue(iostr.getvalue())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _prepareRequest(self, host, port):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Setup the request for sending. We might need to do this several times
</span><span class="cx">         whilst following redirects.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component, method = self._prepareData()
-        self._prepareHeaders(host, port, component, method)
</del><ins>+        component, method = (yield self._prepareData())
+        yield self._prepareHeaders(host, port, component, method)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _prepareHeaders(self, host, port, component, method):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Always generate a new set of headers because the Host may varying during redirects,
</span><span class="lines">@@ -357,7 +359,7 @@
</span><span class="cx">         # The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
</span><span class="cx">         originator = self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee
</span><span class="cx">         if self.server.unNormalizeAddresses:
</span><del>-            originator = normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
</del><ins>+            originator = yield normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
</ins><span class="cx">         self.headers.addRawHeader(&quot;Originator&quot;, utf8String(originator))
</span><span class="cx">         self.sign_headers.append(&quot;Originator&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -399,6 +401,7 @@
</span><span class="cx">             self.sign_headers.append(&quot;Authorization&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _prepareData(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Prepare data via normalization etc. Only need to do this once even when
</span><span class="lines">@@ -411,7 +414,7 @@
</span><span class="cx">             normalizedCalendar = self.scheduler.calendar.duplicate()
</span><span class="cx">             self.original_organizer = normalizedCalendar.getOrganizer()
</span><span class="cx">             if self.server.unNormalizeAddresses:
</span><del>-                normalizedCalendar.normalizeCalendarUserAddresses(
</del><ins>+                yield normalizedCalendar.normalizeCalendarUserAddresses(
</ins><span class="cx">                     normalizationLookup,
</span><span class="cx">                     self.scheduler.txn.directoryService().recordWithCalendarUserAddress,
</span><span class="cx">                     toUUID=False)
</span><span class="lines">@@ -423,12 +426,12 @@
</span><span class="cx">             component = normalizedCalendar.mainType()
</span><span class="cx">             method = normalizedCalendar.propertyValue(&quot;METHOD&quot;)
</span><span class="cx">             self.data = str(normalizedCalendar)
</span><del>-            return component, method
</del><ins>+            returnValue(component, method)
</ins><span class="cx">         else:
</span><span class="cx">             cal = Component.fromString(self.data)
</span><span class="cx">             component = cal.mainType()
</span><span class="cx">             method = cal.propertyValue(&quot;METHOD&quot;)
</span><del>-            return component, method
</del><ins>+            returnValue(component, method)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduleresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -99,12 +99,13 @@
</span><span class="cx">         return False
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForCalendarUserAddress(self, address):
</span><span class="cx">         for principalCollection in self.principalCollections():
</span><del>-            principal = principalCollection.principalForCalendarUserAddress(address)
</del><ins>+            principal = yield principalCollection.principalForCalendarUserAddress(address)
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
-        return None
</del><ins>+                returnValue(principal)
+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def render(self, request):
</span><span class="lines">@@ -353,11 +354,13 @@
</span><span class="cx">             davxml.Privilege(caldavxml.ScheduleDeliver()),
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        return davxml.ACL(
-            # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(*privs),
+                    davxml.Protected(),
+                ),
+            )
</ins><span class="cx">         )
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingischeduleschedulerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/scheduler.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/scheduler.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/ischedule/scheduler.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -186,7 +186,7 @@
</span><span class="cx">         # Normalize recipient addresses
</span><span class="cx">         results = []
</span><span class="cx">         for recipient in recipients:
</span><del>-            normalized = normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
</del><ins>+            normalized = yield normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx">             self.recipientsNormalizationMap[normalized] = recipient
</span><span class="cx">             results.append(normalized)
</span><span class="cx">         recipients = results
</span><span class="lines">@@ -205,7 +205,7 @@
</span><span class="cx">         if not self.checkForFreeBusy():
</span><span class="cx">             # Need to normalize the calendar data and recipient values to keep those in sync,
</span><span class="cx">             # as we might later try to match them
</span><del>-            self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
</del><ins>+            return self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def checkAuthorization(self):
</span><span class="lines">@@ -226,7 +226,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # For remote requests we do not allow the originator to be a local user or one within our domain.
</span><del>-        originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
</del><ins>+        originatorPrincipal = (yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator))
</ins><span class="cx">         localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
</span><span class="cx">         if originatorPrincipal or localUser:
</span><span class="cx">             if originatorPrincipal.thisServer():
</span><span class="lines">@@ -367,7 +367,7 @@
</span><span class="cx">         # Verify that the ORGANIZER's cu address does not map to a valid user
</span><span class="cx">         organizer = self.calendar.getOrganizer()
</span><span class="cx">         if organizer:
</span><del>-            organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
</del><ins>+            organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
</ins><span class="cx">             if organizerPrincipal:
</span><span class="cx">                 if organizerPrincipal.thisServer():
</span><span class="cx">                     log.error(&quot;Invalid ORGANIZER in calendar data: %s&quot; % (self.calendar,))
</span><span class="lines">@@ -408,7 +408,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Attendee cannot be local.
</span><del>-        attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
</del><ins>+        attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
</ins><span class="cx">         if attendeePrincipal:
</span><span class="cx">             if attendeePrincipal.thisServer():
</span><span class="cx">                 log.error(&quot;Invalid ATTENDEE in calendar data: %s&quot; % (self.calendar,))
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingitippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/itip.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/itip.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/itip.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -42,6 +42,7 @@
</span><span class="cx">     &quot;iTipGenerator&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class iTipProcessing(object):
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/processing.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/processing.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/processing.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -36,6 +36,7 @@
</span><span class="cx"> from txdav.caldav.datastore.scheduling.work import ScheduleRefreshWork, \
</span><span class="cx">     ScheduleAutoReplyWork
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState, ComponentRemoveState
</span><ins>+from txdav.who.idirectory import AutoScheduleMode
</ins><span class="cx"> 
</span><span class="cx"> import collections
</span><span class="cx"> import hashlib
</span><span class="lines">@@ -58,6 +59,8 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class ImplicitProcessorException(Exception):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, msg):
</span><span class="lines">@@ -431,9 +434,9 @@
</span><span class="cx"> 
</span><span class="cx">             # Handle auto-reply behavior
</span><span class="cx">             organizer = normalizeCUAddr(self.message.getOrganizer())
</span><del>-            if self.recipient.principal.canAutoSchedule(organizer=organizer):
</del><ins>+            if (yield self.recipient.principal.canAutoSchedule(organizer=organizer)):
</ins><span class="cx">                 # auto schedule mode can depend on who the organizer is
</span><del>-                mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</del><ins>+                mode = yield self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</ins><span class="cx">                 send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, mode))
</span><span class="cx"> 
</span><span class="cx">                 # Only store inbox item when reply is not sent or always for users
</span><span class="lines">@@ -464,9 +467,9 @@
</span><span class="cx"> 
</span><span class="cx">                 # Handle auto-reply behavior
</span><span class="cx">                 organizer = normalizeCUAddr(self.message.getOrganizer())
</span><del>-                if self.recipient.principal.canAutoSchedule(organizer=organizer) and not hasattr(self.txn, &quot;doing_attendee_refresh&quot;):
</del><ins>+                if (yield self.recipient.principal.canAutoSchedule(organizer=organizer)) and not hasattr(self.txn, &quot;doing_attendee_refresh&quot;):
</ins><span class="cx">                     # auto schedule mode can depend on who the organizer is
</span><del>-                    mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</del><ins>+                    mode = yield self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</ins><span class="cx">                     send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, mode))
</span><span class="cx"> 
</span><span class="cx">                     # Only store inbox item when reply is not sent or always for users
</span><span class="lines">@@ -545,7 +548,7 @@
</span><span class="cx">             # inbox item on them even if auto-schedule is true so that they get a notification
</span><span class="cx">             # of the cancel.
</span><span class="cx">             organizer = normalizeCUAddr(self.message.getOrganizer())
</span><del>-            autoprocessed = self.recipient.principal.canAutoSchedule(organizer=organizer)
</del><ins>+            autoprocessed = yield self.recipient.principal.canAutoSchedule(organizer=organizer)
</ins><span class="cx">             store_inbox = not autoprocessed or self.recipient.principal.getCUType() == &quot;INDIVIDUAL&quot;
</span><span class="cx"> 
</span><span class="cx">             # Check to see if this is a cancel of the entire event
</span><span class="lines">@@ -604,19 +607,28 @@
</span><span class="cx">         @param calendar: the iTIP message to process
</span><span class="cx">         @type calendar: L{Component}
</span><span class="cx">         @param automode: the auto-schedule mode for the recipient
</span><del>-        @type automode: C{str}
</del><ins>+        @type automode: L{txdav.who.idirectory.AutoScheduleMode}
</ins><span class="cx"> 
</span><span class="cx">         @return: C{tuple} of C{bool}, C{bool}, C{str} indicating whether changes were made, whether the inbox item
</span><span class="cx">             should be added, and the new PARTSTAT.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-
</del><span class="cx">         # First ignore the none mode
</span><del>-        if automode == &quot;none&quot;:
</del><ins>+        if automode == AutoScheduleMode.none:
</ins><span class="cx">             returnValue((False, True, &quot;&quot;,))
</span><del>-        elif not automode or automode == &quot;default&quot;:
-            automode = config.Scheduling.Options.AutoSchedule.DefaultMode
</del><ins>+        elif not automode:
+            automode = {
+                &quot;none&quot;: AutoScheduleMode.none,
+                &quot;accept-always&quot;: AutoScheduleMode.accept,
+                &quot;decline-always&quot;: AutoScheduleMode.decline,
+                &quot;accept-if-free&quot;: AutoScheduleMode.acceptIfFree,
+                &quot;decline-if-busy&quot;: AutoScheduleMode.declineIfBusy,
+                &quot;automatic&quot;: AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+            }.get(
+                config.Scheduling.Options.AutoSchedule.DefaultMode,
+                AutoScheduleMode.acceptIfFreeDeclineIfBusy
+            )
</ins><span class="cx"> 
</span><del>-        log.debug(&quot;ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s&quot; % (self.recipient.cuaddr, self.uid, automode,))
</del><ins>+        log.debug(&quot;ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s&quot; % (self.recipient.cuaddr, self.uid, automode.name,))
</ins><span class="cx"> 
</span><span class="cx">         cuas = self.recipient.principal.calendarUserAddresses
</span><span class="cx"> 
</span><span class="lines">@@ -704,13 +716,19 @@
</span><span class="cx">         partstat_counts = collections.defaultdict(int)
</span><span class="cx">         for instance in instances.instances.itervalues():
</span><span class="cx">             if instance.partstat == &quot;NEEDS-ACTION&quot; and instance.active:
</span><del>-                if automode == &quot;accept-always&quot;:
</del><ins>+                if automode == AutoScheduleMode.accept:
</ins><span class="cx">                     freePartstat = busyPartstat = &quot;ACCEPTED&quot;
</span><del>-                elif automode == &quot;decline-always&quot;:
</del><ins>+                elif automode == AutoScheduleMode.decline:
</ins><span class="cx">                     freePartstat = busyPartstat = &quot;DECLINED&quot;
</span><span class="cx">                 else:
</span><del>-                    freePartstat = &quot;ACCEPTED&quot; if automode in (&quot;accept-if-free&quot;, &quot;automatic&quot;,) else &quot;NEEDS-ACTION&quot;
-                    busyPartstat = &quot;DECLINED&quot; if automode in (&quot;decline-if-busy&quot;, &quot;automatic&quot;,) else &quot;NEEDS-ACTION&quot;
</del><ins>+                    freePartstat = &quot;ACCEPTED&quot; if automode in (
+                        AutoScheduleMode.acceptIfFree,
+                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+                    ) else &quot;NEEDS-ACTION&quot;
+                    busyPartstat = &quot;DECLINED&quot; if automode in (
+                        AutoScheduleMode.declineIfBusy,
+                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+                    ) else &quot;NEEDS-ACTION&quot;
</ins><span class="cx">                 instance.partstat = freePartstat if instance.free else busyPartstat
</span><span class="cx">             partstat_counts[instance.partstat] += 1
</span><span class="cx"> 
</span><span class="lines">@@ -901,7 +919,7 @@
</span><span class="cx"> 
</span><span class="cx">         # We only need to fix data that already exists
</span><span class="cx">         if recipient_resource is not None:
</span><del>-            if originator_calendar.mainType() != None:
</del><ins>+            if originator_calendar.mainType() is not None:
</ins><span class="cx">                 yield self.writeCalendarResource(None, recipient_resource, originator_calendar)
</span><span class="cx">             else:
</span><span class="cx">                 yield self.deleteCalendarResource(recipient_resource)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingschedulerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/scheduler.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/scheduler.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/scheduler.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -162,7 +162,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.calendar = calendar
</span><del>-        self.preProcessCalendarData()
</del><ins>+        yield self.preProcessCalendarData()
</ins><span class="cx"> 
</span><span class="cx">         if self.logItems is not None:
</span><span class="cx">             self.logItems[&quot;recipients&quot;] = len(recipients)
</span><span class="lines">@@ -550,7 +550,7 @@
</span><span class="cx">         results = []
</span><span class="cx">         for recipient in self.recipients:
</span><span class="cx">             # Get the principal resource for this recipient
</span><del>-            principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
</del><ins>+            principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
</ins><span class="cx"> 
</span><span class="cx">             # If no principal we may have a remote recipient but we should check whether
</span><span class="cx">             # the address is one that ought to be on our server and treat that as a missing
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreschedulingworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/work.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/work.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/scheduling/work.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -45,6 +45,8 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class ScheduleWorkMixin(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Base class for common schedule work item behavior.
</span><span class="lines">@@ -189,7 +191,7 @@
</span><span class="cx">         try:
</span><span class="cx">             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
</span><span class="cx">             resource = (yield home.objectResourceWithID(self.resourceID))
</span><del>-            organizerPrincipal = home.directoryService().recordWithUID(home.uid())
</del><ins>+            organizerPrincipal = yield home.directoryService().recordWithUID(home.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx">             organizer = organizerPrincipal.canonicalCalendarUserAddress()
</span><span class="cx">             calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
</span><span class="cx">             calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
</span><span class="lines">@@ -311,7 +313,7 @@
</span><span class="cx">         try:
</span><span class="cx">             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
</span><span class="cx">             resource = (yield home.objectResourceWithID(self.resourceID))
</span><del>-            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
</del><ins>+            attendeePrincipal = yield home.directoryService().recordWithUID(home.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx">             attendee = attendeePrincipal.canonicalCalendarUserAddress()
</span><span class="cx">             calendar = (yield resource.componentForUser())
</span><span class="cx">             organizer = calendar.validOrganizerForScheduling()
</span><span class="lines">@@ -336,6 +338,7 @@
</span><span class="cx">             self._dequeued()
</span><span class="cx"> 
</span><span class="cx">         except Exception, e:
</span><ins>+            # FIXME: calendar may not be set here!
</ins><span class="cx">             log.debug(&quot;ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=calendar.resourceUID(), err=str(e))
</span><span class="cx">             raise
</span><span class="cx">         except:
</span><span class="lines">@@ -381,7 +384,7 @@
</span><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
</span><del>-            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
</del><ins>+            attendeePrincipal = yield home.directoryService().recordWithUID(home.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx">             attendee = attendeePrincipal.canonicalCalendarUserAddress()
</span><span class="cx">             calendar = Component.fromString(self.icalendarText)
</span><span class="cx">             organizer = calendar.validOrganizerForScheduling()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/sql.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/sql.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/sql.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1915,14 +1915,14 @@
</span><span class="cx"> 
</span><span class="cx">             # Normalize the calendar user addresses once we know we have valid
</span><span class="cx">             # calendar data
</span><del>-            component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
</del><ins>+            yield component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
</ins><span class="cx"> 
</span><span class="cx">         # Possible timezone stripping
</span><span class="cx">         if config.EnableTimezonesByReference:
</span><span class="cx">             component.stripKnownTimezones()
</span><span class="cx"> 
</span><span class="cx">         # Check location/resource organizer requirement
</span><del>-        self.validLocationResourceOrganizer(component, inserting, internal_state)
</del><ins>+        yield self.validLocationResourceOrganizer(component, inserting, internal_state)
</ins><span class="cx"> 
</span><span class="cx">         # Check access
</span><span class="cx">         if config.EnablePrivateEvents:
</span><span class="lines">@@ -1986,13 +1986,14 @@
</span><span class="cx">                     raise TooManyAttendeesError(&quot;Attendee list size %d is larger than allowed limit %d&quot; % (attendeeListLength, config.MaxAttendeesPerInstance))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def validLocationResourceOrganizer(self, component, inserting, internal_state):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if internal_state == ComponentUpdateState.NORMAL:
</span><del>-            originatorPrincipal = self.calendar().ownerHome().directoryRecord()
</del><ins>+            originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
</ins><span class="cx">             cutype = originatorPrincipal.getCUType() if originatorPrincipal is not None else &quot;INDIVIDUAL&quot;
</span><span class="cx">             organizer = component.getOrganizer()
</span><span class="cx"> 
</span><span class="lines">@@ -2009,9 +2010,9 @@
</span><span class="cx"> 
</span><span class="cx">                 # Find current principal and update modified by details
</span><span class="cx">                 if self._txn._authz_uid is not None:
</span><del>-                    authz = self.directoryService().recordWithUID(self._txn._authz_uid)
</del><ins>+                    authz = yield self.directoryService().recordWithUID(self._txn._authz_uid.decode(&quot;utf-8&quot;))
</ins><span class="cx">                     prop = Property(&quot;X-CALENDARSERVER-MODIFIED-BY&quot;, authz.canonicalCalendarUserAddress())
</span><del>-                    prop.setParameter(&quot;CN&quot;, authz.displayName())
</del><ins>+                    prop.setParameter(&quot;CN&quot;, authz.displayName)
</ins><span class="cx">                     for candidate in authz.calendarUserAddresses:
</span><span class="cx">                         if candidate.startswith(&quot;mailto:&quot;):
</span><span class="cx">                             prop.setParameter(&quot;EMAIL&quot;, candidate[7:])
</span><span class="lines">@@ -2108,7 +2109,7 @@
</span><span class="cx">                 log.debug(&quot;Organizer and attendee properties were entirely removed by the client. Restoring existing properties.&quot;)
</span><span class="cx"> 
</span><span class="cx">                 # Get the originator who is the owner of the calendar resource being modified
</span><del>-                originatorPrincipal = self.calendar().ownerHome().directoryRecord()
</del><ins>+                originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
</ins><span class="cx">                 originatorAddresses = originatorPrincipal.calendarUserAddresses
</span><span class="cx"> 
</span><span class="cx">                 for component in calendar.subcomponents():
</span><span class="lines">@@ -2145,7 +2146,7 @@
</span><span class="cx">                 log.debug(&quot;Sync COMPLETED property change.&quot;)
</span><span class="cx"> 
</span><span class="cx">                 # Get the originator who is the owner of the calendar resource being modified
</span><del>-                originatorPrincipal = self.calendar().ownerHome().directoryRecord()
</del><ins>+                originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
</ins><span class="cx">                 originatorAddresses = originatorPrincipal.calendarUserAddresses
</span><span class="cx"> 
</span><span class="cx">                 for component in calendar.subcomponents():
</span><span class="lines">@@ -2264,6 +2265,7 @@
</span><span class="cx">             self._componentChanged = True
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def addStructuredLocation(self, component):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Scan the component for ROOM attendees; if any are associated with an
</span><span class="lines">@@ -2271,34 +2273,33 @@
</span><span class="cx">         X-APPLE-STRUCTURED-LOCATION property and update the LOCATION property
</span><span class="cx">         to contain the name and street address.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        dir = self.directoryService()
</ins><span class="cx">         for sub in component.subcomponents():
</span><span class="cx">             for attendee in sub.getAllAttendeeProperties():
</span><span class="cx">                 if attendee.parameterValue(&quot;CUTYPE&quot;) == &quot;ROOM&quot;:
</span><span class="cx">                     value = attendee.value()
</span><del>-                    if value.startswith(&quot;urn:uuid:&quot;):
-                        guid = value[9:]
-                        loc = self.directoryService().recordWithGUID(guid)
-                        if loc is not None:
-                            guid = loc.extras.get(&quot;associatedAddress&quot;,
-                                None)
-                            if guid is not None:
-                                addr = self.directoryService().recordWithGUID(guid)
-                                if addr is not None:
-                                    street = addr.extras.get(&quot;streetAddress&quot;, &quot;&quot;)
-                                    geo = addr.extras.get(&quot;geo&quot;, &quot;&quot;)
-                                    if street and geo:
-                                        title = attendee.parameterValue(&quot;CN&quot;)
-                                        params = {
-                                            &quot;X-ADDRESS&quot; : street,
-                                            &quot;X-APPLE-RADIUS&quot; : &quot;71&quot;,
-                                            &quot;X-TITLE&quot; : title,
-                                        }
-                                        structured = Property(&quot;X-APPLE-STRUCTURED-LOCATION&quot;,
-                                            &quot;geo:%s&quot; % (geo,), params=params,
-                                            valuetype=Value.VALUETYPE_URI)
-                                        sub.replaceProperty(structured)
-                                        sub.replaceProperty(Property(&quot;LOCATION&quot;,
-                                            &quot;%s\n%s&quot; % (title, street)))
</del><ins>+                    loc = yield dir.recordWithCalendarUserAddress(value)
+                    if loc is not None:
+                        uid = getattr(loc, &quot;associatedAddress&quot;, &quot;&quot;)
+                        if uid:
+                            addr = yield dir.recordWithUID(uid)
+                            if addr is not None:
+                                street = getattr(addr, &quot;streetAddress&quot;, &quot;&quot;)
+                                geo = getattr(addr, &quot;geographicLocation&quot;, &quot;&quot;)
+                                if street and geo:
+                                    title = attendee.parameterValue(&quot;CN&quot;)
+                                    params = {
+                                        &quot;X-ADDRESS&quot;: street,
+                                        &quot;X-APPLE-RADIUS&quot;: &quot;71&quot;,
+                                        &quot;X-TITLE&quot;: title,
+                                    }
+                                    structured = Property(&quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+                                        &quot;geo:%s&quot; % (geo.encode(&quot;utf-8&quot;),), params=params,
+                                        valuetype=Value.VALUETYPE_URI)
+                                    sub.replaceProperty(structured)
+                                    newLocProperty = Property(&quot;LOCATION&quot;,
+                                        &quot;%s\n%s&quot; % (title, street.encode(&quot;utf-8&quot;)))
+                                    sub.replaceProperty(newLocProperty)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2539,7 +2540,7 @@
</span><span class="cx">             self.processAlarms(component, inserting)
</span><span class="cx"> 
</span><span class="cx">             # Process structured location
</span><del>-            self.addStructuredLocation(component)
</del><ins>+            yield self.addStructuredLocation(component)
</ins><span class="cx"> 
</span><span class="cx">             # Do scheduling
</span><span class="cx">             implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state))
</span><span class="lines">@@ -3760,9 +3761,9 @@
</span><span class="cx">             raise InvalidSplit()
</span><span class="cx"> 
</span><span class="cx">         # Cannot be attendee
</span><del>-        ownerPrincipal = self.calendar().ownerHome().directoryRecord()
</del><ins>+        ownerPrincipal = yield self.calendar().ownerHome().directoryRecord()
</ins><span class="cx">         organizer = component.getOrganizer()
</span><del>-        organizerPrincipal = self.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
</del><ins>+        organizerPrincipal = (yield self.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
</ins><span class="cx">         if organizer is not None and organizerPrincipal.uid != ownerPrincipal.uid:
</span><span class="cx">             raise InvalidSplit()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestattachmentsaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/accounts.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/accounts.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/accounts.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,19 +18,19 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;!DOCTYPE accounts SYSTEM &quot;../../../conf/auth/accounts.dtd&quot;&gt;
</span><span class="cx"> 
</span><del>-&lt;accounts realm=&quot;/Search&quot;&gt;
-  &lt;user&gt;
</del><ins>+&lt;directory realm=&quot;/Search&quot;&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;home1&lt;/uid&gt;
</span><del>-    &lt;guid&gt;home1&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;home1&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;home1&lt;/password&gt;
</span><del>-    &lt;name&gt;Example User 1&lt;/name&gt;
-    &lt;email-address&gt;home1@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-  &lt;user&gt;
</del><ins>+    &lt;full-name&gt;Example User 1&lt;/full-name&gt;
+    &lt;email&gt;home1@example.com&lt;/email&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;user&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;home2&lt;/uid&gt;
</span><del>-    &lt;guid&gt;home2&lt;/guid&gt;
</del><ins>+    &lt;short-name&gt;home2&lt;/short-name&gt;
</ins><span class="cx">     &lt;password&gt;home2&lt;/password&gt;
</span><del>-    &lt;name&gt;Example User 2&lt;/name&gt;
-    &lt;email-address&gt;home2@example.com&lt;/email-address&gt;
-  &lt;/user&gt;
-&lt;/accounts&gt;
</del><ins>+    &lt;full-name&gt;Example User 2&lt;/full-name&gt;
+    &lt;email&gt;home2@example.com&lt;/email&gt;
+  &lt;/record&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestattachmentsresourcesxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/resources.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/resources.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/attachments/resources.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,4 +1,4 @@
</span><span class="cx"> &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
</span><span class="cx"> 
</span><del>-&lt;accounts realm=&quot;/Search&quot;&gt;
-&lt;/accounts&gt;
</del><ins>+&lt;directory realm=&quot;/Search&quot;&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretesttest_attachmentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/test_attachments.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/test_attachments.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/test_attachments.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -14,7 +14,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-from calendarserver.tap.util import directoryFromConfig
</del><ins>+from txdav.who.util import directoryFromConfig
</ins><span class="cx"> 
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from pycalendar.value import Value
</span><span class="lines">@@ -49,7 +49,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> storePath = FilePath(__file__).parent().child(&quot;calendar_store&quot;)
</span><del>-homeRoot = storePath.child(&quot;ho&quot;).child(&quot;me&quot;).child(&quot;home1&quot;)
</del><ins>+homeRoot = storePath.child(&quot;ho&quot;).child(&quot;me&quot;).child(u&quot;home1&quot;)
</ins><span class="cx"> cal1Root = homeRoot.child(&quot;calendar_1&quot;)
</span><span class="cx"> 
</span><span class="cx"> calendar1_objectNames = [
</span><span class="lines">@@ -597,7 +597,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -609,7 +609,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertFalse(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -648,7 +648,7 @@
</span><span class="cx">         self.assertTrue(os.path.exists(apath1))
</span><span class="cx">         self.assertTrue(os.path.exists(apath2))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -661,7 +661,7 @@
</span><span class="cx">         self.assertFalse(os.path.exists(apath1))
</span><span class="cx">         self.assertFalse(os.path.exists(apath2))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -743,7 +743,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -755,7 +755,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -767,7 +767,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertFalse(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -1131,7 +1131,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -1143,7 +1143,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertFalse(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -1178,7 +1178,7 @@
</span><span class="cx">         self.assertTrue(os.path.exists(apath1))
</span><span class="cx">         self.assertTrue(os.path.exists(apath2))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -1191,7 +1191,7 @@
</span><span class="cx">         self.assertFalse(os.path.exists(apath1))
</span><span class="cx">         self.assertFalse(os.path.exists(apath2))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -1218,7 +1218,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -1230,7 +1230,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertNotEqual(quota, 0)
</span><span class="lines">@@ -1242,7 +1242,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertFalse(os.path.exists(apath))
</span><span class="cx"> 
</span><del>-        home = (yield self.transactionUnderTest().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield self.transactionUnderTest().calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         quota = (yield home.quotaUsedBytes())
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.assertEqual(quota, 0)
</span><span class="lines">@@ -1363,7 +1363,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     requirements = {
</span><del>-        &quot;home1&quot; : {
</del><ins>+        u&quot;home1&quot; : {
</ins><span class="cx">             &quot;calendar1&quot; : {
</span><span class="cx">                 &quot;1.1.ics&quot; : (PLAIN_ICS % {&quot;year&quot;: now, &quot;uid&quot;: &quot;1.1&quot;, }, metadata,),
</span><span class="cx">                 &quot;1.2.ics&quot; : (ATTACHMENT_ICS % {&quot;year&quot;: now, &quot;uid&quot;: &quot;1.2&quot;, &quot;userid&quot;: &quot;user01&quot;, &quot;dropboxid&quot;: &quot;1.2&quot;}, metadata,),
</span><span class="lines">@@ -1372,7 +1372,7 @@
</span><span class="cx">                 &quot;1.5.ics&quot; : (ATTACHMENT_ICS % {&quot;year&quot;: now, &quot;uid&quot;: &quot;1.5&quot;, &quot;userid&quot;: &quot;user01&quot;, &quot;dropboxid&quot;: &quot;1.4&quot;}, metadata,),
</span><span class="cx">             }
</span><span class="cx">         },
</span><del>-        &quot;home2&quot; : {
</del><ins>+        u&quot;home2&quot; : {
</ins><span class="cx">             &quot;calendar2&quot; : {
</span><span class="cx">                 &quot;2-2.1.ics&quot; : (PLAIN_ICS % {&quot;year&quot;: now, &quot;uid&quot;: &quot;2-2.1&quot;, }, metadata,),
</span><span class="cx">                 &quot;2-2.2.ics&quot; : (ATTACHMENT_ICS % {&quot;year&quot;: now, &quot;uid&quot;: &quot;2-2.2&quot;, &quot;userid&quot;: &quot;user02&quot;, &quot;dropboxid&quot;: &quot;2.2&quot;}, metadata,),
</span><span class="lines">@@ -1488,16 +1488,16 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Add the full set of attachments to be used for testing.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_1.txt&quot;)
-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_2.txt&quot;)
-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, &quot;1.3&quot;, &quot;attach_1_3.txt&quot;)
-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, &quot;1.4&quot;, &quot;attach_1_4.txt&quot;)
-        yield self._addAttachmentProperty(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
</del><ins>+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_1.txt&quot;)
+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_2.txt&quot;)
+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, &quot;1.3&quot;, &quot;attach_1_3.txt&quot;)
+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, &quot;1.4&quot;, &quot;attach_1_4.txt&quot;)
+        yield self._addAttachmentProperty(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
</ins><span class="cx"> 
</span><del>-        yield self._addAttachment(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, &quot;2.2&quot;, &quot;attach_2_2.txt&quot;)
-        yield self._addAttachmentProperty(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, &quot;1.3&quot;, &quot;home1&quot;, &quot;attach_1_3.txt&quot;)
-        yield self._addAttachmentProperty(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
-        yield self._addAttachmentProperty(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
</del><ins>+        yield self._addAttachment(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, &quot;2.2&quot;, &quot;attach_2_2.txt&quot;)
+        yield self._addAttachmentProperty(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, &quot;1.3&quot;, &quot;home1&quot;, &quot;attach_1_3.txt&quot;)
+        yield self._addAttachmentProperty(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
+        yield self._addAttachmentProperty(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, &quot;1.4&quot;, &quot;home1&quot;, &quot;attach_1_4.txt&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1586,7 +1586,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test L{txdav.caldav.datastore.sql.DropboxAttachment.convertToManaged} converts properly to a ManagedAttachment.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2.txt&quot;)
</del><ins>+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2.txt&quot;)
</ins><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx"> 
</span><span class="lines">@@ -1616,11 +1616,11 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test L{txdav.caldav.datastore.sql.ManagedAttachment.newReference} creates a new managed attachment reference.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, &quot;1.4&quot;, &quot;attach_1_4.txt&quot;)
</del><ins>+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, &quot;1.4&quot;, &quot;attach_1_4.txt&quot;)
</ins><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx"> 
</span><del>-        home = (yield txn.calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield txn.calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
</span><span class="cx">         event4 = (yield calendar.calendarObjectWithName(&quot;1.4.ics&quot;))
</span><span class="cx">         event5 = (yield calendar.calendarObjectWithName(&quot;1.5.ics&quot;))
</span><span class="lines">@@ -1664,12 +1664,12 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test L{txdav.caldav.datastore.sql.CalendarObject.convertAttachments} re-writes calendar data.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_1.txt&quot;)
-        yield self._addAttachment(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_2.txt&quot;)
</del><ins>+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_1.txt&quot;)
+        yield self._addAttachment(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, &quot;1.2&quot;, &quot;attach_1_2_2.txt&quot;)
</ins><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx"> 
</span><del>-        home = (yield txn.calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield txn.calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
</span><span class="cx">         event = (yield calendar.calendarObjectWithName(&quot;1.2.ics&quot;))
</span><span class="cx"> 
</span><span class="lines">@@ -1688,7 +1688,7 @@
</span><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx"> 
</span><del>-        home = (yield txn.calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield txn.calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
</span><span class="cx">         event = (yield calendar.calendarObjectWithName(&quot;1.2.ics&quot;))
</span><span class="cx"> 
</span><span class="lines">@@ -1712,7 +1712,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Convert the second dropbox attachment
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield txn.calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
</span><span class="cx">         event = (yield calendar.calendarObjectWithName(&quot;1.2.ics&quot;))
</span><span class="cx">         dattachment = (yield DropBoxAttachment.load(txn, &quot;1.2.dropbox&quot;, &quot;attach_1_2_2.txt&quot;))
</span><span class="lines">@@ -1722,7 +1722,7 @@
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        home = (yield txn.calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+        home = (yield txn.calendarHomeWithUID(u&quot;home1&quot;))
</ins><span class="cx">         calendar = (yield home.calendarWithName(&quot;calendar1&quot;))
</span><span class="cx">         event = (yield calendar.calendarObjectWithName(&quot;1.2.ics&quot;))
</span><span class="cx">         component = (yield event.componentForUser()).mainComponent()
</span><span class="lines">@@ -1760,14 +1760,14 @@
</span><span class="cx">         yield calstore._upgradeDropbox(txn, &quot;1.2.dropbox&quot;)
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><del>-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</del><ins>+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1784,14 +1784,14 @@
</span><span class="cx">         yield calstore._upgradeDropbox(txn, &quot;1.3.dropbox&quot;)
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><del>-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</del><ins>+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1808,14 +1808,14 @@
</span><span class="cx">         yield calstore._upgradeDropbox(txn, &quot;1.4.dropbox&quot;)
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><del>-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
-        yield self._verifyNoConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</del><ins>+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
+        yield self._verifyNoConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1830,14 +1830,14 @@
</span><span class="cx">         calstore = CalendarStoreFeatures(self._sqlCalendarStore)
</span><span class="cx">         yield calstore.upgradeToManagedAttachments(2)
</span><span class="cx"> 
</span><del>-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyConversion(&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
-        yield self._verifyConversion(&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</del><ins>+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.2.ics&quot;, (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.4.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyConversion(u&quot;home1&quot;, &quot;calendar1&quot;, &quot;1.5.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.2.ics&quot;, (&quot;attach_2_2.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar2&quot;, &quot;2-2.3.ics&quot;, (&quot;attach_1_3.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.2.ics&quot;, (&quot;attach_1_4.txt&quot;,))
+        yield self._verifyConversion(u&quot;home2&quot;, &quot;calendar3&quot;, &quot;2-3.3.ics&quot;, (&quot;attach_1_4.txt&quot;,))
</ins><span class="cx"> 
</span><span class="cx">         # Paths do not exist
</span><span class="cx">         for path in self.paths.values():
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/test/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -16,7 +16,7 @@
</span><span class="cx"> ##
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> from twext.python.clsprop import classproperty
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><ins>+from twisted.internet.defer import inlineCallbacks, succeed
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Store test utility functions
</span><span class="lines">@@ -40,7 +40,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recordWithCalendarUserAddress(self, cuaddr):
</span><del>-        return self.recordsByCUA.get(cuaddr)
</del><ins>+        return succeed(self.recordsByCUA.get(cuaddr))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def addRecord(self, record):
</span><span class="lines">@@ -63,30 +63,46 @@
</span><span class="cx">         cutype=&quot;INDIVIDUAL&quot;,
</span><span class="cx">         thisServer=True,
</span><span class="cx">         server=None,
</span><del>-        extras={},
</del><ins>+        associatedAddress=None,
+        streetAddress=None,
+        geographicLocation=None
</ins><span class="cx">     ):
</span><span class="cx"> 
</span><del>-        super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName, thisServer, server, extras=extras)
</del><ins>+        super(TestCalendarStoreDirectoryRecord, self).__init__(
+            uid, shortNames, fullName, thisServer, server,
+        )
</ins><span class="cx">         self.calendarUserAddresses = calendarUserAddresses
</span><span class="cx">         self.cutype = cutype
</span><ins>+        self.associatedAddress = associatedAddress
+        self.streetAddress = streetAddress
+        self.geographicLocation = geographicLocation
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def canonicalCalendarUserAddress(self):
</span><del>-        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
</del><ins>+        &quot;&quot;&quot;
+            Return a CUA for this record, preferring in this order:
+            urn:uuid: form
+            mailto: form
+            /principals/__uids__/ form
+            first in calendarUserAddresses list (sorted)
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        sortedCuas = sorted(self.calendarUserAddresses)
</ins><span class="cx"> 
</span><ins>+        for prefix in (
+            &quot;urn:uuid:&quot;,
+            &quot;mailto:&quot;,
+            &quot;/principals/__uids__/&quot;
+        ):
+            for candidate in sortedCuas:
+                if candidate.startswith(prefix):
+                    return candidate
+
+        # fall back to using the first one
+        return sortedCuas[0]
+
+
</ins><span class="cx">     def calendarsEnabled(self):
</span><span class="cx">         return True
</span><span class="cx"> 
</span><span class="lines">@@ -109,15 +125,15 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def canAutoSchedule(self, organizer):
</span><del>-        return False
</del><ins>+        return succeed(False)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getAutoScheduleMode(self, organizer):
</span><del>-        return &quot;automatic&quot;
</del><ins>+        return succeed(&quot;automatic&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def isProxyFor(self, other):
</span><del>-        return False
</del><ins>+        return succeed(False)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -161,31 +177,23 @@
</span><span class="cx">     # Structured Locations
</span><span class="cx">     directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx">         &quot;il1&quot;, (&quot;il1&quot;,), &quot;1 Infinite Loop&quot;, [],
</span><del>-        extras={
-            &quot;geo&quot; : &quot;37.331741,-122.030333&quot;,
-            &quot;streetAddress&quot; : &quot;1 Infinite Loop, Cupertino, CA 95014&quot;,
-        }
</del><ins>+        geographicLocation=&quot;37.331741,-122.030333&quot;,
+        streetAddress=&quot;1 Infinite Loop, Cupertino, CA 95014&quot;
</ins><span class="cx">     ))
</span><span class="cx">     directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx">         &quot;il2&quot;, (&quot;il2&quot;,), &quot;2 Infinite Loop&quot;, [],
</span><del>-        extras={
-            &quot;geo&quot; : &quot;37.332633,-122.030502&quot;,
-            &quot;streetAddress&quot; : &quot;2 Infinite Loop, Cupertino, CA 95014&quot;,
-        }
</del><ins>+        geographicLocation=&quot;37.332633,-122.030502&quot;,
+        streetAddress=&quot;2 Infinite Loop, Cupertino, CA 95014&quot;
</ins><span class="cx">     ))
</span><span class="cx">     directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx">         &quot;room1&quot;, (&quot;room1&quot;,), &quot;Conference Room One&quot;,
</span><span class="cx">         frozenset((&quot;urn:uuid:room1&quot;,)),
</span><del>-        extras={
-            &quot;associatedAddress&quot; : &quot;il1&quot;,
-        }
</del><ins>+        associatedAddress=&quot;il1&quot;,
</ins><span class="cx">     ))
</span><span class="cx">     directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx">         &quot;room2&quot;, (&quot;room2&quot;,), &quot;Conference Room Two&quot;,
</span><span class="cx">         frozenset((&quot;urn:uuid:room2&quot;,)),
</span><del>-        extras={
-            &quot;associatedAddress&quot; : &quot;il2&quot;,
-        }
</del><ins>+        associatedAddress=&quot;il2&quot;,
</ins><span class="cx">     ))
</span><span class="cx"> 
</span><span class="cx">     return directory
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavdatastoreutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/datastore/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -100,35 +100,42 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
</ins><span class="cx"> def normalizationLookup(cuaddr, recordFunction, config):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Lookup function to be passed to ical.normalizeCalendarUserAddresses.
</span><del>-    Returns a tuple of (Full name, guid, and calendar user address list)
</del><ins>+    Returns a tuple of (Full name C{str}, guid C{UUID}, and calendar user address list C{str})
</ins><span class="cx">     for the given cuaddr.  The recordFunction is called to retrieve the
</span><span class="cx">     record for the cuaddr.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     try:
</span><del>-        record = recordFunction(cuaddr)
</del><ins>+        record = yield recordFunction(cuaddr)
</ins><span class="cx">     except Exception, e:
</span><span class="cx">         log.debug(&quot;Lookup of %s failed: %s&quot; % (cuaddr, e))
</span><span class="cx">         record = None
</span><span class="cx"> 
</span><span class="cx">     if record is None:
</span><del>-        return (None, None, None)
</del><ins>+        returnValue((None, None, None))
</ins><span class="cx">     else:
</span><ins>+
</ins><span class="cx">         # RFC5545 syntax does not allow backslash escaping in
</span><span class="cx">         # parameter values. A double-quote is thus not allowed
</span><span class="cx">         # in a parameter value except as the start/end delimiters.
</span><span class="cx">         # Single quotes are allowed, so we convert any double-quotes
</span><span class="cx">         # to single-quotes.
</span><del>-        return (
-            record.fullName.replace('&quot;', &quot;'&quot;),
-            record.uid,
-            record.calendarUserAddresses,
</del><ins>+        fullName = record.displayName.replace('&quot;', &quot;'&quot;).encode(&quot;utf-8&quot;)
+        cuas = set(
+            [cua.encode(&quot;utf-8&quot;) for cua in record.calendarUserAddresses]
</ins><span class="cx">         )
</span><ins>+        try:
+            guid = record.guid
+        except AttributeError:
+            guid = None
</ins><span class="cx"> 
</span><ins>+        returnValue((fullName, guid, cuas))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def dropboxIDFromCalendarObject(calendarObject):
</span><span class="cx">     &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcaldavicalendardirectoryservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/icalendardirectoryservice.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/icalendardirectoryservice.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/caldav/icalendardirectoryservice.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -37,8 +37,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the record for the specified calendar user address.
</span><span class="cx"> 
</span><del>-        @return: the record.
-        @rtype: L{ICalendarStoreDirectoryRecord}
</del><ins>+        @return: Deferred resulting in the record.
+        @rtype: L{Deferred} resulting in L{ICalendarStoreDirectoryRecord}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcarddavdatastorequeryfilterpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/carddav/datastore/query/filter.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/carddav/datastore/query/filter.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/carddav/datastore/query/filter.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -136,7 +136,7 @@
</span><span class="cx">                     return not allof
</span><span class="cx">             return allof
</span><span class="cx">         else:
</span><del>-            return True
</del><ins>+            return False
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def valid(self):
</span><span class="lines">@@ -190,9 +190,6 @@
</span><span class="cx">             else:
</span><span class="cx">                 raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
</span><span class="cx"> 
</span><del>-        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
-            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
-
</del><span class="cx">         if xml_element.qname() == (carddav_namespace, &quot;prop-filter&quot;):
</span><span class="cx">             propfilter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
</span><span class="cx">             if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
</span><span class="lines">@@ -200,13 +197,16 @@
</span><span class="cx">         else:
</span><span class="cx">             propfilter_test = &quot;anyof&quot;
</span><span class="cx"> 
</span><ins>+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0) and propfilter_test == &quot;allof&quot;:
+            raise ValueError(&quot;When test is allof, no other tests allowed when CardDAV:is-not-defined is present&quot;)
+
</ins><span class="cx">         self.propfilter_test = propfilter_test
</span><span class="cx">         self.qualifier = qualifier
</span><span class="cx">         self.filters = filters
</span><span class="cx">         self.filter_name = xml_element.attributes[&quot;name&quot;]
</span><span class="cx">         if isinstance(self.filter_name, unicode):
</span><span class="cx">             self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
</span><del>-        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
</del><ins>+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined) or len(filters)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _deserialize(self, data):
</span><span class="lines">@@ -241,25 +241,18 @@
</span><span class="cx">         matches this filter, False otherwise.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined:
-            return True
</del><ins>+        allof = self.propfilter_test == &quot;allof&quot;
+        if self.qualifier and allof != self.qualifier.match(item):
+            return not allof
</ins><span class="cx"> 
</span><del>-        if self.qualifier and not self.qualifier.match(item):
-            return False
-
</del><span class="cx">         if len(self.filters) &gt; 0:
</span><del>-            allof = self.propfilter_test == &quot;allof&quot;
</del><span class="cx">             for filter in self.filters:
</span><span class="cx">                 if allof != filter._match(item):
</span><span class="cx">                     return not allof
</span><span class="cx">             return allof
</span><span class="cx">         else:
</span><del>-            return True
</del><ins>+            return not allof
</ins><span class="cx"> 
</span><del>-
-
</del><span class="cx"> class PropertyFilter (FilterChildBase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Limits a search to specific properties.
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/file.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/file.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/file.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -148,6 +148,10 @@
</span><span class="cx">         return self._directoryService
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def setDirectoryService(self, directoryService):
+        self._directoryService = directoryService
+
+
</ins><span class="cx">     def callWithNewTransactions(self, callback):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Registers a method to be called whenever a new transaction is
</span><span class="lines">@@ -700,7 +704,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def directoryRecord(self):
</span><del>-        return self.directoryService().recordWithUID(self.uid())
</del><ins>+        return self.directoryService().recordWithUID(self.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def retrieveOldShares(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingconduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/conduit.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/conduit.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/conduit.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -76,32 +76,33 @@
</span><span class="cx">         self.store = store
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def validRequst(self, source_guid, destination_guid):
</del><ins>+    @inlineCallbacks
+    def validRequest(self, source_uid, destination_uid):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Verify that the specified GUIDs are valid for the request and return the
</del><ins>+        Verify that the specified uids are valid for the request and return the
</ins><span class="cx">         matching directory records.
</span><span class="cx"> 
</span><del>-        @param source_guid: GUID for the user on whose behalf the request is being made
-        @type source_guid: C{str}
-        @param destination_guid: GUID for the user to whom the request is being sent
-        @type destination_guid: C{str}
</del><ins>+        @param source_uid: UID for the user on whose behalf the request is being made
+        @type source_uid: C{str}
+        @param destination_uid: UID for the user to whom the request is being sent
+        @type destination_uid: C{str}
</ins><span class="cx"> 
</span><del>-        @return: C{tuple} of L{IStoreDirectoryRecord}
</del><ins>+        @return: L{Deferred} resulting in C{tuple} of L{IStoreDirectoryRecord}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        source = self.store.directoryService().recordWithUID(source_guid)
</del><ins>+        source = yield self.store.directoryService().recordWithUID(source_uid)
</ins><span class="cx">         if source is None:
</span><del>-            raise DirectoryRecordNotFoundError(&quot;Cross-pod source: {}&quot;.format(source_guid))
</del><ins>+            raise DirectoryRecordNotFoundError(&quot;Cross-pod source: {}&quot;.format(source_uid))
</ins><span class="cx">         if not source.thisServer():
</span><del>-            raise FailedCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_guid))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_uid))
</ins><span class="cx"> 
</span><del>-        destination = self.store.directoryService().recordWithUID(destination_guid)
</del><ins>+        destination = yield self.store.directoryService().recordWithUID(destination_uid)
</ins><span class="cx">         if destination is None:
</span><del>-            raise DirectoryRecordNotFoundError(&quot;Cross-pod destination: {}&quot;.format(destination_guid))
</del><ins>+            raise DirectoryRecordNotFoundError(&quot;Cross-pod destination: {}&quot;.format(destination_uid))
</ins><span class="cx">         if destination.thisServer():
</span><del>-            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_guid))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_uid))
</ins><span class="cx"> 
</span><del>-        return (source, destination,)
</del><ins>+        returnValue((source, destination,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -166,13 +167,13 @@
</span><span class="cx"> 
</span><span class="cx">         @param homeType: Type of home being shared.
</span><span class="cx">         @type homeType: C{int}
</span><del>-        @param ownerUID: GUID of the sharer.
</del><ins>+        @param ownerUID: UID of the sharer.
</ins><span class="cx">         @type ownerUID: C{str}
</span><span class="cx">         @param ownerID: resource ID of the sharer calendar
</span><span class="cx">         @type ownerID: C{int}
</span><span class="cx">         @param ownerName: owner's name of the sharer calendar
</span><span class="cx">         @type ownerName: C{str}
</span><del>-        @param shareeUID: GUID of the sharee
</del><ins>+        @param shareeUID: UID of the sharee
</ins><span class="cx">         @type shareeUID: C{str}
</span><span class="cx">         @param shareUID: Resource/invite ID for sharee
</span><span class="cx">         @type shareUID: C{str}
</span><span class="lines">@@ -186,7 +187,7 @@
</span><span class="cx">         @type supported_components: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</del><ins>+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;shareinvite&quot;,
</span><span class="lines">@@ -250,17 +251,17 @@
</span><span class="cx"> 
</span><span class="cx">         @param homeType: Type of home being shared.
</span><span class="cx">         @type homeType: C{int}
</span><del>-        @param ownerUID: GUID of the sharer.
</del><ins>+        @param ownerUID: UID of the sharer.
</ins><span class="cx">         @type ownerUID: C{str}
</span><span class="cx">         @param ownerID: resource ID of the sharer calendar
</span><span class="cx">         @type ownerID: C{int}
</span><del>-        @param shareeUID: GUID of the sharee
</del><ins>+        @param shareeUID: UID of the sharee
</ins><span class="cx">         @type shareeUID: C{str}
</span><span class="cx">         @param shareUID: Resource/invite ID for sharee
</span><span class="cx">         @type shareUID: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</del><ins>+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;shareuninvite&quot;,
</span><span class="lines">@@ -313,9 +314,9 @@
</span><span class="cx"> 
</span><span class="cx">         @param homeType: Type of home being shared.
</span><span class="cx">         @type homeType: C{int}
</span><del>-        @param ownerUID: GUID of the sharer.
</del><ins>+        @param ownerUID: UID of the sharer.
</ins><span class="cx">         @type ownerUID: C{str}
</span><del>-        @param shareeUID: GUID of the recipient
</del><ins>+        @param shareeUID: UID of the recipient
</ins><span class="cx">         @type shareeUID: C{str}
</span><span class="cx">         @param shareUID: Resource/invite ID for recipient
</span><span class="cx">         @type shareUID: C{str}
</span><span class="lines">@@ -325,7 +326,7 @@
</span><span class="cx">         @type summary: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</del><ins>+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;sharereply&quot;,
</span><span class="lines">@@ -398,7 +399,7 @@
</span><span class="cx"> 
</span><span class="cx">         actionName = &quot;add-attachment&quot;
</span><span class="cx">         shareeView = objectResource._parentCollection
</span><del>-        action, recipient = self._send(actionName, shareeView, objectResource)
</del><ins>+        action, recipient = yield self._send(actionName, shareeView, objectResource)
</ins><span class="cx">         action[&quot;rids&quot;] = rids
</span><span class="cx">         action[&quot;filename&quot;] = filename
</span><span class="cx">         result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
</span><span class="lines">@@ -458,7 +459,7 @@
</span><span class="cx"> 
</span><span class="cx">         actionName = &quot;update-attachment&quot;
</span><span class="cx">         shareeView = objectResource._parentCollection
</span><del>-        action, recipient = self._send(actionName, shareeView, objectResource)
</del><ins>+        action, recipient = yield self._send(actionName, shareeView, objectResource)
</ins><span class="cx">         action[&quot;managedID&quot;] = managed_id
</span><span class="cx">         action[&quot;filename&quot;] = filename
</span><span class="cx">         result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
</span><span class="lines">@@ -514,7 +515,7 @@
</span><span class="cx"> 
</span><span class="cx">         actionName = &quot;remove-attachment&quot;
</span><span class="cx">         shareeView = objectResource._parentCollection
</span><del>-        action, recipient = self._send(actionName, shareeView, objectResource)
</del><ins>+        action, recipient = yield self._send(actionName, shareeView, objectResource)
</ins><span class="cx">         action[&quot;rids&quot;] = rids
</span><span class="cx">         action[&quot;managedID&quot;] = managed_id
</span><span class="cx">         result = yield self.sendRequest(shareeView._txn, recipient, action)
</span><span class="lines">@@ -557,6 +558,7 @@
</span><span class="cx">     # Sharer data access related apis
</span><span class="cx">     #
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def _send(self, action, parent, child=None):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Base behavior for an operation on a L{CommonHomeChild}.
</span><span class="lines">@@ -570,7 +572,7 @@
</span><span class="cx">         ownerID = parent.external_id()
</span><span class="cx">         shareeUID = parent.viewerHome().uid()
</span><span class="cx"> 
</span><del>-        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</del><ins>+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
</ins><span class="cx"> 
</span><span class="cx">         result = {
</span><span class="cx">             &quot;action&quot;: action,
</span><span class="lines">@@ -581,7 +583,7 @@
</span><span class="cx">         }
</span><span class="cx">         if child is not None:
</span><span class="cx">             result[&quot;resource_id&quot;] = child.id()
</span><del>-        return result, recipient
</del><ins>+        returnValue((result, recipient))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -644,7 +646,7 @@
</span><span class="cx">         @type kwargs: C{dict}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        action, recipient = self._send(actionName, shareeView, objectResource)
</del><ins>+        action, recipient = yield self._send(actionName, shareeView, objectResource)
</ins><span class="cx">         if args is not None:
</span><span class="cx">             action[&quot;arguments&quot;] = args
</span><span class="cx">         if kwargs is not None:
</span><span class="lines">@@ -710,7 +712,7 @@
</span><span class="cx">         servertoserver,
</span><span class="cx">         event_details,
</span><span class="cx">     ):
</span><del>-        action, recipient = self._send(&quot;freebusy&quot;, calresource)
</del><ins>+        action, recipient = yield self._send(&quot;freebusy&quot;, calresource)
</ins><span class="cx">         action[&quot;timerange&quot;] = [timerange.start.getText(), timerange.end.getText()]
</span><span class="cx">         action[&quot;matchtotal&quot;] = matchtotal
</span><span class="cx">         action[&quot;excludeuid&quot;] = excludeuid
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -88,12 +88,13 @@
</span><span class="cx">         return False
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalForCalendarUserAddress(self, address):
</span><span class="cx">         for principalCollection in self.principalCollections():
</span><del>-            principal = principalCollection.principalForCalendarUserAddress(address)
</del><ins>+            principal = yield principalCollection.principalForCalendarUserAddress(address)
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
-        return None
</del><ins>+                returnValue(principal)
+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def render(self, request):
</span><span class="lines">@@ -179,11 +180,13 @@
</span><span class="cx">             davxml.Privilege(davxml.Read()),
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
</del><ins>+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(*privs),
+                    davxml.Protected(),
+                ),
+            )
</ins><span class="cx">         )
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/test/test_conduit.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/test/test_conduit.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/podding/test/test_conduit.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -123,29 +123,41 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def test_validRequst(self):
</del><ins>+    @inlineCallbacks
+    def test_validRequest(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Cross-pod request fails when there is no shared secret header present.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         conduit = PoddingConduit(self.storeUnderTest())
</span><del>-        r1, r2 = conduit.validRequst(&quot;user01&quot;, &quot;puser02&quot;)
</del><ins>+        r1, r2 = yield conduit.validRequest(&quot;user01&quot;, &quot;puser02&quot;)
</ins><span class="cx">         self.assertTrue(r1 is not None)
</span><span class="cx">         self.assertTrue(r2 is not None)
</span><span class="cx"> 
</span><del>-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;bogus01&quot;, &quot;user02&quot;)
-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;user01&quot;, &quot;bogus02&quot;)
-        self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, &quot;user01&quot;, &quot;user02&quot;)
</del><ins>+        self.assertFailure(
+            conduit.validRequest(&quot;bogus01&quot;, &quot;user02&quot;),
+            DirectoryRecordNotFoundError
+        )
</ins><span class="cx"> 
</span><ins>+        self.assertFailure(
+            conduit.validRequest(&quot;user01&quot;, &quot;bogus02&quot;),
+            DirectoryRecordNotFoundError
+        )
</ins><span class="cx"> 
</span><ins>+        self.assertFailure(
+            conduit.validRequest(&quot;user01&quot;, &quot;user02&quot;),
+            FailedCrossPodRequestError
+        )
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class TestConduitToConduit(MultiStoreConduitTest):
</span><span class="cx"> 
</span><span class="cx">     class FakeConduit(PoddingConduit):
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def send_fake(self, txn, ownerUID, shareeUID):
</span><del>-            _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
</del><ins>+            _ignore_owner, sharee = yield self.validRequest(ownerUID, shareeUID)
</ins><span class="cx">             action = {
</span><span class="cx">                 &quot;action&quot;: &quot;fake&quot;,
</span><span class="cx">                 &quot;echo&quot;: &quot;bravo&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/sql.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/sql.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/sql.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -211,6 +211,10 @@
</span><span class="cx">         return self._directoryService
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def setDirectoryService(self, directoryService):
+        self._directoryService = directoryService
+
+
</ins><span class="cx">     def callWithNewTransactions(self, callback):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Registers a method to be called whenever a new transaction is
</span><span class="lines">@@ -925,102 +929,164 @@
</span><span class="cx">     @classproperty
</span><span class="cx">     def _addGroupQuery(cls):
</span><span class="cx">         gr = schema.GROUPS
</span><del>-        return Insert({gr.NAME: Parameter(&quot;name&quot;),
-                       gr.GROUP_GUID: Parameter(&quot;groupGUID&quot;),
-                       gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;)},
-                       Return=gr.GROUP_ID)
</del><ins>+        return Insert(
+            {
+                gr.NAME: Parameter(&quot;name&quot;),
+                gr.GROUP_GUID: Parameter(&quot;groupUID&quot;),
+                gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;)
+            },
+            Return=gr.GROUP_ID
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="cx">     def _updateGroupQuery(cls):
</span><span class="cx">         gr = schema.GROUPS
</span><del>-        return Update({gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;),
-            gr.NAME: Parameter(&quot;name&quot;), gr.MODIFIED: Parameter(&quot;timestamp&quot;)},
-            Where=(gr.GROUP_GUID == Parameter(&quot;groupGUID&quot;)))
</del><ins>+        return Update(
+            {
+                gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;),
+                gr.NAME: Parameter(&quot;name&quot;),
+                gr.MODIFIED:
+                Parameter(&quot;timestamp&quot;)
+            },
+            Where=(gr.GROUP_GUID == Parameter(&quot;groupUID&quot;))
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _groupByGUID(cls):
</del><ins>+    def _groupByUID(cls):
</ins><span class="cx">         gr = schema.GROUPS
</span><del>-        return Select([gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
-                Where=(
-                    gr.GROUP_GUID == Parameter(&quot;groupGUID&quot;)
-                )
-            )
</del><ins>+        return Select(
+            [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH],
+            From=gr,
+            Where=(gr.GROUP_GUID == Parameter(&quot;groupUID&quot;))
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="cx">     def _groupByID(cls):
</span><span class="cx">         gr = schema.GROUPS
</span><del>-        return Select([gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
-                Where=(
-                    gr.GROUP_ID == Parameter(&quot;groupID&quot;)
-                )
-            )
</del><ins>+        return Select(
+            [gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH],
+            From=gr,
+            Where=(gr.GROUP_ID == Parameter(&quot;groupID&quot;))
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="cx">     def _deleteGroup(cls):
</span><span class="cx">         gr = schema.GROUPS
</span><del>-        return Delete(From=gr,
-              Where=(gr.GROUP_ID == Parameter(&quot;groupID&quot;)))
</del><ins>+        return Delete(
+            From=gr,
+            Where=(gr.GROUP_ID == Parameter(&quot;groupID&quot;))
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def addGroup(self, groupGUID, name, membershipHash):
</del><ins>+    def addGroup(self, groupUID, name, membershipHash):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        @type groupGUID: C{UUID}
</del><ins>+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        return self._addGroupQuery.on(self, name=name,
-            groupGUID=str(groupGUID), membershipHash=membershipHash)
</del><ins>+        return self._addGroupQuery.on(
+            self,
+            name=name.encode(&quot;utf-8&quot;),
+            groupUID=groupUID.encode(&quot;utf-8&quot;),
+            membershipHash=membershipHash
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def updateGroup(self, groupGUID, name, membershipHash):
</del><ins>+    def updateGroup(self, groupUID, name, membershipHash):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        @type groupGUID: C{UUID}
</del><ins>+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         timestamp = datetime.datetime.utcnow()
</span><del>-        return self._updateGroupQuery.on(self, name=name,
-            groupGUID=str(groupGUID), timestamp=timestamp,
-            membershipHash=membershipHash)
</del><ins>+        return self._updateGroupQuery.on(
+            self,
+            name=name.encode(&quot;utf-8&quot;),
+            groupUID=groupUID.encode(&quot;utf-8&quot;),
+            timestamp=timestamp,
+            membershipHash=membershipHash
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def groupByGUID(self, groupGUID):
</del><ins>+    def groupByUID(self, groupUID):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        @type groupGUID: C{UUID}
</del><ins>+        Return or create a record for the group UID.
+
+        @type groupUID: C{unicode}
+
+        @return: Deferred firing with tuple of group ID C{str}, group name
+            C{unicode}, and membership hash C{str}
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        results = (yield self._groupByGUID.on(self, groupGUID=str(groupGUID)))
</del><ins>+        results = (
+            yield self._groupByUID.on(
+                self, groupUID=groupUID.encode(&quot;utf-8&quot;)
+            )
+        )
</ins><span class="cx">         if results:
</span><del>-            returnValue(results[0])
</del><ins>+            returnValue((
+                results[0][0],  # group id
+                results[0][1].decode(&quot;utf-8&quot;),  # name
+                results[0][2],  # membership hash
+            ))
</ins><span class="cx">         else:
</span><del>-            savepoint = SavepointAction(&quot;groupByGUID&quot;)
</del><ins>+            savepoint = SavepointAction(&quot;groupByUID&quot;)
</ins><span class="cx">             yield savepoint.acquire(self)
</span><span class="cx">             try:
</span><del>-                yield self.addGroup(groupGUID, &quot;&quot;, &quot;&quot;)
</del><ins>+                yield self.addGroup(groupUID, u&quot;&quot;, &quot;&quot;)
</ins><span class="cx">             except Exception:
</span><span class="cx">                 yield savepoint.rollback(self)
</span><del>-                results = (yield self._groupByGUID.on(self,
-                    groupGUID=str(groupGUID)))
</del><ins>+                results = (
+                    yield self._groupByUID.on(
+                        self, groupUID=groupUID.encode(&quot;utf-8&quot;)
+                    )
+                )
</ins><span class="cx">                 if results:
</span><del>-                    returnValue(results[0])
</del><ins>+                    returnValue((
+                        results[0][0],  # group id
+                        results[0][1].decode(&quot;utf-8&quot;),  # name
+                        results[0][2],  # membership hash
+                    ))
</ins><span class="cx">                 else:
</span><span class="cx">                     raise
</span><span class="cx">             else:
</span><span class="cx">                 yield savepoint.release(self)
</span><del>-                results = (yield self._groupByGUID.on(self,
-                    groupGUID=str(groupGUID)))
</del><ins>+                results = (
+                    yield self._groupByUID.on(
+                        self, groupUID=groupUID.encode(&quot;utf-8&quot;)
+                    )
+                )
</ins><span class="cx">                 if results:
</span><del>-                    returnValue(results[0])
</del><ins>+                    returnValue((
+                        results[0][0],  # group id
+                        results[0][1].decode(&quot;utf-8&quot;),  # name
+                        results[0][2],  # membership hash
+                    ))
</ins><span class="cx">                 else:
</span><span class="cx">                     raise
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def groupByID(self, groupID):
</span><ins>+        &quot;&quot;&quot;
+        Given a group ID, return the group UID, or raise NotFoundError
+
+        @type groupID: C{str}
+        @return: Deferred firing with a tuple of group UID C{unicode},
+            group name C{unicode}, and membership hash C{str}
+        &quot;&quot;&quot;
</ins><span class="cx">         try:
</span><span class="cx">             results = (yield self._groupByID.on(self, groupID=groupID))[0]
</span><span class="cx">             if results:
</span><del>-                results = [UUID(&quot;urn:uuid:&quot; + results[0])] + results[1:]
</del><ins>+                results = (
+                    results[0].decode(&quot;utf-8&quot;),
+                    results[1].decode(&quot;utf-8&quot;),
+                    results[2]
+                )
</ins><span class="cx">             returnValue(results)
</span><span class="cx">         except IndexError:
</span><span class="cx">             raise NotFoundError
</span><span class="lines">@@ -1040,7 +1106,7 @@
</span><span class="cx">         return Insert(
</span><span class="cx">             {
</span><span class="cx">                 gm.GROUP_ID: Parameter(&quot;groupID&quot;),
</span><del>-                gm.MEMBER_GUID: Parameter(&quot;memberGUID&quot;)
</del><ins>+                gm.MEMBER_GUID: Parameter(&quot;memberUID&quot;)
</ins><span class="cx">             }
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -1053,7 +1119,7 @@
</span><span class="cx">             Where=(
</span><span class="cx">                 gm.GROUP_ID == Parameter(&quot;groupID&quot;)
</span><span class="cx">             ).And(
</span><del>-                gm.MEMBER_GUID == Parameter(&quot;memberGUID&quot;)
</del><ins>+                gm.MEMBER_GUID == Parameter(&quot;memberUID&quot;)
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -1072,25 +1138,35 @@
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="cx">     def _selectGroupsForQuery(cls):
</span><ins>+        gr = schema.GROUPS
</ins><span class="cx">         gm = schema.GROUP_MEMBERSHIP
</span><ins>+
</ins><span class="cx">         return Select(
</span><del>-            [gm.GROUP_ID],
-            From=gm,
</del><ins>+            [gr.GROUP_GUID],
+            From=gr,
</ins><span class="cx">             Where=(
</span><del>-                gm.MEMBER_GUID == Parameter(&quot;guid&quot;)
</del><ins>+                gr.GROUP_ID.In(
+                    Select(
+                        [gm.GROUP_ID],
+                        From=gm,
+                        Where=(
+                            gm.MEMBER_GUID == Parameter(&quot;uid&quot;)
+                        )
+                    )
+                )
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def addMemberToGroup(self, memberGUID, groupID):
</del><ins>+    def addMemberToGroup(self, memberUID, groupID):
</ins><span class="cx">         return self._addMemberToGroupQuery.on(
</span><del>-            self, groupID=groupID, memberGUID=str(memberGUID)
</del><ins>+            self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def removeMemberFromGroup(self, memberGUID, groupID):
</del><ins>+    def removeMemberFromGroup(self, memberUID, groupID):
</ins><span class="cx">         return self._removeMemberFromGroupQuery.on(
</span><del>-            self, groupID=groupID, memberGUID=str(memberGUID)
</del><ins>+            self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1110,25 +1186,29 @@
</span><span class="cx">         members = set()
</span><span class="cx">         results = (yield self._selectGroupMembersQuery.on(self, groupID=groupID))
</span><span class="cx">         for row in results:
</span><del>-            members.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            members.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx">         returnValue(members)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def groupsFor(self, guid):
</del><ins>+    def groupsFor(self, uid):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Returns the cached set of GUIDs for the groups this given guid is
</del><ins>+        Returns the cached set of UIDs for the groups this given uid is
</ins><span class="cx">         a member of.
</span><span class="cx"> 
</span><del>-        @param guid: the guid
-        @type guid: C{UUID}
</del><ins>+        @param uid: the uid
+        @type uid: C{unicode}
</ins><span class="cx">         @return: the set of group IDs
</span><span class="cx">         @rtype: a Deferred which fires with a set() of C{int} group IDs
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         groups = set()
</span><del>-        results = (yield self._selectGroupsForQuery.on(self, guid=str(guid)))
</del><ins>+        results = (
+            yield self._selectGroupsForQuery.on(
+                self, uid=uid.encode(&quot;utf-8&quot;)
+            )
+        )
</ins><span class="cx">         for row in results:
</span><del>-            groups.add(row[0])
</del><ins>+            groups.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx">         returnValue(groups)
</span><span class="cx"> 
</span><span class="cx">     # End of Group Members
</span><span class="lines">@@ -1171,6 +1251,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><ins>+    def _removeDelegatesQuery(cls):
+        de = schema.DELEGATES
+        return Delete(
+            From=de,
+            Where=(
+                de.DELEGATOR == Parameter(&quot;delegator&quot;)
+            ).And(
+                de.READ_WRITE == Parameter(&quot;readWrite&quot;)
+            )
+        )
+
+
+    @classproperty
</ins><span class="cx">     def _removeDelegateGroupQuery(cls):
</span><span class="cx">         ds = schema.DELEGATE_GROUPS
</span><span class="cx">         return Delete(
</span><span class="lines">@@ -1186,6 +1279,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><ins>+    def _removeDelegateGroupsQuery(cls):
+        ds = schema.DELEGATE_GROUPS
+        return Delete(
+            From=ds,
+            Where=(
+                ds.DELEGATOR == Parameter(&quot;delegator&quot;)
+            ).And(
+                ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
+            )
+        )
+
+
+    @classproperty
</ins><span class="cx">     def _selectDelegatesQuery(cls):
</span><span class="cx">         de = schema.DELEGATES
</span><span class="cx">         return Select(
</span><span class="lines">@@ -1202,13 +1308,23 @@
</span><span class="cx">     @classproperty
</span><span class="cx">     def _selectDelegateGroupsQuery(cls):
</span><span class="cx">         ds = schema.DELEGATE_GROUPS
</span><ins>+        gr = schema.GROUPS
+
</ins><span class="cx">         return Select(
</span><del>-            [ds.GROUP_ID],
-            From=ds,
</del><ins>+            [gr.GROUP_GUID],
+            From=gr,
</ins><span class="cx">             Where=(
</span><del>-                ds.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
</del><ins>+                gr.GROUP_ID.In(
+                    Select(
+                        [ds.GROUP_ID],
+                        From=ds,
+                        Where=(
+                            ds.DELEGATOR == Parameter(&quot;delegator&quot;)
+                        ).And(
+                            ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
+                        )
+                    )
+                )
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -1320,18 +1436,18 @@
</span><span class="cx">         Adds a row to the DELEGATES table.  The delegate should not be a
</span><span class="cx">         group.  To delegate to a group, call addDelegateGroup() instead.
</span><span class="cx"> 
</span><del>-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
</del><ins>+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
</ins><span class="cx">         @param readWrite: grant read and write access if True, otherwise
</span><span class="cx">             read-only access
</span><span class="cx">         @type readWrite: C{boolean}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._addDelegateQuery.on(
</span><span class="cx">             self,
</span><del>-            delegator=str(delegator),
-            delegate=str(delegate),
</del><ins>+            delegator=delegator.encode(&quot;utf-8&quot;),
+            delegate=delegate.encode(&quot;utf-8&quot;),
</ins><span class="cx">             readWrite=1 if readWrite else 0
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -1342,8 +1458,8 @@
</span><span class="cx">         Adds a row to the DELEGATE_GROUPS table.  The delegate should be a
</span><span class="cx">         group.  To delegate to a person, call addDelegate() instead.
</span><span class="cx"> 
</span><del>-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
</del><ins>+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
</ins><span class="cx">         @param delegateGroupID: the GROUP_ID of the delegate group
</span><span class="cx">         @type delegateGroupID: C{int}
</span><span class="cx">         @param readWrite: grant read and write access if True, otherwise
</span><span class="lines">@@ -1352,7 +1468,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._addDelegateGroupQuery.on(
</span><span class="cx">             self,
</span><del>-            delegator=str(delegator),
</del><ins>+            delegator=delegator.encode(&quot;utf-8&quot;),
</ins><span class="cx">             groupID=delegateGroupID,
</span><span class="cx">             readWrite=1 if readWrite else 0,
</span><span class="cx">             isExternal=1 if isExternal else 0
</span><span class="lines">@@ -1364,29 +1480,47 @@
</span><span class="cx">         Removes a row from the DELEGATES table.  The delegate should not be a
</span><span class="cx">         group.  To remove a delegate group, call removeDelegateGroup() instead.
</span><span class="cx"> 
</span><del>-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
</del><ins>+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
</ins><span class="cx">         @param readWrite: remove read and write access if True, otherwise
</span><span class="cx">             read-only access
</span><span class="cx">         @type readWrite: C{boolean}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._removeDelegateQuery.on(
</span><span class="cx">             self,
</span><del>-            delegator=str(delegator),
-            delegate=str(delegate),
</del><ins>+            delegator=delegator.encode(&quot;utf-8&quot;),
+            delegate=delegate.encode(&quot;utf-8&quot;),
</ins><span class="cx">             readWrite=1 if readWrite else 0
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def removeDelegates(self, delegator, readWrite):
+        &quot;&quot;&quot;
+        Removes all rows for this delegator/readWrite combination from the
+        DELEGATES table.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return self._removeDelegatesQuery.on(
+            self,
+            delegator=delegator.encode(&quot;utf-8&quot;),
+            readWrite=1 if readWrite else 0
+        )
+
+
</ins><span class="cx">     def removeDelegateGroup(self, delegator, delegateGroupID, readWrite):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Removes a row from the DELEGATE_GROUPS table.  The delegate should be a
</span><span class="cx">         group.  To remove a delegate person, call removeDelegate() instead.
</span><span class="cx"> 
</span><del>-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
</del><ins>+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
</ins><span class="cx">         @param delegateGroupID: the GROUP_ID of the delegate group
</span><span class="cx">         @type delegateGroupID: C{int}
</span><span class="cx">         @param readWrite: remove read and write access if True, otherwise
</span><span class="lines">@@ -1395,26 +1529,44 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._removeDelegateGroupQuery.on(
</span><span class="cx">             self,
</span><del>-            delegator=str(delegator),
</del><ins>+            delegator=delegator.encode(&quot;utf-8&quot;),
</ins><span class="cx">             groupID=delegateGroupID,
</span><span class="cx">             readWrite=1 if readWrite else 0
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def removeDelegateGroups(self, delegator, readWrite):
+        &quot;&quot;&quot;
+        Removes all rows for this delegator/readWrite combination from the
+        DELEGATE_GROUPS table.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return self._removeDelegateGroupsQuery.on(
+            self,
+            delegator=delegator.encode(&quot;utf-8&quot;),
+            readWrite=1 if readWrite else 0
+        )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def delegates(self, delegator, readWrite):
</del><ins>+    def delegates(self, delegator, readWrite, expanded=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Returns the GUIDs of all delegates for the given delegator.  If
-        delegate access was granted to any groups, those groups' members
-        (flattened) will be included. No GUIDs of the groups themselves
-        will be returned.
</del><ins>+        Returns the UIDs of all delegates for the given delegator.  If
+        expanded is False, only the direct delegates (users and groups)
+        are returned.  If expanded is True, the expanded membmership is
+        returned, not including the groups themselves.
</ins><span class="cx"> 
</span><del>-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
</del><ins>+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
</ins><span class="cx">         @param readWrite: the access-type to check for; read and write
</span><span class="cx">             access if True, otherwise read-only access
</span><span class="cx">         @type readWrite: C{boolean}
</span><del>-        @returns: the GUIDs of the delegates (for the specified access
</del><ins>+        @returns: the UIDs of the delegates (for the specified access
</ins><span class="cx">             type)
</span><span class="cx">         @rtype: a Deferred resulting in a set
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1424,35 +1576,48 @@
</span><span class="cx">         results = (
</span><span class="cx">             yield self._selectDelegatesQuery.on(
</span><span class="cx">                 self,
</span><del>-                delegator=str(delegator),
</del><ins>+                delegator=delegator.encode(&quot;utf-8&quot;),
</ins><span class="cx">                 readWrite=1 if readWrite else 0
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx">         for row in results:
</span><del>-            delegates.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            delegates.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><del>-        # Finally get those who are in groups which have been delegated to
-        results = (
-            yield self._selectIndirectDelegatesQuery.on(
-                self,
-                delegator=str(delegator),
-                readWrite=1 if readWrite else 0
</del><ins>+        if expanded:
+            # Get those who are in groups which have been delegated to
+            results = (
+                yield self._selectIndirectDelegatesQuery.on(
+                    self,
+                    delegator=delegator.encode(&quot;utf-8&quot;),
+                    readWrite=1 if readWrite else 0
+                )
</ins><span class="cx">             )
</span><del>-        )
-        for row in results:
-            delegates.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            for row in results:
+                delegates.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><ins>+        else:
+            # Get the directly-delegated-to groups
+            results = (
+                yield self._selectDelegateGroupsQuery.on(
+                    self,
+                    delegator=delegator.encode(&quot;utf-8&quot;),
+                    readWrite=1 if readWrite else 0
+                )
+            )
+            for row in results:
+                delegates.add(row[0].decode(&quot;utf-8&quot;))
+
</ins><span class="cx">         returnValue(delegates)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def delegators(self, delegate, readWrite):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Returns the GUIDs of all delegators which have granted access to
</del><ins>+        Returns the UIDs of all delegators which have granted access to
</ins><span class="cx">         the given delegate, either directly or indirectly via groups.
</span><span class="cx"> 
</span><del>-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
</del><ins>+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
</ins><span class="cx">         @param readWrite: the access-type to check for; read and write
</span><span class="cx">             access if True, otherwise read-only access
</span><span class="cx">         @type readWrite: C{boolean}
</span><span class="lines">@@ -1466,24 +1631,24 @@
</span><span class="cx">         results = (
</span><span class="cx">             yield self._selectDirectDelegatorsQuery.on(
</span><span class="cx">                 self,
</span><del>-                delegate=str(delegate),
</del><ins>+                delegate=delegate.encode(&quot;utf-8&quot;),
</ins><span class="cx">                 readWrite=1 if readWrite else 0
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx">         for row in results:
</span><del>-            delegators.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            delegators.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">         # Finally get those who have delegated to groups the delegate
</span><span class="cx">         # is a member of
</span><span class="cx">         results = (
</span><span class="cx">             yield self._selectIndirectDelegatorsQuery.on(
</span><span class="cx">                 self,
</span><del>-                delegate=str(delegate),
</del><ins>+                delegate=delegate.encode(&quot;utf-8&quot;),
</ins><span class="cx">                 readWrite=1 if readWrite else 0
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx">         for row in results:
</span><del>-            delegators.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            delegators.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">         returnValue(delegators)
</span><span class="cx"> 
</span><span class="lines">@@ -1491,11 +1656,11 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def allGroupDelegates(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Return the GUIDs of all groups which have been delegated to.  Useful
</del><ins>+        Return the UIDs of all groups which have been delegated to.  Useful
</ins><span class="cx">         for obtaining the set of groups which need to be synchronized from
</span><span class="cx">         the directory.
</span><span class="cx"> 
</span><del>-        @returns: the GUIDs of all delegated-to groups
</del><ins>+        @returns: the UIDs of all delegated-to groups
</ins><span class="cx">         @rtype: a Deferred resulting in a set
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         gr = schema.GROUPS
</span><span class="lines">@@ -1508,7 +1673,7 @@
</span><span class="cx">         ).on(self))
</span><span class="cx">         delegates = set()
</span><span class="cx">         for row in results:
</span><del>-            delegates.add(UUID(&quot;urn:uuid:&quot; + row[0]))
</del><ins>+            delegates.add(row[0].decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx">         returnValue(delegates)
</span><span class="cx"> 
</span><span class="lines">@@ -1516,22 +1681,22 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def externalDelegates(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Returns a dictionary mapping delegate GUIDs to (read-group, write-group)
</del><ins>+        Returns a dictionary mapping delegate UIDs to (read-group, write-group)
</ins><span class="cx">         tuples, including only those assignments that originated from the
</span><span class="cx">         directory.
</span><span class="cx"> 
</span><del>-        @returns: dictionary mapping delegator guid to (readDelegateGUID,
-            writeDelegateGUID) tuples
</del><ins>+        @returns: dictionary mapping delegator uid to (readDelegateUID,
+            writeDelegateUID) tuples
</ins><span class="cx">         @rtype: a Deferred resulting in a dictionary
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         delegates = {}
</span><span class="cx"> 
</span><span class="cx">         # Get the externally managed delegates (which are all groups)
</span><span class="cx">         results = (yield self._selectExternalDelegateGroupsQuery.on(self))
</span><del>-        for delegator, readDelegateGUID, writeDelegateGUID in results:
-            delegates[UUID(delegator)] = (
-                UUID(readDelegateGUID) if readDelegateGUID else None,
-                UUID(writeDelegateGUID) if writeDelegateGUID else None
</del><ins>+        for delegator, readDelegateUID, writeDelegateUID in results:
+            delegates[delegator.encode(&quot;utf-8&quot;)] = (
+                readDelegateUID.encode(&quot;utf-8&quot;) if readDelegateUID else None,
+                writeDelegateUID.encode(&quot;utf-8&quot;) if writeDelegateUID else None
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">         returnValue(delegates)
</span><span class="lines">@@ -1540,7 +1705,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def assignExternalDelegates(
</span><span class="cx">         self, delegator, readDelegateGroupID, writeDelegateGroupID,
</span><del>-        readDelegateGUID, writeDelegateGUID
</del><ins>+        readDelegateUID, writeDelegateUID
</ins><span class="cx">     ):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Update the external delegate group table so we can quickly identify
</span><span class="lines">@@ -1563,12 +1728,12 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Store new assignments in the external comparison table
</span><del>-        if readDelegateGUID or writeDelegateGUID:
</del><ins>+        if readDelegateUID or writeDelegateUID:
</ins><span class="cx">             readDelegateForDB = (
</span><del>-                str(readDelegateGUID) if readDelegateGUID else &quot;&quot;
</del><ins>+                readDelegateUID.encode(&quot;utf-8&quot;) if readDelegateUID else &quot;&quot;
</ins><span class="cx">             )
</span><span class="cx">             writeDelegateForDB = (
</span><del>-                str(writeDelegateGUID) if writeDelegateGUID else &quot;&quot;
</del><ins>+                writeDelegateUID.encode(&quot;utf-8&quot;) if writeDelegateUID else &quot;&quot;
</ins><span class="cx">             )
</span><span class="cx">             yield self._storeExternalDelegateGroupsPairQuery.on(
</span><span class="cx">                 self,
</span><span class="lines">@@ -2745,6 +2910,9 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def homeWithUID(cls, txn, uid, create=False):
</span><ins>+        &quot;&quot;&quot;
+        @param uid: I'm going to assume uid is utf-8 encoded bytes
+        &quot;&quot;&quot;
</ins><span class="cx">         homeObject = yield cls.makeClass(txn, uid)
</span><span class="cx">         if homeObject is not None:
</span><span class="cx">             returnValue(homeObject)
</span><span class="lines">@@ -2753,7 +2921,7 @@
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><span class="cx">             # Determine if the user is local or external
</span><del>-            record = txn.directoryService().recordWithUID(uid)
</del><ins>+            record = yield txn.directoryService().recordWithUID(uid.decode(&quot;utf-8&quot;))
</ins><span class="cx">             if record is None:
</span><span class="cx">                 raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
</span><span class="cx"> 
</span><span class="lines">@@ -2847,7 +3015,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def directoryRecord(self):
</span><del>-        return self.directoryService().recordWithUID(self.uid())
</del><ins>+        return self.directoryService().recordWithUID(self.uid().decode(&quot;utf-8&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -6748,6 +6916,9 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def notificationsWithUID(cls, txn, uid, create):
</span><ins>+        &quot;&quot;&quot;
+        @param uid: I'm going to assume uid is utf-8 encoded bytes
+        &quot;&quot;&quot;
</ins><span class="cx">         rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid)
</span><span class="cx"> 
</span><span class="cx">         if rows:
</span><span class="lines">@@ -6755,7 +6926,7 @@
</span><span class="cx">             created = False
</span><span class="cx">         elif create:
</span><span class="cx">             # Determine if the user is local or external
</span><del>-            record = txn.directoryService().recordWithUID(uid)
</del><ins>+            record = yield txn.directoryService().recordWithUID(uid.decode(&quot;utf-8&quot;))
</ins><span class="cx">             if record is None:
</span><span class="cx">                 raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/test/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/test/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/common/datastore/test/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -46,7 +46,7 @@
</span><span class="cx"> from twisted.application.service import Service
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import Deferred, inlineCallbacks
</span><del>-from twisted.internet.defer import returnValue
</del><ins>+from twisted.internet.defer import returnValue, succeed
</ins><span class="cx"> from twisted.internet.task import deferLater
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> 
</span><span class="lines">@@ -110,14 +110,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recordWithUID(self, uid):
</span><del>-        return self.records.get(uid)
</del><ins>+        return succeed(self.records.get(uid))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recordWithGUID(self, guid):
</span><span class="cx">         for record in self.records.itervalues():
</span><span class="cx">             if record.guid == guid:
</span><del>-                return record
-        return None
</del><ins>+                return succeed(record)
+        return succeed(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def addRecord(self, record):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpsclientpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/client.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/client.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/client.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,53 +15,90 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> import cPickle as pickle
</span><ins>+import uuid
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.who.directory import DirectoryRecord as BaseDirectoryRecord
</span><span class="cx"> from twext.who.directory import DirectoryService as BaseDirectoryService
</span><del>-from twext.who.idirectory import RecordType
</del><ins>+from twext.who.expression import Operand
+from twext.who.idirectory import RecordType, IDirectoryService
</ins><span class="cx"> import twext.who.idirectory
</span><span class="cx"> from twext.who.util import ConstantsContainer
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.protocol import ClientCreator
</span><span class="cx"> from twisted.protocols import amp
</span><ins>+from twisted.python.constants import Names, NamedConstant
+from txdav.caldav.icalendardirectoryservice import (
+    ICalendarStoreDirectoryRecord
+)
+from txdav.common.idirectoryservice import IStoreDirectoryService
</ins><span class="cx"> from txdav.dps.commands import (
</span><span class="cx">     RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
</span><span class="cx">     RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
</span><ins>+    RecordsMatchingTokensCommand, RecordsMatchingFieldsCommand,
+    MembersCommand, GroupsCommand, SetMembersCommand,
</ins><span class="cx">     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand
</span><span class="cx"> )
</span><ins>+from txdav.who.directory import (
+    CalendarDirectoryRecordMixin, CalendarDirectoryServiceMixin
+)
+import txdav.who.augment
+import txdav.who.delegates
</ins><span class="cx"> import txdav.who.idirectory
</span><ins>+import txdav.who.wiki
</ins><span class="cx"> from zope.interface import implementer
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> ##
</span><span class="cx"> ## Client implementation of Directory Proxy Service
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@implementer(twext.who.idirectory.IDirectoryService)
-class DirectoryService(BaseDirectoryService):
</del><ins>+
+## MOVE2WHO TODOs:
+## SACLs
+## LDAP
+## Tests from old twistedcaldav/directory
+## Cmd line tools
+## Store based directory service (records in the store, i.e.
+##    locations/resources)
+## Separate store for DPS (augments and delegates separate from calendar data)
+## Store autoAcceptGroups in the group db?
+
+@implementer(IDirectoryService, IStoreDirectoryService)
+class DirectoryService(BaseDirectoryService, CalendarDirectoryServiceMixin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Client side of directory proxy
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    # FIXME: somehow these should come from the actual directory:
+
</ins><span class="cx">     recordType = ConstantsContainer(
</span><span class="cx">         (twext.who.idirectory.RecordType,
</span><del>-         txdav.who.idirectory.RecordType)
</del><ins>+         txdav.who.idirectory.RecordType,
+         txdav.who.delegates.RecordType,
+         txdav.who.wiki.RecordType)
</ins><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    fieldName = ConstantsContainer(
+        (twext.who.idirectory.FieldName,
+         txdav.who.idirectory.FieldName,
+         txdav.who.augment.FieldName)
+    )
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def _dictToRecord(self, serializedFields):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        This to be replaced by something awesome
</del><ins>+        Turn a dictionary of fields sent from the server into a directory
+        record
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if not serializedFields:
</span><span class="cx">             return None
</span><span class="cx"> 
</span><ins>+        # print(&quot;FIELDS&quot;, serializedFields)
+
</ins><span class="cx">         fields = {}
</span><span class="cx">         for fieldName, value in serializedFields.iteritems():
</span><span class="cx">             try:
</span><span class="lines">@@ -70,8 +107,21 @@
</span><span class="cx">                 # unknown field
</span><span class="cx">                 pass
</span><span class="cx">             else:
</span><del>-                fields[field] = value
-        fields[self.fieldName.recordType] = self.recordType.user
</del><ins>+                valueType = self.fieldName.valueType(field)
+                if valueType in (unicode, bool):
+                    fields[field] = value
+                elif valueType is uuid.UUID:
+                    fields[field] = uuid.UUID(value)
+                elif issubclass(valueType, Names):
+                    if value is not None:
+                        fields[field] = field.valueType.lookupByName(value)
+                    else:
+                        fields[field] = None
+                elif issubclass(valueType, NamedConstant):
+                    if fieldName == &quot;recordType&quot;:  # Is there a better way?
+                        fields[field] = self.recordType.lookupByName(value)
+
+        # print(&quot;AFTER:&quot;, fields)
</ins><span class="cx">         return DirectoryRecord(self, fields)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -92,14 +142,17 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _getConnection(self):
</span><del>-        # TODO: make socket patch configurable
</del><span class="cx">         # TODO: reconnect if needed
</span><span class="cx"> 
</span><del>-        # path = config.DirectoryProxy.SocketPath
-        path = &quot;data/Logs/state/directory-proxy.sock&quot;
</del><ins>+        # FIXME:
+        from twistedcaldav.config import config
+        path = config.DirectoryProxy.SocketPath
+        # path = &quot;data/Logs/state/directory-proxy.sock&quot;
</ins><span class="cx">         if getattr(self, &quot;_connection&quot;, None) is None:
</span><span class="cx">             log.debug(&quot;Creating connection&quot;)
</span><del>-            connection = (yield ClientCreator(reactor, amp.AMP).connectUNIX(path))
</del><ins>+            connection = (
+                yield ClientCreator(reactor, amp.AMP).connectUNIX(path)
+            )
</ins><span class="cx">             self._connection = connection
</span><span class="cx">         else:
</span><span class="cx">             log.debug(&quot;Already have connection&quot;)
</span><span class="lines">@@ -127,15 +180,31 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recordWithShortName(self, recordType, shortName):
</span><ins>+        # MOVE2WHO
+        # temporary hack until we can fix all callers not to pass strings:
+        if isinstance(recordType, (str, unicode)):
+            recordType = self.recordType.lookupByName(recordType)
+
+        # 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><del>-            recordType=recordType.description.encode(&quot;utf-8&quot;),
</del><ins>+            recordType=recordType.name.encode(&quot;utf-8&quot;),
</ins><span class="cx">             shortName=shortName.encode(&quot;utf-8&quot;)
</span><span class="cx">         )
</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">@@ -147,7 +216,7 @@
</span><span class="cx">         return self._call(
</span><span class="cx">             RecordWithGUIDCommand,
</span><span class="cx">             self._processSingleRecord,
</span><del>-            guid=guid.encode(&quot;utf-8&quot;)
</del><ins>+            guid=str(guid)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -155,7 +224,7 @@
</span><span class="cx">         return self._call(
</span><span class="cx">             RecordsWithRecordTypeCommand,
</span><span class="cx">             self._processMultipleRecords,
</span><del>-            recordType=recordType.description.encode(&quot;utf-8&quot;)
</del><ins>+            recordType=recordType.name.encode(&quot;utf-8&quot;)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -163,13 +232,62 @@
</span><span class="cx">         return self._call(
</span><span class="cx">             RecordsWithEmailAddressCommand,
</span><span class="cx">             self._processMultipleRecords,
</span><del>-            emailAddress=emailAddress
</del><ins>+            emailAddress=emailAddress.encode(&quot;utf-8&quot;)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def recordsMatchingTokens(
+        self, tokens, context=None, limitResults=50, timeoutSeconds=10
+    ):
+        return self._call(
+            RecordsMatchingTokensCommand,
+            self._processMultipleRecords,
+            tokens=[t.encode(&quot;utf-8&quot;) for t in tokens],
+            context=context
+        )
</ins><span class="cx"> 
</span><del>-class DirectoryRecord(BaseDirectoryRecord):
</del><span class="cx"> 
</span><ins>+    def recordsMatchingFields(
+        self, fields, operand=Operand.OR, recordType=None
+    ):
+        newFields = []
+        for fieldName, searchTerm, matchFlags, matchType in fields:
+
+            if isinstance(searchTerm, uuid.UUID):
+                searchTerm = unicode(searchTerm)
+
+            newFields.append(
+                (
+                    fieldName.encode(&quot;utf-8&quot;),
+                    searchTerm.encode(&quot;utf-8&quot;),
+                    matchFlags.name.encode(&quot;utf-8&quot;),
+                    matchType.name.encode(&quot;utf-8&quot;)
+                )
+            )
+        if recordType is not None:
+            recordType = recordType.name.encode(&quot;utf-8&quot;)
+
+        return self._call(
+            RecordsMatchingFieldsCommand,
+            self._processMultipleRecords,
+            fields=newFields,
+            operand=operand.name.encode(&quot;utf-8&quot;),
+            recordType=recordType
+        )
+
+
+    def recordsFromExpression(self, expression):
+        raise NotImplementedError(
+            &quot;This won't work until expressions are serializable to send &quot;
+            &quot;across AMP&quot;
+        )
+
+
+
+@implementer(ICalendarStoreDirectoryRecord)
+class DirectoryRecord(BaseDirectoryRecord, CalendarDirectoryRecordMixin):
+
+
</ins><span class="cx">     def verifyPlaintextPassword(self, password):
</span><span class="cx">         return self.service._call(
</span><span class="cx">             VerifyPlaintextPasswordCommand,
</span><span class="lines">@@ -201,6 +319,35 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def members(self):
+        return self.service._call(
+            MembersCommand,
+            self.service._processMultipleRecords,
+            uid=self.uid.encode(&quot;utf-8&quot;)
+        )
+
+
+    def groups(self):
+        return self.service._call(
+            GroupsCommand,
+            self.service._processMultipleRecords,
+            uid=self.uid.encode(&quot;utf-8&quot;)
+        )
+
+
+    def setMembers(self, members):
+        log.debug(&quot;DPS Client setMembers&quot;)
+        memberUIDs = [m.uid.encode(&quot;utf-8&quot;) for m in members]
+        return self.service._call(
+            SetMembersCommand,
+            lambda x: x['success'],
+            uid=self.uid.encode(&quot;utf-8&quot;),
+            memberUIDs=memberUIDs
+        )
+
+
+
+
</ins><span class="cx"> # Test client:
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -212,22 +359,28 @@
</span><span class="cx">     if record:
</span><span class="cx">         authenticated = (yield record.verifyPlaintextPassword(&quot;negas&quot;))
</span><span class="cx">         print(&quot;plain auth: {a}&quot;.format(a=authenticated))
</span><del>-    &quot;&quot;&quot;
-    record = (yield ds.recordWithUID(&quot;__dre__&quot;))
-    print(&quot;uid: {r}&quot;.format(r=record))
-    if record:
-        authenticated = (yield record.verifyPlaintextPassword(&quot;erd&quot;))
-        print(&quot;plain auth: {a}&quot;.format(a=authenticated))
-    record = (yield ds.recordWithGUID(&quot;A3B1158F-0564-4F5B-81E4-A89EA5FF81B0&quot;))
-    print(&quot;guid: {r}&quot;.format(r=record))
-    records = (yield ds.recordsWithRecordType(RecordType.user))
-    print(&quot;recordType: {r}&quot;.format(r=records))
-    records = (yield ds.recordsWithEmailAddress(&quot;cdaboo@bitbucket.calendarserver.org&quot;))
-    print(&quot;emailAddress: {r}&quot;.format(r=records))
-    &quot;&quot;&quot;
</del><span class="cx"> 
</span><ins>+    # record = (yield ds.recordWithUID(&quot;__dre__&quot;))
+    # print(&quot;uid: {r}&quot;.format(r=record))
+    # if record:
+    #     authenticated = (yield record.verifyPlaintextPassword(&quot;erd&quot;))
+    #     print(&quot;plain auth: {a}&quot;.format(a=authenticated))
</ins><span class="cx"> 
</span><ins>+    # record = yield ds.recordWithGUID(
+    #     &quot;A3B1158F-0564-4F5B-81E4-A89EA5FF81B0&quot;
+    # )
+    # print(&quot;guid: {r}&quot;.format(r=record))
</ins><span class="cx"> 
</span><ins>+    # records = yield ds.recordsWithRecordType(RecordType.user)
+    # print(&quot;recordType: {r}&quot;.format(r=records))
+
+    # records = yield ds.recordsWithEmailAddress(
+    #     &quot;cdaboo@bitbucket.calendarserver.org&quot;
+    # )
+    # print(&quot;emailAddress: {r}&quot;.format(r=records))
+
+
+
</ins><span class="cx"> def succeeded(result):
</span><span class="cx">     print(&quot;yay&quot;)
</span><span class="cx">     reactor.stop()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpscommandspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/commands.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/commands.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/commands.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -67,7 +67,27 @@
</span><span class="cx">     ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class RecordsMatchingTokensCommand(amp.Command):
+    arguments = [
+        ('tokens', amp.ListOf(amp.String())),
+        ('context', amp.String(optional=True)),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
</ins><span class="cx"> 
</span><ins>+
+class RecordsMatchingFieldsCommand(amp.Command):
+    arguments = [
+        ('fields', amp.ListOf(amp.ListOf(amp.String()))),
+        ('operand', amp.String()),
+        ('recordType', amp.String(optional=True)),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
+
+
</ins><span class="cx"> class UpdateRecordsCommand(amp.Command):
</span><span class="cx">     arguments = [
</span><span class="cx">         ('fieldsList', amp.String()),
</span><span class="lines">@@ -89,6 +109,37 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class MembersCommand(amp.Command):
+    arguments = [
+        ('uid', amp.String()),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
+
+
+
+class GroupsCommand(amp.Command):
+    arguments = [
+        ('uid', amp.String()),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
+
+
+
+class SetMembersCommand(amp.Command):
+    arguments = [
+        ('uid', amp.String()),
+        ('memberUIDs', amp.ListOf(amp.String())),
+    ]
+    response = [
+        ('success', amp.Boolean()),
+    ]
+
+
+
</ins><span class="cx"> class VerifyPlaintextPasswordCommand(amp.Command):
</span><span class="cx">     arguments = [
</span><span class="cx">         ('uid', amp.String()),
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpsjsonpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavdpsjsonpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/json.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/dps/json.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/json.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/json.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,149 @@
</span><ins>+##
+# Copyright (c) 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 __future__ import absolute_import
+
+&quot;&quot;&quot;
+JSON serialization utilities.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;expressionAsJSONText&quot;,
+    &quot;expressionFromJSONText&quot;,
+]
+
+from json import dumps, loads as from_json_text
+
+from twext.who.expression import (
+    CompoundExpression, Operand,
+    MatchExpression, MatchType, MatchFlags,
+)
+
+
+
+def expressionAsJSONText(expression):
+    json = expressionAsJSON(expression)
+    return to_json_text(json)
+
+
+def expressionAsJSON(expression):
+    if isinstance(expression, CompoundExpression):
+        return compoundExpressionAsJSON(expression)
+
+    if isinstance(expression, MatchExpression):
+        return matchExpressionAsJSON(expression)
+
+    raise NotImplementedError(
+        &quot;Unknown expression type: {!r}&quot;.format(expression)
+    )
+
+
+def compoundExpressionAsJSON(expression):
+    return dict(
+        type=expression.__class__.__name__,
+        operand=expression.operand.name,
+        expressions=[expressionAsJSON(e) for e in expression.expressions],
+    )
+
+
+def matchExpressionAsJSON(expression):
+    return dict(
+        type=expression.__class__.__name__,
+        field=expression.fieldName.name,
+        value=expression.fieldValue,
+        match=expression.matchType.name,
+        flags=expression.flags.name,
+    )
+    raise NotImplementedError()
+
+
+def expressionFromJSONText(jsonText):
+    json = from_json_text(jsonText)
+    return expressionFromJSON(json)
+
+
+def expressionFromJSON(json):
+    if not isinstance(json, dict):
+        raise TypeError(&quot;JSON expression must be a dict.&quot;)
+
+    try:
+        json_type = json[&quot;type&quot;]
+    except KeyError as e:
+        raise ValueError(&quot;JSON expression must have {!r} key.&quot;.format(e[0]))
+
+    if json_type == &quot;CompoundExpression&quot;:
+        return compoundExpressionFromJSON(json)
+
+    if json_type == &quot;MatchExpression&quot;:
+        return matchExpressionFromJSON(json)
+
+    raise NotImplementedError(
+        &quot;Unknown expression type: {}&quot;.format(json_type)
+    )
+
+
+def compoundExpressionFromJSON(json):
+    try:
+        expressions_json = json[&quot;expressions&quot;]
+        operand_json = json[&quot;operand&quot;]
+    except KeyError as e:
+        raise ValueError(
+            &quot;JSON compound expression must have {!r} key.&quot;.format(e[0])
+        )
+
+    expressions = tuple(expressionFromJSON(e) for e in expressions_json)
+    operand = Operand.lookupByName(operand_json)
+
+    return CompoundExpression(expressions, operand)
+
+
+def matchExpressionFromJSON(json):
+    try:
+        field_json = json[&quot;field&quot;]
+        value_json = json[&quot;value&quot;]
+        match_json = json[&quot;match&quot;]
+        flags_json = json[&quot;flags&quot;]
+    except KeyError as e:
+        raise ValueError(
+            &quot;JSON match expression must have {!r} key.&quot;.format(e[0])
+        )
+
+    raise NotImplementedError()
+
+    fieldName = NotImplemented, field_json   # Need service...
+    fieldValue = NotImplemented, value_json  # Need to cast to correct value
+    matchType = MatchType.lookupByName(match_json)
+    flags = NotImplemented, flags_json       # Need to handle composite flags
+
+    MatchFlags  # Shh, flakes
+
+    return MatchExpression(
+        fieldName, fieldValue,
+        matchType=matchType, flags=flags,
+    )
+
+
+def to_json_text(obj):
+    &quot;&quot;&quot;
+    Convert an object into JSON text.
+
+    @param obj: An object that is serializable to JSON.
+    @type obj: L{object}
+
+    @return: JSON text.
+    @rtype: L{unicode}
+    &quot;&quot;&quot;
+    return dumps(obj, separators=(',', ':')).decode(&quot;UTF-8&quot;)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpsserverpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/server.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/server.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/server.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,32 +15,32 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> import cPickle as pickle
</span><del>-import os
</del><span class="cx"> import uuid
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><del>-from twext.who.idirectory import RecordType
</del><ins>+from twext.who.expression import MatchType, MatchFlags, Operand
</ins><span class="cx"> from twisted.application import service
</span><span class="cx"> from twisted.application.strports import service as strPortsService
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.protocol import Factory
</span><span class="cx"> from twisted.plugin import IPlugin
</span><span class="cx"> from twisted.protocols import amp
</span><del>-from twisted.python.filepath import FilePath
</del><ins>+from twisted.python.constants import Names, NamedConstant
</ins><span class="cx"> from twisted.python.usage import Options, UsageError
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
</span><span class="cx"> from txdav.dps.commands import (
</span><span class="cx">     RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
</span><span class="cx">     RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
</span><ins>+    RecordsMatchingTokensCommand, RecordsMatchingFieldsCommand,
+    MembersCommand, GroupsCommand, SetMembersCommand,
</ins><span class="cx">     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand,
</span><span class="cx">     # UpdateRecordsCommand, RemoveRecordsCommand
</span><span class="cx"> )
</span><del>-from twext.who.ldap import DirectoryService as LDAPDirectoryService
-from txdav.who.xml import DirectoryService as XMLDirectoryService
</del><ins>+from txdav.who.util import directoryFromConfig
</ins><span class="cx"> from zope.interface import implementer
</span><del>-from twisted.cred.credentials import UsernamePassword
</del><span class="cx"> 
</span><ins>+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -63,15 +63,21 @@
</span><span class="cx"> 
</span><span class="cx">     def recordToDict(self, record):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        This to be replaced by something awesome
</del><ins>+        Turn a record in a dictionary of fields which can be reconstituted
+        within the client
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         fields = {}
</span><span class="cx">         if record is not None:
</span><span class="cx">             for field, value in record.fields.iteritems():
</span><del>-                # print(&quot;%s: %s&quot; % (field.name, value))
-                valueType = self._directory.fieldName.valueType(field)
-                if valueType is unicode:
</del><ins>+                valueType = record.service.fieldName.valueType(field)
+                # print(&quot;%s: %s (%s)&quot; % (field.name, value, valueType))
+                if valueType in (unicode, bool):
</ins><span class="cx">                     fields[field.name] = value
</span><ins>+                elif valueType is uuid.UUID:
+                    fields[field.name] = str(value)
+                elif issubclass(valueType, (Names, NamedConstant)):
+                    fields[field.name] = value.name if value else None
+        # print(&quot;Server side fields&quot;, fields)
</ins><span class="cx">         return fields
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -82,7 +88,7 @@
</span><span class="cx">         shortName = shortName.decode(&quot;utf-8&quot;)
</span><span class="cx">         log.debug(&quot;RecordWithShortName: {r} {n}&quot;, r=recordType, n=shortName)
</span><span class="cx">         record = (yield self._directory.recordWithShortName(
</span><del>-            RecordType.lookupByName(recordType), shortName)
</del><ins>+            self._directory.recordType.lookupByName(recordType), shortName)
</ins><span class="cx">         )
</span><span class="cx">         fields = self.recordToDict(record)
</span><span class="cx">         response = {
</span><span class="lines">@@ -130,7 +136,7 @@
</span><span class="cx">         recordType = recordType  # as bytes
</span><span class="cx">         log.debug(&quot;RecordsWithRecordType: {r}&quot;, r=recordType)
</span><span class="cx">         records = (yield self._directory.recordsWithRecordType(
</span><del>-            RecordType.lookupByName(recordType))
</del><ins>+            self._directory.recordType.lookupByName(recordType))
</ins><span class="cx">         )
</span><span class="cx">         fieldsList = []
</span><span class="cx">         for record in records:
</span><span class="lines">@@ -158,6 +164,132 @@
</span><span class="cx">         returnValue(response)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @RecordsMatchingTokensCommand.responder
+    @inlineCallbacks
+    def recordsMatchingTokens(self, tokens, context=None):
+        tokens = [t.decode(&quot;utf-8&quot;) for t in tokens]
+        log.debug(&quot;RecordsMatchingTokens: {t}&quot;, t=(&quot;, &quot;.join(tokens)))
+        records = yield self._directory.recordsMatchingTokens(
+            tokens, context=context
+        )
+        fieldsList = []
+        for record in records:
+            fieldsList.append(self.recordToDict(record))
+        response = {
+            &quot;fieldsList&quot;: pickle.dumps(fieldsList),
+        }
+        log.debug(&quot;Responding with: {response}&quot;, response=response)
+        returnValue(response)
+
+
+    @RecordsMatchingFieldsCommand.responder
+    @inlineCallbacks
+    def recordsMatchingFields(self, fields, operand=&quot;OR&quot;, recordType=None):
+        log.debug(&quot;RecordsMatchingFields&quot;)
+        newFields = []
+        for fieldName, searchTerm, matchFlags, matchType in fields:
+            fieldName = fieldName.decode(&quot;utf-8&quot;)
+            searchTerm = searchTerm.decode(&quot;utf-8&quot;)
+            try:
+                field = self._directory.fieldName.lookupByName(fieldName)
+            except ValueError:
+                field = None
+            if field:
+                valueType = self._directory.fieldName.valueType(field)
+                if valueType is uuid.UUID:
+                    searchTerm = uuid.UUID(searchTerm)
+            matchFlags = MatchFlags.lookupByName(matchFlags.decode(&quot;utf-8&quot;))
+            matchType = MatchType.lookupByName(matchType.decode(&quot;utf-8&quot;))
+            newFields.append((fieldName, searchTerm, matchFlags, matchType))
+        operand = Operand.lookupByName(operand)
+        if recordType:
+            recordType = self._directory.recordType.lookupByName(recordType)
+        records = yield self._directory.recordsMatchingFields(
+            newFields, operand=operand, recordType=recordType
+        )
+        fieldsList = []
+        for record in records:
+            fieldsList.append(self.recordToDict(record))
+        response = {
+            &quot;fieldsList&quot;: pickle.dumps(fieldsList),
+        }
+        log.debug(&quot;Responding with: {response}&quot;, response=response)
+        returnValue(response)
+
+
+    @MembersCommand.responder
+    @inlineCallbacks
+    def members(self, uid):
+        uid = uid.decode(&quot;utf-8&quot;)
+        log.debug(&quot;Members: {u}&quot;, u=uid)
+        try:
+            record = (yield self._directory.recordWithUID(uid))
+        except Exception as e:
+            log.error(&quot;Failed in members&quot;, error=e)
+            record = None
+
+        fieldsList = []
+        if record is not None:
+            for member in (yield record.members()):
+                fieldsList.append(self.recordToDict(member))
+        response = {
+            &quot;fieldsList&quot;: pickle.dumps(fieldsList),
+        }
+        log.debug(&quot;Responding with: {response}&quot;, response=response)
+        returnValue(response)
+
+
+    @SetMembersCommand.responder
+    @inlineCallbacks
+    def setMembers(self, uid, memberUIDs):
+        uid = uid.decode(&quot;utf-8&quot;)
+        memberUIDs = [m.decode(&quot;utf-8&quot;) for m in memberUIDs]
+        log.debug(&quot;Set Members: {u} -&gt; {m}&quot;, u=uid, m=memberUIDs)
+        try:
+            record = (yield self._directory.recordWithUID(uid))
+        except Exception as e:
+            log.error(&quot;Failed in setMembers&quot;, error=e)
+            record = None
+
+        if record is not None:
+            memberRecords = []
+            for memberUID in memberUIDs:
+                memberRecord = yield self._directory.recordWithUID(memberUID)
+                if memberRecord is not None:
+                    memberRecords.append(memberRecord)
+            yield record.setMembers(memberRecords)
+            success = True
+        else:
+            success = False
+
+        response = {
+            &quot;success&quot;: success,
+        }
+        log.debug(&quot;Responding with: {response}&quot;, response=response)
+        returnValue(response)
+
+
+    @GroupsCommand.responder
+    @inlineCallbacks
+    def groups(self, uid):
+        uid = uid.decode(&quot;utf-8&quot;)
+        log.debug(&quot;Groups: {u}&quot;, u=uid)
+        try:
+            record = (yield self._directory.recordWithUID(uid))
+        except Exception as e:
+            log.error(&quot;Failed in groups&quot;, error=e)
+            record = None
+
+        fieldsList = []
+        for group in (yield record.groups()):
+            fieldsList.append(self.recordToDict(group))
+        response = {
+            &quot;fieldsList&quot;: pickle.dumps(fieldsList),
+        }
+        log.debug(&quot;Responding with: {response}&quot;, response=response)
+        returnValue(response)
+
+
</ins><span class="cx">     @VerifyPlaintextPasswordCommand.responder
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def verifyPlaintextPassword(self, uid, password):
</span><span class="lines">@@ -333,36 +465,17 @@
</span><span class="cx">         else:
</span><span class="cx">             setproctitle(&quot;CalendarServer Directory Proxy Service&quot;)
</span><span class="cx"> 
</span><del>-        directoryType = config.DirectoryProxy.DirectoryType
-        args = config.DirectoryProxy.Arguments
-        kwds = config.DirectoryProxy.Keywords
</del><ins>+        try:
+            directory = directoryFromConfig(config)
+        except Exception as e:
+            log.error(&quot;Failed to create directory service&quot;, error=e)
+            raise
</ins><span class="cx"> 
</span><del>-        if directoryType == &quot;OD&quot;:
-            from twext.who.opendirectory import DirectoryService as ODDirectoryService
-            directory = ODDirectoryService(*args, **kwds)
</del><ins>+        log.info(&quot;Created directory service&quot;)
</ins><span class="cx"> 
</span><del>-        elif directoryType == &quot;LDAP&quot;:
-            authDN = kwds.pop(&quot;authDN&quot;, &quot;&quot;)
-            password = kwds.pop(&quot;password&quot;, &quot;&quot;)
-            if authDN and password:
-                creds = UsernamePassword(authDN, password)
-            else:
-                creds = None
-            kwds[&quot;credentials&quot;] = creds
-            debug = kwds.pop(&quot;debug&quot;, &quot;&quot;)
-            directory = LDAPDirectoryService(*args, _debug=debug, **kwds)
-
-        elif directoryType == &quot;XML&quot;:
-            path = kwds.pop(&quot;path&quot;, &quot;&quot;)
-            if not path or not os.path.exists(path):
-                log.error(&quot;Path not found for XML directory: {p}&quot;, p=path)
-            fp = FilePath(path)
-            directory = XMLDirectoryService(fp, *args, **kwds)
-
-        else:
-            log.error(&quot;Invalid DirectoryType: {dt}&quot;, dt=directoryType)
-
-        desc = &quot;unix:{path}:mode=660&quot;.format(
-            path=config.DirectoryProxy.SocketPath
</del><ins>+        return strPortsService(
+            &quot;unix:{path}:mode=660&quot;.format(
+                path=config.DirectoryProxy.SocketPath
+            ),
+            DirectoryProxyAMPFactory(directory)
</ins><span class="cx">         )
</span><del>-        return strPortsService(desc, DirectoryProxyAMPFactory(directory))
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpstesttestxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -23,6 +23,7 @@
</span><span class="cx"> 
</span><span class="cx">   &lt;record type=&quot;user&quot;&gt;
</span><span class="cx">     &lt;uid&gt;__sagen__&lt;/uid&gt;
</span><ins>+    &lt;guid&gt;B3B1158F-0564-4F5B-81E4-A89EA5FF81B0&lt;/guid&gt;
</ins><span class="cx">     &lt;short-name&gt;sagen&lt;/short-name&gt;
</span><span class="cx">     &lt;full-name&gt;Morgen Sagen&lt;/full-name&gt;
</span><span class="cx">     &lt;password&gt;negas&lt;/password&gt;
</span><span class="lines">@@ -113,4 +114,10 @@
</span><span class="cx">     &lt;member-uid&gt;__alyssa__&lt;/member-uid&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx"> 
</span><ins>+  &lt;record type=&quot;location&quot;&gt;
+    &lt;uid&gt;__sanchezoffice__&lt;/uid&gt;
+    &lt;short-name&gt;sanchezoffice&lt;/short-name&gt;
+    &lt;full-name&gt;Sanchez Office&lt;/full-name&gt;
+  &lt;/record&gt;
+
</ins><span class="cx"> &lt;/directory&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavdpstesttest_clientpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test_client.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test_client.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/dps/test/test_client.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -15,8 +15,12 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> import os
</span><ins>+import uuid
</ins><span class="cx"> 
</span><del>-from twext.who.idirectory import RecordType
</del><ins>+from twext.who.expression import (
+    Operand, MatchType, MatchFlags, MatchExpression
+)
+from twext.who.idirectory import RecordType, FieldName
</ins><span class="cx"> from twisted.cred.credentials import calcResponse, calcHA1, calcHA2
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, succeed
</span><span class="cx"> from twisted.protocols.amp import AMP
</span><span class="lines">@@ -25,19 +29,48 @@
</span><span class="cx"> from twisted.trial import unittest
</span><span class="cx"> from txdav.dps.client import DirectoryService
</span><span class="cx"> from txdav.dps.server import DirectoryProxyAMPProtocol
</span><ins>+from txdav.who.directory import CalendarDirectoryServiceMixin
+from twistedcaldav.test.util import StoreTestCase
+from twistedcaldav.config import config
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> testMode = &quot;xml&quot;  # &quot;xml&quot; or &quot;od&quot;
</span><span class="cx"> if testMode == &quot;xml&quot;:
</span><ins>+    testShortName = u&quot;wsanchez&quot;
+    testUID = u&quot;__wsanchez__&quot;
+    testPassword = u&quot;zehcnasw&quot;
</ins><span class="cx">     from txdav.who.xml import DirectoryService as XMLDirectoryService
</span><ins>+
+    # Mix in the calendar-specific service methods
+    class CalendarXMLDirectoryService(
+        XMLDirectoryService,
+        CalendarDirectoryServiceMixin
+    ):
+        pass
+
</ins><span class="cx"> elif testMode == &quot;od&quot;:
</span><del>-    odpw = &quot;secret&quot;
</del><ins>+    testShortName = u&quot;becausedigest&quot;
+    testUID = u&quot;381D56CA-3B89-4AA1-942A-D4BFBC4F6F69&quot;
+    testPassword = u&quot;password&quot;
</ins><span class="cx">     from twext.who.opendirectory import DirectoryService as OpenDirectoryService
</span><span class="cx"> 
</span><ins>+    # Mix in the calendar-specific service methods
+    class CalendarODDirectoryService(
+        OpenDirectoryService,
+        CalendarDirectoryServiceMixin
+    ):
+        pass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-class DPSClientTest(unittest.TestCase):
</del><span class="cx"> 
</span><ins>+
+class DPSClientSingleDirectoryTest(unittest.TestCase):
+    &quot;&quot;&quot;
+    Tests the client against a single directory service (as opposed to the
+    augmented, aggregated structure you get from directoryFromConfig(), which
+    is tested in the class below)
+    &quot;&quot;&quot;
+
</ins><span class="cx">     def setUp(self):
</span><span class="cx"> 
</span><span class="cx">         # The &quot;local&quot; directory service
</span><span class="lines">@@ -46,9 +79,9 @@
</span><span class="cx">         # The &quot;remote&quot; directory service
</span><span class="cx">         if testMode == &quot;xml&quot;:
</span><span class="cx">             path = os.path.join(os.path.dirname(__file__), &quot;test.xml&quot;)
</span><del>-            remoteDirectory = XMLDirectoryService(FilePath(path))
</del><ins>+            remoteDirectory = CalendarXMLDirectoryService(FilePath(path))
</ins><span class="cx">         elif testMode == &quot;od&quot;:
</span><del>-            remoteDirectory = OpenDirectoryService()
</del><ins>+            remoteDirectory = CalendarODDirectoryService()
</ins><span class="cx"> 
</span><span class="cx">         # Connect the two services directly via an IOPump
</span><span class="cx">         client = AMP()
</span><span class="lines">@@ -73,64 +106,382 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_uid(self):
</span><del>-        record = (yield self.directory.recordWithUID(&quot;__dre__&quot;))
-        self.assertEquals(record.shortNames, [u&quot;dre&quot;])
</del><ins>+        record = (yield self.directory.recordWithUID(testUID))
+        self.assertTrue(testShortName in record.shortNames)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_shortName(self):
</span><span class="cx">         record = (yield self.directory.recordWithShortName(
</span><span class="cx">             RecordType.user,
</span><del>-            &quot;wsanchez&quot;
</del><ins>+            testShortName
</ins><span class="cx">         ))
</span><del>-        self.assertEquals(record.shortNames, [u'wsanchez', u'wilfredo_sanchez'])
</del><ins>+        self.assertEquals(record.uid, testUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_guid(self):
+        if testMode == &quot;od&quot;:
+            record = (yield self.directory.recordWithGUID(testUID))
+            self.assertTrue(testShortName in record.shortNames)
+
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def test_guid(self):
-        record = (yield self.directory.recordWithGUID(
-            &quot;A3B1158F-0564-4F5B-81E4-A89EA5FF81B0&quot;
</del><ins>+    def test_recordType(self):
+        if testMode != &quot;od&quot;:
+            records = (yield self.directory.recordsWithRecordType(
+                RecordType.user
+            ))
+            self.assertEquals(len(records), 9)
+
+
+    @inlineCallbacks
+    def test_emailAddress(self):
+        if testMode == &quot;xml&quot;:
+            records = (yield self.directory.recordsWithEmailAddress(
+                u&quot;cdaboo@bitbucket.calendarserver.org&quot;
+            ))
+            self.assertEquals(len(records), 1)
+            self.assertEquals(records[0].shortNames, [u&quot;cdaboo&quot;])
+
+
+    @inlineCallbacks
+    def test_recordsMatchingTokens(self):
+        records = (yield self.directory.recordsMatchingTokens(
+            [u&quot;anche&quot;]
</ins><span class="cx">         ))
</span><del>-        self.assertEquals(record.shortNames, [u'dre'])
</del><ins>+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_recordsMatchingFields_anyType(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+            (u&quot;fullNames&quot;, &quot;morgen&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.directory.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=None
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;sagen&quot; in matchingShortNames)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        self.assertTrue(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_oneType(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.directory.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=RecordType.user
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        # This location should *not* appear in the results
+        self.assertFalse(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_unsupportedField(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+            # This should be ignored:
+            (u&quot;foo&quot;, &quot;bar&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.directory.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=None
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        self.assertTrue(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_nonUnicode(self):
+        fields = (
+            (u&quot;guid&quot;, uuid.UUID(&quot;A3B1158F-0564-4F5B-81E4-A89EA5FF81B0&quot;),
+                MatchFlags.caseInsensitive, MatchType.equals),
+        )
+        records = (yield self.directory.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=None
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertFalse(&quot;wsanchez&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsFromMatchExpression(self):
+        expression = MatchExpression(
+            FieldName.uid,
+            testUID,
+            MatchType.equals,
+            MatchFlags.none
+        )
+        records = yield self.directory.recordsFromExpression(expression)
+        self.assertEquals(len(records), 1)
+
+    test_recordsFromMatchExpression.todo = &quot;Won't work until we can serialize expressions&quot;
+
+
+    @inlineCallbacks
+    def test_verifyPlaintextPassword(self):
+        expectations = (
+            (testPassword, True),  # Correct
+            (&quot;wrong&quot;, False)  # Incorrect
+        )
+        record = (
+            yield self.directory.recordWithShortName(
+                RecordType.user,
+                testShortName
+            )
+        )
+
+        for password, answer in expectations:
+            authenticated = (yield record.verifyPlaintextPassword(password))
+            self.assertEquals(authenticated, answer)
+
+
+    @inlineCallbacks
+    def test_verifyHTTPDigest(self):
+        expectations = (
+            (testPassword, True),  # Correct
+            (&quot;wrong&quot;, False)  # Incorrect
+        )
+        record = (
+            yield self.directory.recordWithShortName(
+                RecordType.user,
+                testShortName
+            )
+        )
+
+        realm = &quot;host.example.com&quot;
+        nonce = &quot;128446648710842461101646794502&quot;
+        algorithm = &quot;md5&quot;
+        uri = &quot;http://host.example.com&quot;
+        method = &quot;GET&quot;
+
+        for password, answer in expectations:
+            for qop, nc, cnonce in (
+                (&quot;&quot;, &quot;&quot;, &quot;&quot;),
+                (&quot;auth&quot;, &quot;00000001&quot;, &quot;/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE=&quot;),
+            ):
+                response = calcResponse(
+                    calcHA1(algorithm, testShortName, realm, password, nonce, cnonce),
+                    calcHA2(algorithm, method, uri, qop, None),
+                    algorithm, nonce, nc, cnonce, qop)
+
+                authenticated = (
+                    yield record.verifyHTTPDigest(
+                        testShortName, realm, uri, nonce, cnonce, algorithm, nc, qop,
+                        response, method
+                    )
+                )
+                self.assertEquals(authenticated, answer)
+
+
+
+
+
+class DPSClientAugmentedAggregateDirectoryTest(StoreTestCase):
+    &quot;&quot;&quot;
+    Similar to the above tests, but in the context of the directory structure
+    that directoryFromConfig() returns
+    &quot;&quot;&quot;
+
+    wsanchezUID = u&quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()
+
+        # The &quot;local&quot; directory service
+        self.client = DirectoryService(None)
+
+        # The &quot;remote&quot; directory service
+        remoteDirectory = self.directory
+
+        # Connect the two services directly via an IOPump
+        client = AMP()
+        server = DirectoryProxyAMPProtocol(remoteDirectory)
+        pump = returnConnected(server, client)
+
+        # Replace the normal _getConnection method with one that bypasses any
+        # actual networking
+        self.patch(self.client, &quot;_getConnection&quot;, lambda: succeed(client))
+
+        # Wrap the normal _call method with one that flushes the IOPump
+        # afterwards
+        origCall = self.client._call
+
+        def newCall(*args, **kwds):
+            d = origCall(*args, **kwds)
+            pump.flush()
+            return d
+
+        self.patch(self.client, &quot;_call&quot;, newCall)
+
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Override configuration hook to turn on wiki.
+        &quot;&quot;&quot;
+        super(DPSClientAugmentedAggregateDirectoryTest, self).configure()
+        self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
+
+
+    @inlineCallbacks
+    def test_uid(self):
+        record = (yield self.client.recordWithUID(self.wsanchezUID))
+        self.assertTrue(u&quot;wsanchez&quot; in record.shortNames)
+
+
+    @inlineCallbacks
+    def test_shortName(self):
+        record = (yield self.client.recordWithShortName(
+            RecordType.user,
+            u&quot;wsanchez&quot;
+        ))
+        self.assertEquals(record.uid, self.wsanchezUID)
+
+
+    def test_guid(self):
+        record = yield self.client.recordWithGUID(self.wsanchezUID)
+        self.assertTrue(u&quot;wsanchez&quot; in record.shortNames)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_recordType(self):
</span><del>-        records = (yield self.directory.recordsWithRecordType(
</del><ins>+        records = (yield self.client.recordsWithRecordType(
</ins><span class="cx">             RecordType.user
</span><span class="cx">         ))
</span><del>-        self.assertEquals(len(records), 9)
</del><ins>+        self.assertEquals(len(records), 35)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_emailAddress(self):
</span><del>-        records = (yield self.directory.recordsWithEmailAddress(
-            &quot;cdaboo@bitbucket.calendarserver.org&quot;
</del><ins>+        records = (yield self.client.recordsWithEmailAddress(
+            u&quot;wsanchez@example.com&quot;
</ins><span class="cx">         ))
</span><span class="cx">         self.assertEquals(len(records), 1)
</span><del>-        self.assertEquals(records[0].shortNames, [u&quot;cdaboo&quot;])
</del><ins>+        self.assertEquals(records[0].shortNames, [u&quot;wsanchez&quot;])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_recordsMatchingTokens(self):
+        records = (yield self.client.recordsMatchingTokens(
+            [u&quot;anche&quot;]
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_anyType(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+            (u&quot;fullNames&quot;, &quot;morgen&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.client.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=None
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;sagen&quot; in matchingShortNames)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        self.assertTrue(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_oneType(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.client.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=RecordType.user
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        # This location should *not* appear in the results
+        self.assertFalse(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingFields_unsupportedField(self):
+        fields = (
+            (u&quot;fullNames&quot;, &quot;anche&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+            # This should be ignored:
+            (u&quot;foo&quot;, &quot;bar&quot;, MatchFlags.caseInsensitive, MatchType.contains),
+        )
+        records = (yield self.client.recordsMatchingFields(
+            fields, operand=Operand.OR, recordType=None
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+        self.assertTrue(&quot;sanchezoffice&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsFromMatchExpression(self):
+        expression = MatchExpression(
+            FieldName.uid,
+            u&quot;wsanchez&quot;,
+            MatchType.equals,
+            MatchFlags.none
+        )
+        records = yield self.client.recordsFromExpression(expression)
+        self.assertEquals(len(records), 1)
+
+    test_recordsFromMatchExpression.todo = &quot;Won't work until we can serialize expressions&quot;
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_verifyPlaintextPassword(self):
</span><del>-        if testMode == &quot;xml&quot;:
-            expectations = (
-                (&quot;erd&quot;, True),    # Correct
-                (&quot;wrong&quot;, False)  # Incorrect
</del><ins>+        expectations = (
+            (u&quot;zehcnasw&quot;, True),  # Correct
+            (&quot;wrong&quot;, False)  # Incorrect
+        )
+        record = (
+            yield self.client.recordWithShortName(
+                RecordType.user,
+                u&quot;wsanchez&quot;
</ins><span class="cx">             )
</span><del>-            record = (
-                yield self.directory.recordWithShortName(RecordType.user, &quot;dre&quot;)
-            )
-        elif testMode == &quot;od&quot;:
-            expectations = (
-                (odpw, True),     # Correct
-                (&quot;wrong&quot;, False)  # Incorrect
-            )
-            record = (
-                yield self.directory.recordWithGUID(
-                    &quot;D0B38B00-4166-11DD-B22C-A07C87F02F6A&quot;
-                )
-            )
</del><ins>+        )
</ins><span class="cx"> 
</span><span class="cx">         for password, answer in expectations:
</span><span class="cx">             authenticated = (yield record.verifyPlaintextPassword(password))
</span><span class="lines">@@ -139,26 +490,16 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_verifyHTTPDigest(self):
</span><del>-        if testMode == &quot;xml&quot;:
-            username = &quot;dre&quot;
-            expectations = (
-                (&quot;erd&quot;, True),    # Correct
-                (&quot;wrong&quot;, False)  # Incorrect
</del><ins>+        expectations = (
+            (u&quot;zehcnasw&quot;, True),  # Correct
+            (&quot;wrong&quot;, False)  # Incorrect
+        )
+        record = (
+            yield self.client.recordWithShortName(
+                RecordType.user,
+                u&quot;wsanchez&quot;
</ins><span class="cx">             )
</span><del>-            record = (
-                yield self.directory.recordWithShortName(RecordType.user, &quot;dre&quot;)
-            )
-        elif testMode == &quot;od&quot;:
-            username = &quot;sagen&quot;
-            expectations = (
-                (odpw, True),     # Correct
-                (&quot;wrong&quot;, False)  # Incorrect
-            )
-            record = (
-                yield self.directory.recordWithGUID(
-                    &quot;D0B38B00-4166-11DD-B22C-A07C87F02F6A&quot;
-                )
-            )
</del><ins>+        )
</ins><span class="cx"> 
</span><span class="cx">         realm = &quot;host.example.com&quot;
</span><span class="cx">         nonce = &quot;128446648710842461101646794502&quot;
</span><span class="lines">@@ -172,13 +513,13 @@
</span><span class="cx">                 (&quot;auth&quot;, &quot;00000001&quot;, &quot;/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE=&quot;),
</span><span class="cx">             ):
</span><span class="cx">                 response = calcResponse(
</span><del>-                    calcHA1(algorithm, username, realm, password, nonce, cnonce),
</del><ins>+                    calcHA1(algorithm, u&quot;wsanchez&quot;, realm, password, nonce, cnonce),
</ins><span class="cx">                     calcHA2(algorithm, method, uri, qop, None),
</span><span class="cx">                     algorithm, nonce, nc, cnonce, qop)
</span><span class="cx"> 
</span><span class="cx">                 authenticated = (
</span><span class="cx">                     yield record.verifyHTTPDigest(
</span><del>-                        username, realm, uri, nonce, cnonce, algorithm, nc, qop,
</del><ins>+                        u&quot;wsanchez&quot;, realm, uri, nonce, cnonce, algorithm, nc, qop,
</ins><span class="cx">                         response, method
</span><span class="cx">                     )
</span><span class="cx">                 )
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhoaugmentpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhoaugmentpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/augment.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/augment.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/augment.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,406 @@
</span><ins>+# -*- test-case-name: txdav.who.test.test_augment -*-
+##
+# Copyright (c) 2013 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;
+Augmenting Directory Service
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;AugmentedDirectoryService&quot;,
+]
+
+from zope.interface import implementer
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twistedcaldav.directory.augment import AugmentRecord
+from twext.python.log import Logger
+from twext.who.directory import DirectoryRecord
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.idirectory import (
+    IDirectoryService, RecordType, FieldName as BaseFieldName
+)
+from twext.who.util import ConstantsContainer
+
+from txdav.common.idirectoryservice import IStoreDirectoryService
+from txdav.who.directory import (
+    CalendarDirectoryRecordMixin, CalendarDirectoryServiceMixin,
+)
+from txdav.who.idirectory import (
+    AutoScheduleMode, FieldName, RecordType as CalRecordType
+)
+
+log = Logger()
+
+
+
+@implementer(IDirectoryService, IStoreDirectoryService)
+class AugmentedDirectoryService(
+    BaseDirectoryService, CalendarDirectoryServiceMixin
+):
+    &quot;&quot;&quot;
+    Augmented directory service.
+
+    This is a directory service that wraps an L{IDirectoryService} and augments
+    directory records with additional or modified fields.
+    &quot;&quot;&quot;
+
+    fieldName = ConstantsContainer((
+        BaseFieldName,
+        FieldName,
+    ))
+
+
+    def __init__(self, directory, store, augmentDB):
+        BaseDirectoryService.__init__(self, directory.realmName)
+        self._directory = directory
+        self._store = store
+        self._augmentDB = augmentDB
+
+
+    @property
+    def recordType(self):
+        # Defer to the directory service we're augmenting
+        return self._directory.recordType
+
+
+    def recordTypes(self):
+        # Defer to the directory service we're augmenting
+        return self._directory.recordTypes()
+
+
+    @inlineCallbacks
+    def recordsFromExpression(self, expression):
+        records = yield self._directory.recordsFromExpression(expression)
+        augmented = []
+        for record in records:
+            record = yield self._augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordsWithFieldValue(self, fieldName, value):
+        records = yield self._directory.recordsWithFieldValue(
+            fieldName, value
+        )
+        augmented = []
+        for record in records:
+            record = yield self._augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordWithUID(self, uid):
+        # 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;)
+
+        record = yield self._directory.recordWithUID(uid)
+        record = yield self._augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordWithGUID(self, guid):
+        record = yield self._directory.recordWithGUID(guid)
+        record = yield self._augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordsWithRecordType(self, recordType):
+        records = yield self._directory.recordsWithRecordType(recordType)
+        augmented = []
+        for record in records:
+            record = yield self._augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordWithShortName(self, recordType, shortName):
+        # 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;)
+
+        record = yield self._directory.recordWithShortName(
+            recordType, shortName
+        )
+        record = yield self._augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordsWithEmailAddress(self, emailAddress):
+        # 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;)
+
+        records = yield self._directory.recordsWithEmailAddress(emailAddress)
+        augmented = []
+        for record in records:
+            record = yield self._augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def updateRecords(self, records, create=False):
+        &quot;&quot;&quot;
+        Pull out the augmented fields from each record, apply those to the
+        augments database, then update the base records.
+        &quot;&quot;&quot;
+
+        baseRecords = []
+        augmentRecords = []
+
+        for record in records:
+
+            # Split out the base fields from the augment fields
+            baseFields, augmentFields = self._splitFields(record)
+
+            if augmentFields:
+                # Create an AugmentRecord
+                autoScheduleMode = {
+                    AutoScheduleMode.none: &quot;none&quot;,
+                    AutoScheduleMode.accept: &quot;accept-always&quot;,
+                    AutoScheduleMode.decline: &quot;decline-always&quot;,
+                    AutoScheduleMode.acceptIfFree: &quot;accept-if-free&quot;,
+                    AutoScheduleMode.declineIfBusy: &quot;decline-if-busy&quot;,
+                    AutoScheduleMode.acceptIfFreeDeclineIfBusy: &quot;automatic&quot;,
+                }.get(augmentFields.get(FieldName.autoScheduleMode, None), None)
+
+                kwargs = {
+                    &quot;uid&quot;: record.uid,
+                    &quot;autoScheduleMode&quot;: autoScheduleMode,
+                }
+                if FieldName.hasCalendars in augmentFields:
+                    kwargs[&quot;enabledForCalendaring&quot;] = augmentFields[FieldName.hasCalendars]
+                if FieldName.hasContacts in augmentFields:
+                    kwargs[&quot;enabledForAddressBooks&quot;] = augmentFields[FieldName.hasContacts]
+                if FieldName.loginAllowed in augmentFields:
+                    kwargs[&quot;enabledForLogin&quot;] = augmentFields[FieldName.loginAllowed]
+                if FieldName.autoAcceptGroup in augmentFields:
+                    kwargs[&quot;autoAcceptGroup&quot;] = augmentFields[FieldName.autoAcceptGroup]
+                if FieldName.serviceNodeUID in augmentFields:
+                    kwargs[&quot;serverID&quot;] = augmentFields[FieldName.serviceNodeUID]
+                augmentRecord = AugmentRecord(**kwargs)
+
+                augmentRecords.append(augmentRecord)
+
+            # Create new base records:
+            baseRecords.append(DirectoryRecord(self._directory, baseFields))
+
+        # Apply the augment records
+        if augmentRecords:
+            yield self._augmentDB.addAugmentRecords(augmentRecords)
+
+        # Apply the base records
+        if baseRecords:
+            yield self._directory.updateRecords(baseRecords, create=create)
+
+
+    def _splitFields(self, record):
+        &quot;&quot;&quot;
+        Returns a tuple of two dictionaries; the first contains all the non
+        augment fields, and the second contains all the augment fields.
+        &quot;&quot;&quot;
+        if record is None:
+            return None
+
+        augmentFields = {}
+        baseFields = record.fields.copy()
+        for field in (
+            FieldName.loginAllowed,
+            FieldName.hasCalendars, FieldName.hasContacts,
+            FieldName.autoScheduleMode, FieldName.autoAcceptGroup,
+            FieldName.serviceNodeUID
+        ):
+            if field in baseFields:
+                augmentFields[field] = baseFields[field]
+                del baseFields[field]
+
+        return (baseFields, augmentFields)
+
+
+    def removeRecords(self, uids):
+        self._augmentDB.removeAugmentRecords(uids)
+        return self._directory.removeRecords(uids)
+
+
+    def _assignToField(self, fields, name, value):
+        field = self.fieldName.lookupByName(name)
+        fields[field] = value
+
+
+
+    @inlineCallbacks
+    def _augment(self, record):
+        if record is None:
+            returnValue(None)
+
+        try:
+            augmentRecord = yield self._augmentDB.getAugmentRecord(
+                record.uid,
+                self.recordTypeToOldName(record.recordType)
+            )
+        except KeyError:
+            # Augments does not know about this record type, so return
+            # the original record
+            returnValue(record)
+
+        fields = record.fields.copy()
+
+        # print(&quot;Got augment record&quot;, augmentRecord)
+
+        if augmentRecord:
+
+            self._assignToField(
+                fields, &quot;hasCalendars&quot;,
+                augmentRecord.enabledForCalendaring
+            )
+
+            self._assignToField(
+                fields, &quot;hasContacts&quot;,
+                augmentRecord.enabledForAddressBooks
+            )
+
+            autoScheduleMode = {
+                &quot;none&quot;: AutoScheduleMode.none,
+                &quot;accept-always&quot;: AutoScheduleMode.accept,
+                &quot;decline-always&quot;: AutoScheduleMode.decline,
+                &quot;accept-if-free&quot;: AutoScheduleMode.acceptIfFree,
+                &quot;decline-if-busy&quot;: AutoScheduleMode.declineIfBusy,
+                &quot;automatic&quot;: AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+            }.get(augmentRecord.autoScheduleMode, None)
+
+            # Resources/Locations default to automatic
+            if record.recordType in (
+                CalRecordType.location,
+                CalRecordType.resource
+            ):
+                if autoScheduleMode is None:
+                    autoScheduleMode = AutoScheduleMode.acceptIfFreeDeclineIfBusy
+
+            self._assignToField(
+                fields, &quot;autoScheduleMode&quot;,
+                autoScheduleMode
+            )
+
+            if augmentRecord.autoAcceptGroup is not None:
+                self._assignToField(
+                    fields, &quot;autoAcceptGroup&quot;,
+                    augmentRecord.autoAcceptGroup.decode(&quot;utf-8&quot;)
+                )
+
+            self._assignToField(
+                fields, &quot;loginAllowed&quot;,
+                augmentRecord.enabledForLogin
+            )
+
+            self._assignToField(
+                fields, &quot;serviceNodeUID&quot;,
+                augmentRecord.serverID.decode(&quot;utf-8&quot;)
+            )
+
+            if (
+                (
+                    fields.get(
+                        self.fieldName.lookupByName(&quot;hasCalendars&quot;), False
+                    ) or
+                    fields.get(
+                        self.fieldName.lookupByName(&quot;hasContacts&quot;), False
+                    )
+                ) and
+                record.recordType == RecordType.group
+            ):
+                self._assignToField(fields, &quot;hasCalendars&quot;, False)
+                self._assignToField(fields, &quot;hasContacts&quot;, False)
+
+                # For augment records cloned from the Default augment record,
+                # don't emit this message:
+                if not augmentRecord.clonedFromDefault:
+                    log.error(
+                        &quot;Group {record} cannot be enabled for &quot;
+                        &quot;calendaring or address books&quot;,
+                        record=record
+                    )
+
+        else:
+            self._assignToField(fields, &quot;hasCalendars&quot;, False)
+            self._assignToField(fields, &quot;hasContacts&quot;, False)
+            self._assignToField(fields, &quot;loginAllowed&quot;, False)
+
+        # print(&quot;Augmented fields&quot;, fields)
+
+        # Clone to a new record with the augmented fields
+        returnValue(AugmentedDirectoryRecord(self, record, fields))
+
+
+
+class AugmentedDirectoryRecord(DirectoryRecord, CalendarDirectoryRecordMixin):
+    &quot;&quot;&quot;
+    Augmented directory record.
+    &quot;&quot;&quot;
+
+    def __init__(self, service, baseRecord, augmentedFields):
+        DirectoryRecord.__init__(self, service, augmentedFields)
+        self._baseRecord = baseRecord
+
+
+    @inlineCallbacks
+    def members(self):
+        augmented = []
+        records = yield self._baseRecord.members()
+
+        for record in records:
+            augmented.append((yield self.service._augment(record)))
+
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def groups(self):
+        augmented = []
+
+        txn = self.service._store.newTransaction()
+        groupUIDs = yield txn.groupsFor(self.uid)
+
+        for groupUID in groupUIDs:
+            groupRecord = yield self.service.recordWithUID(
+                groupUID
+            )
+            if groupRecord:
+                augmented.append((yield self.service._augment(groupRecord)))
+
+        returnValue(augmented)
+
+
+    def verifyPlaintextPassword(self, password):
+        return self._baseRecord.verifyPlaintextPassword(password)
+
+
+    def verifyHTTPDigest(self, *args):
+        return self._baseRecord.verifyHTTPDigest(*args)
+
+
+    def accessForRecord(self, record):
+        return self._baseRecord.accessForRecord(record)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhodelegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/delegates.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/delegates.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/delegates.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,4 +1,4 @@
</span><del>-# -*- test-case-name: twext.who.test.test_delegates -*-
</del><ins>+# -*- test-case-name: txdav.who.test.test_delegates -*-
</ins><span class="cx"> ##
</span><span class="cx"> # Copyright (c) 2013 Apple Inc. All rights reserved.
</span><span class="cx"> #
</span><span class="lines">@@ -19,13 +19,202 @@
</span><span class="cx"> Delegate assignments
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
-from twext.who.idirectory import RecordType
</del><ins>+from twisted.python.constants import Names, NamedConstant
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><ins>+from twext.who.idirectory import (
+    RecordType as BaseRecordType, FieldName, NotAllowedError
+)
+from twext.who.directory import (
+    DirectoryService as BaseDirectoryService,
+    DirectoryRecord as BaseDirectoryRecord
+)
+from twext.who.expression import MatchExpression, MatchType
+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class RecordType(Names):
+    &quot;&quot;&quot;
+    Constants for read-only delegates and read-write delegate groups
+    &quot;&quot;&quot;
+
+    readDelegateGroup = NamedConstant()
+    readDelegateGroup.description = u&quot;read-delegate-group&quot;
+
+    writeDelegateGroup = NamedConstant()
+    writeDelegateGroup.description = u&quot;write-delegate-group&quot;
+
+    readDelegatorGroup = NamedConstant()
+    readDelegatorGroup.description = u&quot;read-delegator-group&quot;
+
+    writeDelegatorGroup = NamedConstant()
+    writeDelegatorGroup.description = u&quot;write-delegator-group&quot;
+
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+
+    @inlineCallbacks
+    def members(self, expanded=False):
+        &quot;&quot;&quot;
+        If this is a readDelegateGroup or writeDelegateGroup, the members
+        will consist of the records who are delegates *of* this record.
+        If this is a readDelegatorGroup or writeDelegatorGroup,
+        the members will consist of the records who have delegated *to*
+        this record.
+        &quot;&quot;&quot;
+        parentUID, proxyType = self.uid.split(u&quot;#&quot;)
+
+        txn = self.service._store.newTransaction()
+
+        if self.recordType in (
+            RecordType.readDelegateGroup, RecordType.writeDelegateGroup
+        ):  # Members are delegates of this record
+            readWrite = (self.recordType is RecordType.writeDelegateGroup)
+            delegateUIDs = (
+                yield txn.delegates(parentUID, readWrite, expanded=expanded)
+            )
+
+        else:  # Members have delegated to this record
+            readWrite = (self.recordType is RecordType.writeDelegatorGroup)
+            delegateUIDs = (
+                yield txn.delegators(parentUID, readWrite)
+            )
+
+        records = []
+        for uid in delegateUIDs:
+            if uid != parentUID:
+                record = yield self.service._masterDirectory.recordWithUID(uid)
+                if record is not None:
+                    records.append(record)
+        yield txn.commit()
+
+        returnValue(records)
+
+
+
+    @inlineCallbacks
+    def setMembers(self, memberRecords):
+        &quot;&quot;&quot;
+        Replace the members of this group with the new members.
+
+        @param memberRecords: The new members of the group
+        @type memberRecords: iterable of L{iDirectoryRecord}s
+        &quot;&quot;&quot;
+        if self.recordType not in (
+            RecordType.readDelegateGroup, RecordType.writeDelegateGroup
+        ):
+            raise NotAllowedError(&quot;Setting members not supported&quot;)
+
+        parentUID, proxyType = self.uid.split(u&quot;#&quot;)
+        readWrite = (self.recordType is RecordType.writeDelegateGroup)
+
+        log.debug(
+            &quot;Setting delegate assignments for {u} ({rw}) to {m}&quot;,
+            u=parentUID, rw=(&quot;write&quot; if readWrite else &quot;read&quot;),
+            m=[r.uid for r in memberRecords]
+        )
+
+        txn = self.service._store.newTransaction()
+
+        yield txn.removeDelegates(parentUID, readWrite)
+        yield txn.removeDelegateGroups(parentUID, readWrite)
+
+        delegator = (
+            yield self.service._masterDirectory.recordWithUID(parentUID)
+        )
+
+        for delegate in memberRecords:
+            yield addDelegate(txn, delegator, delegate, readWrite)
+
+        yield txn.commit()
+
+
+
+def recordTypeToProxyType(recordType):
+    return {
+        RecordType.readDelegateGroup: &quot;calendar-proxy-read&quot;,
+        RecordType.writeDelegateGroup: &quot;calendar-proxy-write&quot;,
+        RecordType.readDelegatorGroup: &quot;calendar-proxy-read-for&quot;,
+        RecordType.writeDelegatorGroup: &quot;calendar-proxy-write-for&quot;,
+    }.get(recordType, None)
+
+
+def proxyTypeToRecordType(proxyType):
+    return {
+        &quot;calendar-proxy-read&quot;: RecordType.readDelegateGroup,
+        &quot;calendar-proxy-write&quot;: RecordType.writeDelegateGroup,
+        &quot;calendar-proxy-read-for&quot;: RecordType.readDelegatorGroup,
+        &quot;calendar-proxy-write-for&quot;: RecordType.writeDelegatorGroup,
+    }.get(proxyType, None)
+
+
+
+class DirectoryService(BaseDirectoryService):
+    &quot;&quot;&quot;
+    Delegate directory service
+    &quot;&quot;&quot;
+
+    recordType = RecordType
+
+
+    def __init__(self, realmName, store):
+        BaseDirectoryService.__init__(self, realmName)
+        self._store = store
+        self._masterDirectory = None
+
+
+    def setMasterDirectory(self, masterDirectory):
+        self._masterDirectory = masterDirectory
+
+
+    def recordWithShortName(self, recordType, shortName):
+        uid = shortName + &quot;#&quot; + recordTypeToProxyType(recordType)
+
+        record = DirectoryRecord(self, {
+            FieldName.uid: uid,
+            FieldName.recordType: recordType,
+            FieldName.shortNames: (uid,),
+        })
+        return succeed(record)
+
+
+    def recordWithUID(self, uid):
+        if &quot;#&quot; not in uid:  # Not a delegate group uid
+            return succeed(None)
+        uid, proxyType = uid.split(&quot;#&quot;)
+        recordType = proxyTypeToRecordType(proxyType)
+        if recordType is None:
+            return succeed(None)
+        return self.recordWithShortName(recordType, uid)
+
+
+    @inlineCallbacks
+    def recordsFromExpression(self, expression, records=None):
+        &quot;&quot;&quot;
+        It's only ever appropriate to look up delegate group record by
+        shortName or uid.  When wrapped by an aggregate directory, looking up
+        by shortName will already go directly to recordWithShortName.  However
+        when looking up by UID, it won't.  Inspect the expression to see if
+        it's one we can handle.
+        &quot;&quot;&quot;
+        if isinstance(expression, MatchExpression):
+            if(
+                (expression.fieldName is FieldName.uid) and
+                (expression.matchType is MatchType.equals) and
+                (&quot;#&quot; in expression.fieldValue)
+            ):
+                record = yield self.recordWithUID(expression.fieldValue)
+                if record is not None:
+                    returnValue((record,))
+
+        returnValue(())
+
+
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def addDelegate(txn, delegator, delegate, readWrite):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -39,12 +228,12 @@
</span><span class="cx">     @param readWrite: if True, read and write access is granted; read-only
</span><span class="cx">         access otherwise
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    if delegate.recordType == RecordType.group:
</del><ins>+    if delegate.recordType == BaseRecordType.group:
</ins><span class="cx">         # find the groupID
</span><del>-        groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
-        yield txn.addDelegateGroup(delegator.guid, groupID, readWrite)
</del><ins>+        groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+        yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
</ins><span class="cx">     else:
</span><del>-        yield txn.addDelegate(delegator.guid, delegate.guid, readWrite)
</del><ins>+        yield txn.addDelegate(delegator.uid, delegate.uid, readWrite)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -60,16 +249,16 @@
</span><span class="cx">     @param readWrite: if True, read and write access is revoked; read-only
</span><span class="cx">         access otherwise
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    if delegate.recordType == RecordType.group:
</del><ins>+    if delegate.recordType == BaseRecordType.group:
</ins><span class="cx">         # find the groupID
</span><del>-        groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
-        yield txn.removeDelegateGroup(delegator.guid, groupID, readWrite)
</del><ins>+        groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+        yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)
</ins><span class="cx">     else:
</span><del>-        yield txn.removeDelegate(delegator.guid, delegate.guid, readWrite)
</del><ins>+        yield txn.removeDelegate(delegator.uid, delegate.uid, readWrite)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def delegatesOf(txn, delegator, readWrite):
</del><ins>+def delegatesOf(txn, delegator, readWrite, expanded=False):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Return the records of the delegates of &quot;delegator&quot;.  The type of access
</span><span class="cx">     is specified by the &quot;readWrite&quot; parameter.
</span><span class="lines">@@ -83,10 +272,12 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     records = []
</span><span class="cx">     directory = delegator.service
</span><del>-    delegateGUIDs = (yield txn.delegates(delegator.guid, readWrite))
-    for guid in delegateGUIDs:
-        if guid != delegator.guid:
-            record = (yield directory.recordWithGUID(guid))
</del><ins>+    delegateUIDs = (
+        yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
+    )
+    for uid in delegateUIDs:
+        if uid != delegator.uid:
+            record = (yield directory.recordWithUID(uid))
</ins><span class="cx">             if record is not None:
</span><span class="cx">                 records.append(record)
</span><span class="cx">     returnValue(records)
</span><span class="lines">@@ -107,18 +298,10 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     records = []
</span><span class="cx">     directory = delegate.service
</span><del>-    delegatorGUIDs = (yield txn.delegators(delegate.guid, readWrite))
-    for guid in delegatorGUIDs:
-        if guid != delegate.guid:
-            record = (yield directory.recordWithGUID(guid))
</del><ins>+    delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
+    for uid in delegatorUIDs:
+        if uid != delegate.uid:
+            record = (yield directory.recordWithUID(uid))
</ins><span class="cx">             if record is not None:
</span><span class="cx">                 records.append(record)
</span><span class="cx">     returnValue(records)
</span><del>-
-
-def allGroupDelegates(txn):
-    &quot;&quot;&quot;
-    @return: the GUIDs of all groups which are currently delegated to
-    @rtype: a Deferred which fires with a set() of GUID strings
-    &quot;&quot;&quot;
-    return txn.allGroupDelegates()
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhodirectorypyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhodirectorypy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/directory.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/directory.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/directory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,498 @@
</span><ins>+##
+# 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;
+Calendar/Contacts specific methods for DirectoryRecord
+&quot;&quot;&quot;
+
+import uuid
+
+from twext.python.log import Logger
+from twext.who.expression import (
+    MatchType, Operand, MatchExpression, CompoundExpression, MatchFlags
+)
+from twext.who.idirectory import RecordType as BaseRecordType
+from twisted.cred.credentials import UsernamePassword
+from twisted.internet.defer import inlineCallbacks, returnValue
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
+from txdav.who.delegates import RecordType as DelegateRecordType
+from txdav.who.idirectory import (
+    RecordType as DAVRecordType, AutoScheduleMode
+)
+from txweb2.auth.digest import DigestedCredentials
+
+
+log = Logger()
+
+
+__all__ = [
+    &quot;CalendarDirectoryRecordMixin&quot;,
+    &quot;CalendarDirectoryServiceMixin&quot;,
+]
+
+
+
+class CalendarDirectoryServiceMixin(object):
+
+    guid = &quot;1332A615-4D3A-41FE-B636-FBE25BFB982E&quot;
+
+    # Must maintain the hack for a bit longer:
+    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
+
+
+    @inlineCallbacks
+    def recordWithCalendarUserAddress(self, address):
+        # FIXME: moved this here to avoid circular import problems
+        from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
+        address = normalizeCUAddr(address)
+        record = None
+        if address.startswith(&quot;urn:uuid:&quot;):
+            try:
+                guid = uuid.UUID(address[9:])
+            except ValueError:
+                log.info(&quot;Invalid GUID: {guid}&quot;, guid=address[9:])
+                returnValue(None)
+            record = yield self.recordWithGUID(guid)
+        elif address.startswith(&quot;mailto:&quot;):
+            records = yield self.recordsWithEmailAddress(address[7:])
+            if records:
+                returnValue(records[0])
+            else:
+                returnValue(None)
+        elif address.startswith(&quot;/principals/&quot;):
+            parts = address.split(&quot;/&quot;)
+            if len(parts) == 4:
+                if parts[2] == &quot;__uids__&quot;:
+                    uid = parts[3]
+                    record = yield self.recordWithUID(uid)
+                else:
+                    # recordType = self.fieldName.lookupByName(parts[2])
+                    recordType = self.oldNameToRecordType(parts[2])
+                    record = yield self.recordWithShortName(recordType, parts[3])
+
+        returnValue(record if record and record.hasCalendars else None)
+
+
+    def recordsMatchingTokens(self, tokens, context=None, limitResults=50,
+                              timeoutSeconds=10):
+        fields = [
+            (&quot;fullNames&quot;, MatchType.contains),
+            (&quot;emailAddresses&quot;, MatchType.startsWith),
+        ]
+        outer = []
+        for token in tokens:
+            inner = []
+            for name, matchType in fields:
+                inner.append(
+                    MatchExpression(
+                        self.fieldName.lookupByName(name),
+                        token,
+                        matchType,
+                        MatchFlags.caseInsensitive
+                    )
+                )
+            outer.append(
+                CompoundExpression(
+                    inner,
+                    Operand.OR
+                )
+            )
+        expression = CompoundExpression(outer, Operand.AND)
+        return self.recordsFromExpression(expression)
+
+
+    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:
+            try:
+                field = self.fieldName.lookupByName(fieldName)
+            except ValueError:
+                log.debug(
+                    &quot;Unsupported field name: {fieldName}&quot;,
+                    fieldName=fieldName
+                )
+                continue
+            subExpression = MatchExpression(
+                field,
+                searchTerm,
+                matchType,
+                matchFlags
+            )
+            subExpressions.append(subExpression)
+
+        expression = CompoundExpression(subExpressions, operand)
+
+        # AND in the recordType if passed in
+        if recordType is not None:
+            typeExpression = MatchExpression(
+                self.fieldName.recordType,
+                recordType,
+                MatchType.equals,
+                MatchFlags.none
+            )
+            expression = CompoundExpression(
+                [
+                    expression,
+                    typeExpression
+                ],
+                Operand.AND
+            )
+        return self.recordsFromExpression(expression)
+
+
+    _oldRecordTypeNames = {
+        &quot;address&quot;: &quot;addresses&quot;,
+        &quot;group&quot;: &quot;groups&quot;,
+        &quot;location&quot;: &quot;locations&quot;,
+        &quot;resource&quot;: &quot;resources&quot;,
+        &quot;user&quot;: &quot;users&quot;,
+        &quot;macOSXServerWiki&quot;: &quot;wikis&quot;,
+        &quot;readDelegateGroup&quot;: &quot;readDelegateGroups&quot;,
+        &quot;writeDelegateGroup&quot;: &quot;writeDelegateGroups&quot;,
+        &quot;readDelegatorGroup&quot;: &quot;readDelegatorGroups&quot;,
+        &quot;writeDelegatorGroup&quot;: &quot;writeDelegatorGroups&quot;,
+    }
+
+
+    # Maps record types &lt;--&gt; url path segments, i.e. the segment after
+    # /principals/ e.g. &quot;users&quot; or &quot;groups&quot;
+
+    def recordTypeToOldName(self, recordType):
+        return self._oldRecordTypeNames[recordType.name]
+
+    def oldNameToRecordType(self, oldName):
+        for name, value in self._oldRecordTypeNames.iteritems():
+            if oldName == value:
+                return self.recordType.lookupByName(name)
+        return None
+
+
+
+class CalendarDirectoryRecordMixin(object):
+    &quot;&quot;&quot;
+    Calendar (and Contacts) specific logic for directory records lives in this
+    class
+    &quot;&quot;&quot;
+
+
+    @inlineCallbacks
+    def verifyCredentials(self, credentials):
+
+        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
+                ))
+            )
+
+
+    @property
+    def calendarUserAddresses(self):
+        try:
+            if not self.hasCalendars:
+                return frozenset()
+        except AttributeError:
+            pass
+
+        try:
+            cuas = set(
+                [&quot;mailto:%s&quot; % (emailAddress,)
+                 for emailAddress in self.emailAddresses]
+            )
+        except AttributeError:
+            cuas = set()
+
+        try:
+            if self.guid:
+                if isinstance(self.guid, uuid.UUID):
+                    guid = unicode(self.guid).upper()
+                else:
+                    guid = self.guid
+                cuas.add(&quot;urn:uuid:{guid}&quot;.format(guid=guid))
+        except AttributeError:
+            # No guid
+            pass
+        cuas.add(u&quot;/principals/__uids__/{uid}/&quot;.format(uid=self.uid))
+        for shortName in self.shortNames:
+            cuas.add(u&quot;/principals/{rt}/{sn}/&quot;.format(
+                rt=self.service.recordTypeToOldName(self.recordType),
+                sn=shortName)
+            )
+        return frozenset(cuas)
+
+
+    # Mapping from directory record.recordType to RFC2445 CUTYPE values
+    _cuTypes = {
+        BaseRecordType.user: 'INDIVIDUAL',
+        BaseRecordType.group: 'GROUP',
+        DAVRecordType.resource: 'RESOURCE',
+        DAVRecordType.location: '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 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
+
+
+    @property
+    def displayName(self):
+        return self.fullNames[0]
+
+
+    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.name,
+            # self.shortNames, # MOVE2WHO FIXME: is this needed? it's not hashable
+            self.uid,
+            self.hasCalendars,
+        ))
+
+
+    def canonicalCalendarUserAddress(self):
+        &quot;&quot;&quot;
+            Return a CUA for this record, preferring in this order:
+            urn:uuid: form
+            mailto: form
+            /principals/__uids__/ form
+            first in calendarUserAddresses list (sorted)
+        &quot;&quot;&quot;
+
+        sortedCuas = sorted(self.calendarUserAddresses)
+
+        for prefix in (
+            &quot;urn:uuid:&quot;,
+            &quot;mailto:&quot;,
+            &quot;/principals/__uids__/&quot;
+        ):
+            for candidate in sortedCuas:
+                if candidate.startswith(prefix):
+                    return candidate
+
+        # fall back to using the first one
+        return sortedCuas[0]
+
+
+    def enabledAsOrganizer(self):
+        # FIXME:
+        from twistedcaldav.config import config
+
+        if self.recordType == self.service.recordType.user:
+            return True
+        elif self.recordType == self.service.recordType.group:
+            return config.Scheduling.Options.AllowGroupAsOrganizer
+        elif self.recordType == self.service.recordType.location:
+            return config.Scheduling.Options.AllowLocationAsOrganizer
+        elif self.recordType == self.service.recordType.resource:
+            return config.Scheduling.Options.AllowResourceAsOrganizer
+        else:
+            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;
+        # FIXME:
+        from twistedcaldav.config import config
+
+        if config.Servers.Enabled and getattr(self, &quot;serviceNodeUID&quot;, None):
+            return Servers.getServerURIById(self.serviceNodeUID)
+        else:
+            return None
+
+
+    def server(self):
+        &quot;&quot;&quot;
+        Server hosting this record. Return None if hosted on this server.
+        &quot;&quot;&quot;
+        # FIXME:
+        from twistedcaldav.config import config
+
+        if config.Servers.Enabled and getattr(self, &quot;serviceNodeUID&quot;, None):
+            return Servers.getServerById(self.serviceNodeUID)
+        else:
+            return None
+
+
+    def thisServer(self):
+        s = self.server()
+        return s.thisServer if s is not None else True
+
+
+    def isLoginEnabled(self):
+        return self.loginAllowed
+
+
+    def calendarsEnabled(self):
+        # FIXME:
+        from twistedcaldav.config import config
+
+        return config.EnableCalDAV and self.hasCalendars
+
+
+
+    @inlineCallbacks
+    def canAutoSchedule(self, organizer=None):
+        # FIXME:
+        from twistedcaldav.config import config
+
+        if config.Scheduling.Options.AutoSchedule.Enabled:
+            if (
+                config.Scheduling.Options.AutoSchedule.Always or
+                self.autoScheduleMode not in (AutoScheduleMode.none, None) or  # right???
+                (
+                    yield self.autoAcceptFromOrganizer(organizer)
+                )
+            ):
+                if (
+                    self.getCUType() != &quot;INDIVIDUAL&quot; or
+                    config.Scheduling.Options.AutoSchedule.AllowUsers
+                ):
+                    returnValue(True)
+        returnValue(False)
+
+
+    @inlineCallbacks
+    def getAutoScheduleMode(self, organizer):
+        autoScheduleMode = self.autoScheduleMode
+        if (yield self.autoAcceptFromOrganizer(organizer)):
+            autoScheduleMode = AutoScheduleMode.acceptIfFreeDeclineIfBusy
+        returnValue(autoScheduleMode)
+
+
+    @inlineCallbacks
+    def autoAcceptFromOrganizer(self, organizer):
+        try:
+            autoAcceptGroup = self.autoAcceptGroup
+        except AttributeError:
+            autoAcceptGroup = None
+
+        if (
+            organizer is not None and
+            autoAcceptGroup is not None
+        ):
+            service = self.service
+            organizerRecord = yield service.recordWithCalendarUserAddress(organizer)
+            if organizerRecord is not None:
+                autoAcceptGroup = yield service.recordWithUID(autoAcceptGroup)
+                members = yield autoAcceptGroup.expandedMembers()
+                if organizerRecord.uid in ([m.uid for m in members]):
+                    returnValue(True)
+        returnValue(False)
+
+
+    @inlineCallbacks
+    def expandedMembers(self, members=None):
+
+        if members is None:
+            members = set()
+
+        for member in (yield self.members()):
+            if member.recordType == BaseRecordType.user:
+                if member not in members:
+                    members.add(member)
+            yield member.expandedMembers(members)
+
+        returnValue(members)
+
+
+    # For scheduling/freebusy
+    @inlineCallbacks
+    def isProxyFor(self, other):
+        for recordType in (
+            DelegateRecordType.readDelegatorGroup,
+            DelegateRecordType.writeDelegatorGroup,
+        ):
+            delegatorGroup = yield self.service.recordWithShortName(
+                recordType, self.uid
+            )
+            if delegatorGroup:
+                if other in (yield delegatorGroup.members()):
+                    returnValue(True)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhogroupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/groups.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/groups.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/groups.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,4 +1,4 @@
</span><del>-# -*- test-case-name: twext.who.test.test_groups -*-
</del><ins>+# -*- test-case-name: txdav.who.test.test_groups -*-
</ins><span class="cx"> ##
</span><span class="cx"> # Copyright (c) 2013 Apple Inc. All rights reserved.
</span><span class="cx"> #
</span><span class="lines">@@ -22,8 +22,6 @@
</span><span class="cx"> from twext.enterprise.dal.record import fromTable
</span><span class="cx"> from twext.enterprise.dal.syntax import Delete, Select
</span><span class="cx"> from twext.enterprise.jobqueue import WorkItem, PeerConnectionPool
</span><del>-from txdav.who.delegates import allGroupDelegates
-from twext.who.idirectory import RecordType
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema
</span><span class="cx"> import datetime
</span><span class="lines">@@ -46,17 +44,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">@@ -68,22 +63,13 @@
</span><span class="cx"> 
</span><span class="cx">             # New implmementation
</span><span class="cx">             try:
</span><del>-                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">@@ -126,25 +112,28 @@
</span><span class="cx"> 
</span><span class="cx"> class GroupRefreshWork(WorkItem, fromTable(schema.GROUP_REFRESH_WORK)):
</span><span class="cx"> 
</span><del>-    group = property(lambda self: self.groupGUID)
</del><ins>+    # Note, the schema has &quot;groupGuid&quot;, but really it's a UID.  At some point
+    # we should change the column name.
+    group = property(lambda self: self.groupGuid)
</ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><del>-
</del><span class="cx">         # Delete all other work items for this group
</span><span class="cx">         yield Delete(
</span><del>-            From=self.table, Where=(self.table.GROUP_GUID == self.groupGUID)
</del><ins>+            From=self.table, Where=(self.table.GROUP_GUID == self.groupGuid)
</ins><span class="cx">         ).on(self.transaction)
</span><span class="cx"> 
</span><span class="cx">         groupCacher = getattr(self.transaction, &quot;_groupCacher&quot;, None)
</span><span class="cx">         if groupCacher is not None:
</span><span class="cx"> 
</span><span class="cx">             try:
</span><del>-                groupCacher.refreshGroup(self.transaction, self.groupGUID)
</del><ins>+                yield groupCacher.refreshGroup(
+                    self.transaction, self.groupGuid.decode(&quot;utf-8&quot;)
+                )
</ins><span class="cx">             except Exception, e:
</span><span class="cx">                 log.error(
</span><span class="cx">                     &quot;Failed to refresh group {group} {err}&quot;,
</span><del>-                    group=self.groupGUID, err=e
</del><ins>+                    group=self.groupGuid, err=e
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">         else:
</span><span class="lines">@@ -154,11 +143,11 @@
</span><span class="cx">             )
</span><span class="cx">             log.debug(
</span><span class="cx">                 &quot;Rescheduling group refresh for {group}: {when}&quot;,
</span><del>-                group=self.groupGUID, when=notBefore
</del><ins>+                group=self.groupGuid, when=notBefore
</ins><span class="cx">             )
</span><span class="cx">             yield self.transaction.enqueue(
</span><span class="cx">                 GroupRefreshWork,
</span><del>-                groupGUID=self.groupGUID, notBefore=notBefore
</del><ins>+                groupGuid=self.groupGuid, notBefore=notBefore
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -182,51 +171,26 @@
</span><span class="cx">             )
</span><span class="cx">         ).on(self.transaction)
</span><span class="cx"> 
</span><ins>+    # MOVE2WHO
</ins><span class="cx">     # TODO: Pull this over from groupcacher branch
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@inlineCallbacks
-def expandedMembers(record, members=None, records=None):
-    &quot;&quot;&quot;
-    Return the expanded set of member records.  Intermediate groups are not
-    returned in the results, but their members are.
-    &quot;&quot;&quot;
-    if members is None:
-        members = set()
-    if records is None:
-        records = set()
</del><span class="cx"> 
</span><del>-    if record not in records:
-        records.add(record)
-        for member in (yield record.members()):
-            if member not in records:
-                #TODO:  HACK for old-style XML. FIX
-                if (
-                    member.recordType != RecordType.group and
-                    str(member.recordType) != &quot;groups&quot;
-                ):
-                    members.add(member)
-                yield expandedMembers(member, members, records)
-
-    returnValue(members)
-
-
-
</del><span class="cx"> def diffAssignments(old, new):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Compare two proxy assignment lists and return their differences in the form
</span><span class="cx">     of two lists -- one for added/updated assignments, and one for removed
</span><span class="cx">     assignments.
</span><span class="cx"> 
</span><del>-    @param old: dictionary of delegator: (readGroupGUID, writeGroupGUID)
</del><ins>+    @param old: dictionary of delegator: (readGroupUID, writeGroupUID)
</ins><span class="cx">     @type old: C{dict}
</span><span class="cx"> 
</span><del>-    @param new: dictionary of delegator: (readGroupGUID, writeGroupGUID)
</del><ins>+    @param new: dictionary of delegator: (readGroupUID, writeGroupUID)
</ins><span class="cx">     @type new: C{dict}
</span><span class="cx"> 
</span><span class="cx">     @return: Tuple of two lists; the first list contains tuples of (delegator,
</span><del>-        (readGroupGUID, writeGroupGUID)), and represents all the new or updated
</del><ins>+        (readGroupUID, writeGroupUID)), and represents all the new or updated
</ins><span class="cx">         assignments.  The second list contains all the delegators which used to
</span><span class="cx">         have a delegate but don't anymore.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -251,13 +215,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 class="lines">@@ -269,19 +236,21 @@
</span><span class="cx">         # yield self.applyExternalAssignments(txn, externalAssignments)
</span><span class="cx"> 
</span><span class="cx">         # Figure out which groups matter
</span><del>-        groupGUIDs = yield self.groupsToRefresh(txn)
</del><ins>+        groupUIDs = yield self.groupsToRefresh(txn)
</ins><span class="cx">         self.log.debug(
</span><del>-            &quot;Number of groups to refresh: {num}&quot;, num=len(groupGUIDs)
</del><ins>+            &quot;Number of groups to refresh: {num}&quot;, num=len(groupUIDs)
</ins><span class="cx">         )
</span><span class="cx">         # For each of those groups, create a per-group refresh work item
</span><del>-        for groupGUID in groupGUIDs:
</del><ins>+        for groupUID in groupUIDs:
</ins><span class="cx">             notBefore = (
</span><span class="cx">                 datetime.datetime.utcnow() +
</span><span class="cx">                 datetime.timedelta(seconds=1)
</span><span class="cx">             )
</span><ins>+            self.log.debug(&quot;Enqueuing group refresh for {u}&quot;, u=groupUID)
</ins><span class="cx">             yield txn.enqueue(
</span><del>-                GroupRefreshWork, groupGUID=groupGUID, notBefore=notBefore
</del><ins>+                GroupRefreshWork, groupGuid=groupUID, notBefore=notBefore
</ins><span class="cx">             )
</span><ins>+            self.log.debug(&quot;Enqueued group refresh for {u}&quot;, u=groupUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -290,84 +259,91 @@
</span><span class="cx">         oldAssignments = (yield txn.externalDelegates())
</span><span class="cx"> 
</span><span class="cx">         # external assignments is of the form:
</span><del>-        # { delegatorGUID: (readDelegateGroupGUID, writeDelegateGroupGUID),
</del><ins>+        # { delegatorUID: (readDelegateGroupUID, writeDelegateGroupUID),
</ins><span class="cx">         # }
</span><span class="cx"> 
</span><span class="cx">         changed, removed = diffAssignments(oldAssignments, newAssignments)
</span><span class="cx">         if changed:
</span><span class="cx">             for (
</span><del>-                delegatorGUID, (readDelegateGUID, writeDelegateGUID)
</del><ins>+                delegatorUID, (readDelegateUID, writeDelegateUID)
</ins><span class="cx">             ) in changed:
</span><span class="cx">                 readDelegateGroupID = writeDelegateGroupID = None
</span><del>-                if readDelegateGUID:
</del><ins>+                if readDelegateUID:
</ins><span class="cx">                     readDelegateGroupID, _ignore_name, hash = (
</span><del>-                        yield txn.groupByGUID(readDelegateGUID)
</del><ins>+                        yield txn.groupByUID(readDelegateUID)
</ins><span class="cx">                     )
</span><del>-                if writeDelegateGUID:
</del><ins>+                if writeDelegateUID:
</ins><span class="cx">                     writeDelegateGroupID, _ignore_name, hash = (
</span><del>-                        yield txn.groupByGUID(writeDelegateGUID)
</del><ins>+                        yield txn.groupByUID(writeDelegateUID)
</ins><span class="cx">                     )
</span><span class="cx">                 yield txn.assignExternalDelegates(
</span><del>-                    delegatorGUID, readDelegateGroupID, writeDelegateGroupID,
-                    readDelegateGUID, writeDelegateGUID
</del><ins>+                    delegatorUID, readDelegateGroupID, writeDelegateGroupID,
+                    readDelegateUID, writeDelegateUID
</ins><span class="cx">                 )
</span><span class="cx">         if removed:
</span><del>-            for delegatorGUID in removed:
</del><ins>+            for delegatorUID in removed:
</ins><span class="cx">                 yield txn.assignExternalDelegates(
</span><del>-                    delegatorGUID, None, None, None, None
</del><ins>+                    delegatorUID, None, None, None, None
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def refreshGroup(self, txn, groupGUID):
</del><ins>+    def refreshGroup(self, txn, groupUID):
</ins><span class="cx">         # Does the work of a per-group refresh work item
</span><del>-        # Faults in the flattened membership of a group, as GUIDs
</del><ins>+        # Faults in the flattened membership of a group, as UIDs
</ins><span class="cx">         # and updates the GROUP_MEMBERSHIP table
</span><del>-        record = (yield self.directory.recordWithGUID(groupGUID))
-        membershipHashContent = hashlib.md5()
-        members = (yield expandedMembers(record))
-        members = list(members)
-        members.sort(cmp=lambda x, y: cmp(x.guid, y.guid))
-        for member in members:
-            membershipHashContent.update(str(member.guid))
-        membershipHash = membershipHashContent.hexdigest()
-        groupID, _ignore_cachedName, cachedMembershipHash = (
-            yield txn.groupByGUID(groupGUID)
-        )
-
-        if cachedMembershipHash != membershipHash:
-            membershipChanged = True
-            self.log.debug(
-                &quot;Group '{group}' changed&quot;, group=record.fullNames[0]
-            )
</del><ins>+        self.log.debug(&quot;Faulting in group: {g}&quot;, g=groupUID)
+        record = (yield self.directory.recordWithUID(groupUID))
+        if record is None:
+            # FIXME: the group has disappeared from the directory.
+            # How do we want to handle this?
+            self.log.info(&quot;Group has disappeared: {g}&quot;, g=groupUID)
</ins><span class="cx">         else:
</span><del>-            membershipChanged = False
</del><ins>+            self.log.debug(&quot;Got group record: {u}&quot;, u=record.uid)
+            membershipHashContent = hashlib.md5()
+            members = yield record.expandedMembers()
+            members = list(members)
+            members.sort(cmp=lambda x, y: cmp(x.uid, y.uid))
+            for member in members:
+                membershipHashContent.update(str(member.uid))
+            membershipHash = membershipHashContent.hexdigest()
+            groupID, _ignore_cachedName, cachedMembershipHash = (
+                yield txn.groupByUID(groupUID)
+            )
</ins><span class="cx"> 
</span><del>-        yield txn.updateGroup(groupGUID, record.fullNames[0], membershipHash)
</del><ins>+            if cachedMembershipHash != membershipHash:
+                membershipChanged = True
+                self.log.debug(
+                    &quot;Group '{group}' changed&quot;, group=record.fullNames[0]
+                )
+            else:
+                membershipChanged = False
</ins><span class="cx"> 
</span><del>-        if membershipChanged:
-            newMemberGUIDs = set()
-            for member in members:
-                newMemberGUIDs.add(member.guid)
-            yield self.synchronizeMembers(txn, groupID, newMemberGUIDs)
</del><ins>+            yield txn.updateGroup(groupUID, record.fullNames[0], membershipHash)
</ins><span class="cx"> 
</span><del>-        yield self.scheduleEventReconciliations(txn, groupID, groupGUID)
</del><ins>+            if membershipChanged:
+                newMemberUIDs = set()
+                for member in members:
+                    newMemberUIDs.add(member.uid)
+                yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
</ins><span class="cx"> 
</span><ins>+            yield self.scheduleEventReconciliations(txn, groupID, groupUID)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def synchronizeMembers(self, txn, groupID, newMemberGUIDs):
</del><ins>+    def synchronizeMembers(self, txn, groupID, newMemberUIDs):
</ins><span class="cx">         numRemoved = numAdded = 0
</span><del>-        cachedMemberGUIDs = (yield txn.membersOfGroup(groupID))
</del><ins>+        cachedMemberUIDs = (yield txn.membersOfGroup(groupID))
</ins><span class="cx"> 
</span><del>-        for memberGUID in cachedMemberGUIDs:
-            if memberGUID not in newMemberGUIDs:
</del><ins>+        for memberUID in cachedMemberUIDs:
+            if memberUID not in newMemberUIDs:
</ins><span class="cx">                 numRemoved += 1
</span><del>-                yield txn.removeMemberFromGroup(memberGUID, groupID)
</del><ins>+                yield txn.removeMemberFromGroup(memberUID, groupID)
</ins><span class="cx"> 
</span><del>-        for memberGUID in newMemberGUIDs:
-            if memberGUID not in cachedMemberGUIDs:
</del><ins>+        for memberUID in newMemberUIDs:
+            if memberUID not in cachedMemberUIDs:
</ins><span class="cx">                 numAdded += 1
</span><del>-                yield txn.addMemberToGroup(memberGUID, groupID)
</del><ins>+                yield txn.addMemberToGroup(memberUID, groupID)
</ins><span class="cx"> 
</span><span class="cx">         returnValue((numAdded, numRemoved))
</span><span class="cx"> 
</span><span class="lines">@@ -378,23 +354,23 @@
</span><span class="cx">         The members of the given group as recorded in the db
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         members = set()
</span><del>-        memberGUIDs = (yield txn.membersOfGroup(groupID))
-        for guid in memberGUIDs:
-            record = (yield self.directory.recordWithGUID(guid))
</del><ins>+        memberUIDs = (yield txn.membersOfGroup(groupID))
+        for uid in memberUIDs:
+            record = (yield self.directory.recordWithUID(uid))
</ins><span class="cx">             if record is not None:
</span><span class="cx">                 members.add(record)
</span><span class="cx">         returnValue(members)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def cachedGroupsFor(self, txn, guid):
</del><ins>+    def cachedGroupsFor(self, txn, uid):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        The IDs of the groups the guid is a member of
</del><ins>+        The UIDs of the groups the uid is a member of
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        return txn.groupsFor(guid)
</del><ins>+        return txn.groupsFor(uid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def scheduleEventReconciliations(self, txn, groupID, groupGUID):
</del><ins>+    def scheduleEventReconciliations(self, txn, groupID, groupUID):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Find all events who have this groupID as an attendee and create
</span><span class="cx">         work items for them.
</span><span class="lines">@@ -415,29 +391,29 @@
</span><span class="cx">             )
</span><span class="cx">             log.debug(
</span><span class="cx">                 &quot;scheduling group reconciliation for &quot;
</span><del>-                &quot;({eventID}, {groupID}, {groupGUID}): {when}&quot;,
</del><ins>+                &quot;({eventID}, {groupID}, {groupUID}): {when}&quot;,
</ins><span class="cx">                 eventID=eventID,
</span><span class="cx">                 groupID=groupID,
</span><del>-                groupGUID=groupGUID,
</del><ins>+                groupUID=groupUID,
</ins><span class="cx">                 when=notBefore)
</span><span class="cx"> 
</span><span class="cx">             yield txn.enqueue(
</span><span class="cx">                 GroupAttendeeReconciliationWork,
</span><span class="cx">                 eventID=eventID,
</span><span class="cx">                 groupID=groupID,
</span><del>-                groupGUID=groupGUID,
</del><ins>+                groupGuid=groupUID,
</ins><span class="cx">                 notBefore=notBefore
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def groupsToRefresh(self, txn):
</span><del>-        delegatedGUIDs = set((yield allGroupDelegates(txn)))
</del><ins>+        delegatedUIDs = set((yield txn.allGroupDelegates()))
</ins><span class="cx">         self.log.info(
</span><del>-            &quot;There are {count} group delegates&quot;, count=len(delegatedGUIDs)
</del><ins>+            &quot;There are {count} group delegates&quot;, count=len(delegatedUIDs)
</ins><span class="cx">         )
</span><span class="cx"> 
</span><del>-        attendeeGroupGUIDs = set()
</del><ins>+        attendeeGroupUIDs = set()
</ins><span class="cx"> 
</span><span class="cx">         # get all groups from events
</span><span class="cx">         groupAttendee = schema.GROUP_ATTENDEE
</span><span class="lines">@@ -447,7 +423,7 @@
</span><span class="cx">         ).on(txn)
</span><span class="cx">         groupIDs = set([row[0] for row in rows])
</span><span class="cx"> 
</span><del>-        # get groupGUIDs
</del><ins>+        # get groupUIDs
</ins><span class="cx">         if groupIDs:
</span><span class="cx">             gr = schema.GROUPS
</span><span class="cx">             rows = yield Select(
</span><span class="lines">@@ -455,6 +431,8 @@
</span><span class="cx">                 From=gr,
</span><span class="cx">                 Where=gr.GROUP_ID.In(groupIDs)
</span><span class="cx">             ).on(txn)
</span><del>-            attendeeGroupGUIDs = set([row[0] for row in rows])
</del><ins>+            attendeeGroupUIDs = set([row[0] for row in rows])
</ins><span class="cx"> 
</span><del>-        returnValue(delegatedGUIDs.union(attendeeGroupGUIDs))
</del><ins>+        # FIXME: is this a good place to clear out unreferenced groups?
+
+        returnValue(delegatedUIDs.union(attendeeGroupUIDs))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhoidirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/idirectory.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/idirectory.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/idirectory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -23,6 +23,9 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><ins>+    &quot;AutoScheduleMode&quot;,
+    &quot;RecordType&quot;,
+    &quot;FieldName&quot;,
</ins><span class="cx"> ]
</span><span class="cx"> 
</span><span class="cx"> from twisted.python.constants import Names, NamedConstant
</span><span class="lines">@@ -152,3 +155,26 @@
</span><span class="cx">     autoAcceptGroup = NamedConstant()
</span><span class="cx">     autoAcceptGroup.description = u&quot;auto-accept group&quot;
</span><span class="cx">     autoAcceptGroup.valueType = BaseFieldName.valueType(BaseFieldName.uid)
</span><ins>+
+    # For &quot;locations&quot;, i.e., scheduled spaces:
+
+    associatedAddress = NamedConstant()
+    associatedAddress.description = u&quot;associated address UID&quot;
+
+    capacity = NamedConstant()
+    capacity.description = u&quot;room capacity&quot;
+    capacity.valueType = int
+
+    floor = NamedConstant()
+    floor.description = u&quot;building floor&quot;
+
+    # For &quot;addresses&quot;, i.e., non-scheduled areas containing locations:
+
+    abbreviatedName = NamedConstant()
+    abbreviatedName.description = u&quot;abbreviated name&quot;
+
+    geographicLocation = NamedConstant()
+    geographicLocation.description = u&quot;geographic location URI&quot;
+
+    streetAddress = NamedConstant()
+    streetAddress.description = u&quot;street address&quot;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/accounts.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/accounts.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/accounts.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,174 +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;email-address&gt;group01@example.com&lt;/email-address&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;email-address&gt;group02@example.com&lt;/email-address&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="CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsaugmentsxmlfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhotestaccountsaugmentsxml"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/augments.xml (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/test/accounts/augments.xml) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/augments.xml                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/augments.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,185 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+&lt;!DOCTYPE augments SYSTEM &quot;augments.dtd&quot;&gt;
+
+&lt;augments&gt;
+  &lt;record&gt;
+    &lt;uid&gt;Default&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+  &lt;/record&gt;
+  &lt;record repeat=&quot;10&quot;&gt;
+    &lt;uid&gt;location%02d&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record repeat=&quot;4&quot;&gt;
+    &lt;uid&gt;resource%02d&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource05&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;none&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource06&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;accept-always&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource07&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;decline-always&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource08&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;accept-if-free&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource09&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;decline-if-busy&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource10&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;automatic&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;resource11&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;decline-always&lt;/auto-schedule-mode&gt;
+    &lt;auto-accept-group&gt;group01&lt;/auto-accept-group&gt;
+  &lt;/record&gt;
+  &lt;record repeat=&quot;10&quot;&gt;
+    &lt;uid&gt;group%02d&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;disabledgroup&lt;/uid&gt;
+    &lt;enable&gt;false&lt;/enable&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;delegatedroom&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;false&lt;/enable-addressbook&gt;
+    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;03DFF660-8BCC-4198-8588-DD77F776F518&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;80689D41-DAF8-4189-909C-DB017B271892&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;C38BEE7A-36EE-478C-9DCB-CBF4612AFE65&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+    &lt;auto-accept-group&gt;group01&lt;/auto-accept-group&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;0CE0BF31-5F9E-4801-A489-8C70CF287F5F&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;false&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;06E3BDCB-9C19-485A-B14E-F146A80ADDC6&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+  &lt;record&gt;
+    &lt;uid&gt;4D66A20A-1437-437D-8069-2F14E8322234&lt;/uid&gt;
+    &lt;enable&gt;true&lt;/enable&gt;
+    &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
+    &lt;enable-addressbook&gt;true&lt;/enable-addressbook&gt;
+    &lt;enable-login&gt;true&lt;/enable-login&gt;
+    &lt;auto-schedule&gt;true&lt;/auto-schedule&gt;
+    &lt;auto-schedule-mode&gt;default&lt;/auto-schedule-mode&gt;
+  &lt;/record&gt;
+&lt;/augments&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotestaccountsresourcesxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/resources.xml (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/resources.xml        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/accounts/resources.xml        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -1,34 +1,273 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-
-&lt;!--
-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.
- --&gt;
-
-&lt;!DOCTYPE accounts SYSTEM &quot;accounts.dtd&quot;&gt;
-
-&lt;accounts realm=&quot;Test Realm&quot;&gt;
-  &lt;location repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;location%02d&lt;/uid&gt;
-    &lt;guid&gt;location%02d&lt;/guid&gt;
-    &lt;password&gt;location%02d&lt;/password&gt;
-    &lt;name&gt;Room %02d&lt;/name&gt;
-  &lt;/location&gt;
-  &lt;resource repeat=&quot;10&quot;&gt;
-    &lt;uid&gt;resource%02d&lt;/uid&gt;
-    &lt;guid&gt;resource%02d&lt;/guid&gt;
-    &lt;password&gt;resource%02d&lt;/password&gt;
-    &lt;name&gt;Resource %02d&lt;/name&gt;
-  &lt;/resource&gt;
-&lt;/accounts&gt;
</del><ins>+&lt;directory realm=&quot;Test Realm&quot;&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;fantastic&lt;/short-name&gt;
+    &lt;uid&gt;4D66A20A-1437-437D-8069-2F14E8322234&lt;/uid&gt;
+    &lt;full-name&gt;Fantastic Conference Room&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;associatedAddress&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/associatedAddress&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;jupiter&lt;/short-name&gt;
+    &lt;uid&gt;jupiter&lt;/uid&gt;
+    &lt;full-name&gt;Jupiter Conference Room, Building 2, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;uranus&lt;/short-name&gt;
+    &lt;uid&gt;uranus&lt;/uid&gt;
+    &lt;full-name&gt;Uranus Conference Room, Building 3, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;morgensroom&lt;/short-name&gt;
+    &lt;uid&gt;03DFF660-8BCC-4198-8588-DD77F776F518&lt;/uid&gt;
+    &lt;full-name&gt;Morgen's Room&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;mercury&lt;/short-name&gt;
+    &lt;uid&gt;mercury&lt;/uid&gt;
+    &lt;full-name&gt;Mercury Conference Room, Building 1, 2nd Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location09&lt;/short-name&gt;
+    &lt;uid&gt;location09&lt;/uid&gt;
+    &lt;full-name&gt;Room 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location08&lt;/short-name&gt;
+    &lt;uid&gt;location08&lt;/uid&gt;
+    &lt;full-name&gt;Room 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location07&lt;/short-name&gt;
+    &lt;uid&gt;location07&lt;/uid&gt;
+    &lt;full-name&gt;Room 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location06&lt;/short-name&gt;
+    &lt;uid&gt;location06&lt;/uid&gt;
+    &lt;full-name&gt;Room 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location05&lt;/short-name&gt;
+    &lt;uid&gt;location05&lt;/uid&gt;
+    &lt;full-name&gt;Room 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location04&lt;/short-name&gt;
+    &lt;uid&gt;location04&lt;/uid&gt;
+    &lt;full-name&gt;Room 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location03&lt;/short-name&gt;
+    &lt;uid&gt;location03&lt;/uid&gt;
+    &lt;full-name&gt;Room 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location02&lt;/short-name&gt;
+    &lt;uid&gt;location02&lt;/uid&gt;
+    &lt;full-name&gt;Room 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location01&lt;/short-name&gt;
+    &lt;uid&gt;location01&lt;/uid&gt;
+    &lt;full-name&gt;Room 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;delegatedroom&lt;/short-name&gt;
+    &lt;uid&gt;delegatedroom&lt;/uid&gt;
+    &lt;full-name&gt;Delegated Conference Room&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;mars&lt;/short-name&gt;
+    &lt;uid&gt;redplanet&lt;/uid&gt;
+    &lt;full-name&gt;Mars Conference Room, Building 1, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;sharissroom&lt;/short-name&gt;
+    &lt;uid&gt;80689D41-DAF8-4189-909C-DB017B271892&lt;/uid&gt;
+    &lt;full-name&gt;Shari's Room&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;associatedAddress&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/associatedAddress&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;pluto&lt;/short-name&gt;
+    &lt;uid&gt;pluto&lt;/uid&gt;
+    &lt;full-name&gt;Pluto Conference Room, Building 2, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;saturn&lt;/short-name&gt;
+    &lt;uid&gt;saturn&lt;/uid&gt;
+    &lt;full-name&gt;Saturn Conference Room, Building 2, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;location10&lt;/short-name&gt;
+    &lt;uid&gt;location10&lt;/uid&gt;
+    &lt;full-name&gt;Room 10&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;pretend&lt;/short-name&gt;
+    &lt;uid&gt;06E3BDCB-9C19-485A-B14E-F146A80ADDC6&lt;/uid&gt;
+    &lt;full-name&gt;Pretend Conference Room&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;associatedAddress&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/associatedAddress&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;neptune&lt;/short-name&gt;
+    &lt;uid&gt;neptune&lt;/uid&gt;
+    &lt;full-name&gt;Neptune Conference Room, Building 2, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;Earth&lt;/short-name&gt;
+    &lt;uid&gt;Earth&lt;/uid&gt;
+    &lt;full-name&gt;Earth Conference Room, Building 1, 1st Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;location&quot;&gt;
+    &lt;short-name&gt;venus&lt;/short-name&gt;
+    &lt;uid&gt;venus&lt;/uid&gt;
+    &lt;full-name&gt;Venus Conference Room, Building 1, 2nd Floor&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;sharisotherresource&lt;/short-name&gt;
+    &lt;uid&gt;CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9&lt;/uid&gt;
+    &lt;full-name&gt;Shari's Other Resource&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource15&lt;/short-name&gt;
+    &lt;uid&gt;resource15&lt;/uid&gt;
+    &lt;full-name&gt;Resource 15&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource14&lt;/short-name&gt;
+    &lt;uid&gt;resource14&lt;/uid&gt;
+    &lt;full-name&gt;Resource 14&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource17&lt;/short-name&gt;
+    &lt;uid&gt;resource17&lt;/uid&gt;
+    &lt;full-name&gt;Resource 17&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource16&lt;/short-name&gt;
+    &lt;uid&gt;resource16&lt;/uid&gt;
+    &lt;full-name&gt;Resource 16&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource11&lt;/short-name&gt;
+    &lt;uid&gt;resource11&lt;/uid&gt;
+    &lt;full-name&gt;Resource 11&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource10&lt;/short-name&gt;
+    &lt;uid&gt;resource10&lt;/uid&gt;
+    &lt;full-name&gt;Resource 10&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource13&lt;/short-name&gt;
+    &lt;uid&gt;resource13&lt;/uid&gt;
+    &lt;full-name&gt;Resource 13&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource12&lt;/short-name&gt;
+    &lt;uid&gt;resource12&lt;/uid&gt;
+    &lt;full-name&gt;Resource 12&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource19&lt;/short-name&gt;
+    &lt;uid&gt;resource19&lt;/uid&gt;
+    &lt;full-name&gt;Resource 19&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource18&lt;/short-name&gt;
+    &lt;uid&gt;resource18&lt;/uid&gt;
+    &lt;full-name&gt;Resource 18&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;sharisresource&lt;/short-name&gt;
+    &lt;uid&gt;C38BEE7A-36EE-478C-9DCB-CBF4612AFE65&lt;/uid&gt;
+    &lt;full-name&gt;Shari's Resource&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource20&lt;/short-name&gt;
+    &lt;uid&gt;resource20&lt;/uid&gt;
+    &lt;full-name&gt;Resource 20&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource06&lt;/short-name&gt;
+    &lt;uid&gt;resource06&lt;/uid&gt;
+    &lt;full-name&gt;Resource 06&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource07&lt;/short-name&gt;
+    &lt;uid&gt;resource07&lt;/uid&gt;
+    &lt;full-name&gt;Resource 07&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource04&lt;/short-name&gt;
+    &lt;uid&gt;resource04&lt;/uid&gt;
+    &lt;full-name&gt;Resource 04&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource05&lt;/short-name&gt;
+    &lt;uid&gt;resource05&lt;/uid&gt;
+    &lt;full-name&gt;Resource 05&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource02&lt;/short-name&gt;
+    &lt;uid&gt;resource02&lt;/uid&gt;
+    &lt;full-name&gt;Resource 02&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource03&lt;/short-name&gt;
+    &lt;uid&gt;resource03&lt;/uid&gt;
+    &lt;full-name&gt;Resource 03&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource01&lt;/short-name&gt;
+    &lt;uid&gt;resource01&lt;/uid&gt;
+    &lt;full-name&gt;Resource 01&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;sharisotherresource1&lt;/short-name&gt;
+    &lt;uid&gt;0CE0BF31-5F9E-4801-A489-8C70CF287F5F&lt;/uid&gt;
+    &lt;full-name&gt;Shari's Other Resource1&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource08&lt;/short-name&gt;
+    &lt;uid&gt;resource08&lt;/uid&gt;
+    &lt;full-name&gt;Resource 08&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;resource&quot;&gt;
+    &lt;short-name&gt;resource09&lt;/short-name&gt;
+    &lt;uid&gt;resource09&lt;/uid&gt;
+    &lt;full-name&gt;Resource 09&lt;/full-name&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;testaddress1&lt;/short-name&gt;
+    &lt;uid&gt;6F9EE33B-78F6-481B-9289-3D0812FF0D64&lt;/uid&gt;
+    &lt;full-name&gt;Test Address One&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;streetAddress&gt;20300 Stevens Creek Blvd, Cupertino, CA 95014&lt;/streetAddress&gt;
+      &lt;geo&gt;37.322281,-122.028345&lt;/geo&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il2&lt;/short-name&gt;
+    &lt;uid&gt;63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3&lt;/uid&gt;
+    &lt;full-name&gt;IL2&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;streetAddress&gt;2 Infinite Loop, Cupertino, CA 95014&lt;/streetAddress&gt;
+      &lt;geo&gt;37.332633,-122.030502&lt;/geo&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+  &lt;record type=&quot;address&quot;&gt;
+    &lt;short-name&gt;il1&lt;/short-name&gt;
+    &lt;uid&gt;76E7ECA6-08BC-4AE7-930D-F2E7453993A5&lt;/uid&gt;
+    &lt;full-name&gt;IL1&lt;/full-name&gt;
+    &lt;extras&gt;
+      &lt;streetAddress&gt;1 Infinite Loop, Cupertino, CA 95014&lt;/streetAddress&gt;
+      &lt;geo&gt;37.331741,-122.030333&lt;/geo&gt;
+    &lt;/extras&gt;
+  &lt;/record&gt;
+&lt;/directory&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_augmentpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhotesttest_augmentpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_augment.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/test/test_augment.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_augment.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_augment.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,62 @@
</span><ins>+##
+# Copyright (c) 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;
+Augment service tests
+&quot;&quot;&quot;
+
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.test.util import StoreTestCase
+from txdav.who.groups import GroupCacher
+
+
+class AugmentTest(StoreTestCase):
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(AugmentTest, self).setUp()
+        self.groupCacher = GroupCacher(self.directory)
+
+
+    @inlineCallbacks
+    def test_groups(self):
+        &quot;&quot;&quot;
+        Make sure augmented record groups( ) returns only the groups that have
+        been refreshed.
+        &quot;&quot;&quot;
+
+        store = self.storeUnderTest()
+
+        txn = store.newTransaction()
+        yield self.groupCacher.refreshGroup(txn, u&quot;__top_group_1__&quot;)
+        yield txn.commit()
+        record = yield self.directory.recordWithUID(u&quot;__sagen1__&quot;)
+        groups = yield record.groups()
+        self.assertEquals(
+            set([&quot;__top_group_1__&quot;]),
+            set([g.uid for g in groups])
+        )
+
+        txn = store.newTransaction()
+        yield self.groupCacher.refreshGroup(txn, u&quot;__sub_group_1__&quot;)
+        yield txn.commit()
+
+        record = yield self.directory.recordWithUID(u&quot;__sagen1__&quot;)
+        groups = yield record.groups()
+        self.assertEquals(
+            set([&quot;__top_group_1__&quot;, &quot;__sub_group_1__&quot;]),
+            set([g.uid for g in groups])
+        )
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_delegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_delegates.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_delegates.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_delegates.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -19,14 +19,13 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from txdav.who.delegates import (
</span><del>-    addDelegate, removeDelegate, delegatesOf, delegatedTo, allGroupDelegates
</del><ins>+    addDelegate, removeDelegate, delegatesOf, delegatedTo,
+    RecordType as DelegateRecordType
</ins><span class="cx"> )
</span><span class="cx"> from txdav.who.groups import GroupCacher
</span><span class="cx"> from twext.who.idirectory import RecordType
</span><del>-from twext.who.test.test_xml import xmlService
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><del>-from uuid import UUID
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class DelegationTest(StoreTestCase):
</span><span class="lines">@@ -34,40 +33,83 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(DelegationTest, self).setUp()
</span><del>-        self.xmlService = xmlService(self.mktemp(), xmlData=testXMLConfig)
-        self.groupCacher = GroupCacher(self.xmlService)
</del><ins>+        self.store = self.storeUnderTest()
+        self.groupCacher = GroupCacher(self.directory)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_directDelegation(self):
</span><del>-        store = self.storeUnderTest()
-        txn = store.newTransaction()
</del><ins>+        txn = self.store.newTransaction()
</ins><span class="cx"> 
</span><del>-        delegator = yield self.xmlService.recordWithUID(u&quot;__wsanchez__&quot;)
-        delegate1 = yield self.xmlService.recordWithUID(u&quot;__sagen__&quot;)
-        delegate2 = yield self.xmlService.recordWithUID(u&quot;__cdaboo__&quot;)
</del><ins>+        delegator = yield self.directory.recordWithUID(u&quot;__wsanchez1__&quot;)
+        delegate1 = yield self.directory.recordWithUID(u&quot;__sagen1__&quot;)
+        delegate2 = yield self.directory.recordWithUID(u&quot;__cdaboo1__&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Add 1 delegate
</span><span class="cx">         yield addDelegate(txn, delegator, delegate1, True)
</span><span class="cx">         delegates = (yield delegatesOf(txn, delegator, True))
</span><del>-        self.assertEquals([&quot;sagen&quot;], [d.shortNames[0] for d in delegates])
</del><ins>+        self.assertEquals([u&quot;__sagen1__&quot;], [d.uid for d in delegates])
</ins><span class="cx">         delegators = (yield delegatedTo(txn, delegate1, True))
</span><del>-        self.assertEquals([&quot;wsanchez&quot;], [d.shortNames[0] for d in delegators])
</del><ins>+        self.assertEquals([u&quot;__wsanchez1__&quot;], [d.uid for d in delegators])
</ins><span class="cx"> 
</span><ins>+        yield txn.commit()  # So delegateService will see the changes
+        txn = self.store.newTransaction()
+
+        # The &quot;proxy-write&quot; pseudoGroup will have one member
+        pseudoGroup = yield self.directory.recordWithShortName(
+            DelegateRecordType.writeDelegateGroup,
+            u&quot;__wsanchez1__&quot;
+        )
+        self.assertEquals(pseudoGroup.uid, u&quot;__wsanchez1__#calendar-proxy-write&quot;)
+        self.assertEquals(
+            [r.uid for r in (yield pseudoGroup.members())],
+            [u&quot;__sagen1__&quot;]
+        )
+        # The &quot;proxy-read&quot; pseudoGroup will have no members
+        pseudoGroup = yield self.directory.recordWithShortName(
+            DelegateRecordType.readDelegateGroup,
+            u&quot;__wsanchez1__&quot;
+        )
+        self.assertEquals(pseudoGroup.uid, u&quot;__wsanchez1__#calendar-proxy-read&quot;)
+        self.assertEquals(
+            [r.uid for r in (yield pseudoGroup.members())],
+            []
+        )
+        # The &quot;proxy-write-for&quot; pseudoGroup will have one member
+        pseudoGroup = yield self.directory.recordWithShortName(
+            DelegateRecordType.writeDelegatorGroup,
+            u&quot;__sagen1__&quot;
+        )
+        self.assertEquals(pseudoGroup.uid, u&quot;__sagen1__#calendar-proxy-write-for&quot;)
+        self.assertEquals(
+            [r.uid for r in (yield pseudoGroup.members())],
+            [u&quot;__wsanchez1__&quot;]
+        )
+        # The &quot;proxy-read-for&quot; pseudoGroup will have no members
+        pseudoGroup = yield self.directory.recordWithShortName(
+            DelegateRecordType.readDelegatorGroup,
+            u&quot;__sagen1__&quot;
+        )
+        self.assertEquals(pseudoGroup.uid, u&quot;__sagen1__#calendar-proxy-read-for&quot;)
+        self.assertEquals(
+            [r.uid for r in (yield pseudoGroup.members())],
+            []
+        )
+
</ins><span class="cx">         # Add another delegate
</span><span class="cx">         yield addDelegate(txn, delegator, delegate2, True)
</span><span class="cx">         delegates = (yield delegatesOf(txn, delegator, True))
</span><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;, &quot;cdaboo&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><span class="cx">         delegators = (yield delegatedTo(txn, delegate2, True))
</span><del>-        self.assertEquals([&quot;wsanchez&quot;], [d.shortNames[0] for d in delegators])
</del><ins>+        self.assertEquals([u&quot;__wsanchez1__&quot;], [d.uid for d in delegators])
</ins><span class="cx"> 
</span><span class="cx">         # Remove 1 delegate
</span><span class="cx">         yield removeDelegate(txn, delegator, delegate1, True)
</span><span class="cx">         delegates = (yield delegatesOf(txn, delegator, True))
</span><del>-        self.assertEquals([&quot;cdaboo&quot;], [d.shortNames[0] for d in delegates])
</del><ins>+        self.assertEquals([u&quot;__cdaboo1__&quot;], [d.uid for d in delegates])
</ins><span class="cx">         delegators = (yield delegatedTo(txn, delegate1, True))
</span><span class="cx">         self.assertEquals(0, len(delegators))
</span><span class="cx"> 
</span><span class="lines">@@ -78,162 +120,116 @@
</span><span class="cx">         delegators = (yield delegatedTo(txn, delegate2, True))
</span><span class="cx">         self.assertEquals(0, len(delegators))
</span><span class="cx"> 
</span><ins>+        yield txn.commit()  # So delegateService will see the changes
</ins><span class="cx"> 
</span><ins>+        # Now set delegate assignments by using pseudoGroup.setMembers()
+        pseudoGroup = yield self.directory.recordWithShortName(
+            DelegateRecordType.writeDelegateGroup,
+            u&quot;__wsanchez1__&quot;
+        )
+        yield pseudoGroup.setMembers([delegate1, delegate2])
+
+        # Verify the assignments were made
+        txn = self.store.newTransaction()
+        delegates = (yield delegatesOf(txn, delegator, True))
+        self.assertEquals(
+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;]),
+            set([d.uid for d in delegates])
+        )
+        yield txn.commit()
+
+        # Set a different group of assignments:
+        yield pseudoGroup.setMembers([delegate2])
+
+        # Verify the assignments were made
+        txn = self.store.newTransaction()
+        delegates = (yield delegatesOf(txn, delegator, True))
+        self.assertEquals(
+            set([u&quot;__cdaboo1__&quot;]),
+            set([d.uid for d in delegates])
+        )
+        yield txn.commit()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_indirectDelegation(self):
</span><del>-        store = self.storeUnderTest()
-        txn = store.newTransaction()
</del><ins>+        txn = self.store.newTransaction()
</ins><span class="cx"> 
</span><del>-        delegator = yield self.xmlService.recordWithUID(u&quot;__wsanchez__&quot;)
-        delegate1 = yield self.xmlService.recordWithUID(u&quot;__sagen__&quot;)
-        group1 = yield self.xmlService.recordWithUID(u&quot;__top_group_1__&quot;)
-        group2 = yield self.xmlService.recordWithUID(u&quot;__sub_group_1__&quot;)
</del><ins>+        delegator = yield self.directory.recordWithUID(u&quot;__wsanchez1__&quot;)
+        delegate1 = yield self.directory.recordWithUID(u&quot;__sagen1__&quot;)
+        group1 = yield self.directory.recordWithUID(u&quot;__top_group_1__&quot;)
+        group2 = yield self.directory.recordWithUID(u&quot;__sub_group_1__&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Add group delegate, but before the group membership has been
</span><span class="cx">         # pulled in
</span><span class="cx">         yield addDelegate(txn, delegator, group1, True)
</span><del>-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        # Passing expanded=False will return the group
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=False))
+        self.assertEquals(1, len(delegates))
+        self.assertEquals(delegates[0].uid, u&quot;__top_group_1__&quot;)
+        # Passing expanded=True will return not the group -- it only returns
+        # non-groups
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(0, len(delegates))
</span><span class="cx"> 
</span><span class="cx">         # Now refresh the group and there will be 3 delegates (contained
</span><span class="cx">         # within 2 nested groups)
</span><span class="cx">         # guid = &quot;49b350c69611477b94d95516b13856ab&quot;
</span><del>-        yield self.groupCacher.refreshGroup(txn, group1.guid)
-        yield self.groupCacher.refreshGroup(txn, group2.guid)
-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        yield self.groupCacher.refreshGroup(txn, group1.uid)
+        yield self.groupCacher.refreshGroup(txn, group2.uid)
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;, &quot;cdaboo&quot;, &quot;glyph&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;, u&quot;__glyph1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><span class="cx">         delegators = (yield delegatedTo(txn, delegate1, True))
</span><del>-        self.assertEquals([&quot;wsanchez&quot;], [d.shortNames[0] for d in delegators])
</del><ins>+        self.assertEquals([u&quot;__wsanchez1__&quot;], [d.uid for d in delegators])
</ins><span class="cx"> 
</span><span class="cx">         # Verify we can ask for all delegated-to groups
</span><span class="cx">         yield addDelegate(txn, delegator, group2, True)
</span><del>-        groups = (yield allGroupDelegates(txn))
</del><ins>+        groups = (yield txn.allGroupDelegates())
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([
-                UUID(&quot;49b350c69611477b94d95516b13856ab&quot;),
-                UUID(&quot;86144f73345a409782f1b782672087c7&quot;)
-                ]), set(groups))
</del><ins>+            set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
+        )
</ins><span class="cx"> 
</span><span class="cx">         # Delegate to a user who is already indirectly delegated-to
</span><span class="cx">         yield addDelegate(txn, delegator, delegate1, True)
</span><del>-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;, &quot;cdaboo&quot;, &quot;glyph&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;, u&quot;__glyph1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Add a member to the group; they become a delegate
</span><span class="cx">         newSet = set()
</span><del>-        for name in (u&quot;wsanchez&quot;, u&quot;cdaboo&quot;, u&quot;sagen&quot;, u&quot;glyph&quot;, u&quot;dre&quot;):
</del><ins>+        for name in (u&quot;wsanchez1&quot;, u&quot;cdaboo1&quot;, u&quot;sagen1&quot;, u&quot;glyph1&quot;, u&quot;dre1&quot;):
</ins><span class="cx">             record = (
</span><del>-                yield self.xmlService.recordWithShortName(RecordType.user, name)
</del><ins>+                yield self.directory.recordWithShortName(RecordType.user, name)
</ins><span class="cx">             )
</span><del>-            newSet.add(record.guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(group1.guid))
</del><ins>+            newSet.add(record.uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(group1.uid))
</ins><span class="cx">         numAdded, numRemoved = (
</span><span class="cx">             yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
</span><span class="cx">         )
</span><del>-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;, &quot;cdaboo&quot;, &quot;glyph&quot;, &quot;dre&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;, u&quot;__glyph1__&quot;, u&quot;__dre1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Remove delegate access from the top group
</span><span class="cx">         yield removeDelegate(txn, delegator, group1, True)
</span><del>-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;, &quot;cdaboo&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;, u&quot;__cdaboo1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Remove delegate access from the sub group
</span><span class="cx">         yield removeDelegate(txn, delegator, group2, True)
</span><del>-        delegates = (yield delegatesOf(txn, delegator, True))
</del><ins>+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
</ins><span class="cx">         self.assertEquals(
</span><del>-            set([&quot;sagen&quot;]),
-            set([d.shortNames[0] for d in delegates])
</del><ins>+            set([u&quot;__sagen1__&quot;]),
+            set([d.uid for d in delegates])
</ins><span class="cx">         )
</span><del>-
-
-
-testXMLConfig = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-
-&lt;directory realm=&quot;xyzzy&quot;&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__wsanchez__&lt;/uid&gt;
-    &lt;guid&gt;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&lt;/guid&gt;
-    &lt;short-name&gt;wsanchez&lt;/short-name&gt;
-    &lt;short-name&gt;wilfredo_sanchez&lt;/short-name&gt;
-    &lt;full-name&gt;Wilfredo Sanchez&lt;/full-name&gt;
-    &lt;password&gt;zehcnasw&lt;/password&gt;
-    &lt;email&gt;wsanchez@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;wsanchez@devnull.twistedmatrix.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__glyph__&lt;/uid&gt;
-    &lt;guid&gt;9064DF91-1DBC-4E07-9C2B-6839B0953876&lt;/guid&gt;
-    &lt;short-name&gt;glyph&lt;/short-name&gt;
-    &lt;full-name&gt;Glyph Lefkowitz&lt;/full-name&gt;
-    &lt;password&gt;hpylg&lt;/password&gt;
-    &lt;email&gt;glyph@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;glyph@devnull.twistedmatrix.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__sagen__&lt;/uid&gt;
-    &lt;guid&gt;4AD155CB-AE9B-475F-986C-E08A7537893E&lt;/guid&gt;
-    &lt;short-name&gt;sagen&lt;/short-name&gt;
-    &lt;full-name&gt;Morgen Sagen&lt;/full-name&gt;
-    &lt;password&gt;negas&lt;/password&gt;
-    &lt;email&gt;sagen@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;shared@example.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__cdaboo__&lt;/uid&gt;
-    &lt;guid&gt;7D45CB10-479E-456B-B54D-528958C5734B&lt;/guid&gt;
-    &lt;short-name&gt;cdaboo&lt;/short-name&gt;
-    &lt;full-name&gt;Cyrus Daboo&lt;/full-name&gt;
-    &lt;password&gt;suryc&lt;/password&gt;
-    &lt;email&gt;cdaboo@bitbucket.calendarserver.org&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__dre__&lt;/uid&gt;
-    &lt;guid&gt;CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B&lt;/guid&gt;
-    &lt;short-name&gt;dre&lt;/short-name&gt;
-    &lt;full-name&gt;Andre LaBranche&lt;/full-name&gt;
-    &lt;password&gt;erd&lt;/password&gt;
-    &lt;email&gt;dre@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;shared@example.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;group&quot;&gt;
-    &lt;uid&gt;__top_group_1__&lt;/uid&gt;
-    &lt;guid&gt;49B350C6-9611-477B-94D9-5516B13856AB&lt;/guid&gt;
-    &lt;short-name&gt;top-group-1&lt;/short-name&gt;
-    &lt;full-name&gt;Top Group 1&lt;/full-name&gt;
-    &lt;email&gt;topgroup1@example.com&lt;/email&gt;
-    &lt;member-uid&gt;__wsanchez__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__glyph__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__sub_group_1__&lt;/member-uid&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;group&quot;&gt;
-    &lt;uid&gt;__sub_group_1__&lt;/uid&gt;
-    &lt;guid&gt;86144F73-345A-4097-82F1-B782672087C7&lt;/guid&gt;
-    &lt;short-name&gt;sub-group-1&lt;/short-name&gt;
-    &lt;full-name&gt;Sub Group 1&lt;/full-name&gt;
-    &lt;email&gt;subgroup1@example.com&lt;/email&gt;
-    &lt;member-uid&gt;__sagen__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__cdaboo__&lt;/member-uid&gt;
-  &lt;/record&gt;
-
-&lt;/directory&gt;
-&quot;&quot;&quot;
</del><ins>+        yield txn.commit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_directorypyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhotesttest_directorypy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_directory.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/test/test_directory.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_directory.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_directory.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,125 @@
</span><ins>+##
+# Copyright (c) 2013 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 tests
+&quot;&quot;&quot;
+
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.test.util import StoreTestCase
+from twext.who.directory import DirectoryRecord
+from twext.who.idirectory import FieldName, RecordType
+from txdav.who.directory import CalendarDirectoryRecordMixin
+from uuid import UUID
+from twext.who.expression import (
+    MatchType, MatchFlags, MatchExpression
+)
+
+
+
+class TestDirectoryRecord(DirectoryRecord, CalendarDirectoryRecordMixin):
+    pass
+
+
+class DirectoryTestCase(StoreTestCase):
+
+    @inlineCallbacks
+    def test_expandedMembers(self):
+
+        record = yield self.directory.recordWithUID(u&quot;both_coasts&quot;)
+
+        direct = yield record.members()
+        self.assertEquals(
+            set([u&quot;left_coast&quot;, u&quot;right_coast&quot;]),
+            set([r.uid for r in direct])
+        )
+
+        expanded = yield record.expandedMembers()
+        self.assertEquals(
+            set([u&quot;Chris Lecroy&quot;, u&quot;Cyrus Daboo&quot;, u&quot;David Reid&quot;, u&quot;Wilfredo Sanchez&quot;]),
+            set([r.displayName for r in expanded])
+        )
+
+
+    def test_canonicalCalendarUserAddress(self):
+
+        record = TestDirectoryRecord(
+            self.directory,
+            {
+                FieldName.uid: u&quot;uid&quot;,
+                FieldName.shortNames: [u&quot;name&quot;],
+                FieldName.recordType: RecordType.user,
+            }
+        )
+        self.assertEquals(
+            record.canonicalCalendarUserAddress(),
+            u&quot;/principals/__uids__/uid/&quot;
+        )
+
+
+        record = TestDirectoryRecord(
+            self.directory,
+            {
+                FieldName.uid: u&quot;uid&quot;,
+                FieldName.shortNames: [u&quot;name&quot;],
+                FieldName.emailAddresses: [u&quot;test@example.com&quot;],
+                FieldName.recordType: RecordType.user,
+            }
+        )
+        self.assertEquals(
+            record.canonicalCalendarUserAddress(),
+            u&quot;mailto:test@example.com&quot;
+        )
+
+
+        record = TestDirectoryRecord(
+            self.directory,
+            {
+                FieldName.uid: u&quot;uid&quot;,
+                FieldName.guid: UUID(&quot;E2F6C57F-BB15-4EF9-B0AC-47A7578386F1&quot;),
+                FieldName.shortNames: [u&quot;name&quot;],
+                FieldName.emailAddresses: [u&quot;test@example.com&quot;],
+                FieldName.recordType: RecordType.user,
+            }
+        )
+        self.assertEquals(
+            record.canonicalCalendarUserAddress(),
+            u&quot;urn:uuid:E2F6C57F-BB15-4EF9-B0AC-47A7578386F1&quot;
+        )
+
+
+    @inlineCallbacks
+    def test_recordsFromMatchExpression(self):
+        expression = MatchExpression(
+            FieldName.uid,
+            u&quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;,
+            MatchType.equals,
+            MatchFlags.none
+        )
+        records = yield self.directory.recordsFromExpression(expression)
+        self.assertEquals(len(records), 1)
+
+
+    @inlineCallbacks
+    def test_recordsFromMatchExpressionNonUnicode(self):
+        expression = MatchExpression(
+            FieldName.guid,
+            UUID(&quot;6423F94A-6B76-4A3A-815B-D52CFD77935D&quot;),
+            MatchType.equals,
+            MatchFlags.caseInsensitive
+        )
+        records = yield self.directory.recordsFromExpression(expression)
+        self.assertEquals(len(records), 1)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_groupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_groups.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_groups.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_groups.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -18,13 +18,11 @@
</span><span class="cx"> Group membership caching implementation tests
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from txdav.who.groups import GroupCacher, expandedMembers, diffAssignments
</del><ins>+from txdav.who.groups import GroupCacher, diffAssignments
</ins><span class="cx"> from twext.who.idirectory import RecordType
</span><del>-from twext.who.test.test_xml import xmlService
</del><span class="cx"> from twisted.internet.defer import inlineCallbacks
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> from txdav.common.icommondatastore import NotFoundError
</span><del>-from uuid import UUID
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class GroupCacherTest(StoreTestCase):
</span><span class="lines">@@ -32,33 +30,10 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(GroupCacherTest, self).setUp()
</span><del>-        self.xmlService = xmlService(self.mktemp(), xmlData=testXMLConfig)
-        self.groupCacher = GroupCacher(self.xmlService)
</del><ins>+        self.groupCacher = GroupCacher(self.directory)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_expandedMembers(self):
-        &quot;&quot;&quot;
-        Verify expandedMembers() returns a &quot;flattened&quot; set of records
-        belonging to a group (and does not return sub-groups themselves,
-        only their members)
-        &quot;&quot;&quot;
-        record = yield self.xmlService.recordWithUID(u&quot;__top_group_1__&quot;)
-        memberUIDs = set()
-        for member in (yield expandedMembers(record)):
-            memberUIDs.add(member.uid)
-        self.assertEquals(
-            memberUIDs,
-            set([&quot;__cdaboo__&quot;, &quot;__glyph__&quot;, &quot;__sagen__&quot;, &quot;__wsanchez__&quot;])
-        )
-
-        # Non group records return an empty set() of members
-        record = yield self.xmlService.recordWithUID(u&quot;__sagen__&quot;)
-        members = yield expandedMembers(record)
-        self.assertEquals(0, len(list(members)))
-
-
-    @inlineCallbacks
</del><span class="cx">     def test_refreshGroup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Verify refreshGroup() adds a group to the Groups table with the
</span><span class="lines">@@ -68,37 +43,35 @@
</span><span class="cx">         store = self.storeUnderTest()
</span><span class="cx">         txn = store.newTransaction()
</span><span class="cx"> 
</span><del>-        record = yield self.xmlService.recordWithUID(u&quot;__top_group_1__&quot;)
-        yield self.groupCacher.refreshGroup(txn, record.guid)
</del><ins>+        record = yield self.directory.recordWithUID(u&quot;__top_group_1__&quot;)
+        yield self.groupCacher.refreshGroup(txn, record.uid)
</ins><span class="cx"> 
</span><del>-        groupID, name, membershipHash = (yield txn.groupByGUID(record.guid))
-        self.assertEquals(membershipHash, &quot;4b0e162f2937f0f3daa6d10e5a6a6c33&quot;)
</del><ins>+        groupID, name, membershipHash = (yield txn.groupByUID(record.uid))
</ins><span class="cx"> 
</span><del>-        groupGUID, name, membershipHash = (yield txn.groupByID(groupID))
-        self.assertEquals(groupGUID, record.guid)
-        self.assertEquals(name, &quot;Top Group 1&quot;)
-        self.assertEquals(membershipHash, &quot;4b0e162f2937f0f3daa6d10e5a6a6c33&quot;)
</del><ins>+        self.assertEquals(membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
</ins><span class="cx"> 
</span><ins>+        groupUID, name, membershipHash = (yield txn.groupByID(groupID))
+        self.assertEquals(groupUID, record.uid)
+        self.assertEquals(name, u&quot;Top Group 1&quot;)
+        self.assertEquals(membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
+
</ins><span class="cx">         members = (yield txn.membersOfGroup(groupID))
</span><span class="cx">         self.assertEquals(
</span><del>-            set([UUID(&quot;9064df911dbc4e079c2b6839b0953876&quot;),
-                 UUID(&quot;4ad155cbae9b475f986ce08a7537893e&quot;),
-                 UUID(&quot;3bdcb95484d54f6d8035eac19a6d6e1f&quot;),
-                 UUID(&quot;7d45cb10479e456bb54d528958c5734b&quot;)]),
</del><ins>+            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__']),
</ins><span class="cx">             members
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         records = (yield self.groupCacher.cachedMembers(txn, groupID))
</span><span class="cx">         self.assertEquals(
</span><del>-            set([r.shortNames[0] for r in records]),
-            set([&quot;wsanchez&quot;, &quot;cdaboo&quot;, &quot;glyph&quot;, &quot;sagen&quot;])
</del><ins>+            set([r.uid for r in records]),
+            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'])
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # sagen is in the top group, even though it's actually one level
</span><span class="cx">         # removed
</span><del>-        record = yield self.xmlService.recordWithUID(u&quot;__sagen__&quot;)
-        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.guid))
-        self.assertEquals(set([groupID]), groups)
</del><ins>+        record = yield self.directory.recordWithUID(u&quot;__sagen1__&quot;)
+        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
+        self.assertEquals(set([u&quot;__top_group_1__&quot;]), groups)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -113,20 +86,20 @@
</span><span class="cx">         txn = store.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         # Refresh the group so it's assigned a group_id
</span><del>-        guid = UUID(&quot;49b350c69611477b94d95516b13856ab&quot;)
-        yield self.groupCacher.refreshGroup(txn, guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(guid))
</del><ins>+        uid = u&quot;__top_group_1__&quot;
+        yield self.groupCacher.refreshGroup(txn, uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(uid))
</ins><span class="cx"> 
</span><span class="cx">         # Remove two members, and add one member
</span><span class="cx">         newSet = set()
</span><del>-        for name in (u&quot;wsanchez&quot;, u&quot;cdaboo&quot;, u&quot;dre&quot;):
</del><ins>+        for name in (u&quot;wsanchez1&quot;, u&quot;cdaboo1&quot;, u&quot;dre1&quot;):
</ins><span class="cx">             record = (
</span><del>-                yield self.xmlService.recordWithShortName(
</del><ins>+                yield self.directory.recordWithShortName(
</ins><span class="cx">                     RecordType.user,
</span><span class="cx">                     name
</span><span class="cx">                 )
</span><span class="cx">             )
</span><del>-            newSet.add(record.guid)
</del><ins>+            newSet.add(record.uid)
</ins><span class="cx">         numAdded, numRemoved = (
</span><span class="cx">             yield self.groupCacher.synchronizeMembers(
</span><span class="cx">                 txn, groupID, newSet
</span><span class="lines">@@ -137,7 +110,7 @@
</span><span class="cx">         records = (yield self.groupCacher.cachedMembers(txn, groupID))
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set([r.shortNames[0] for r in records]),
</span><del>-            set([&quot;wsanchez&quot;, &quot;cdaboo&quot;, &quot;dre&quot;])
</del><ins>+            set([&quot;wsanchez1&quot;, &quot;cdaboo1&quot;, &quot;dre1&quot;])
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Remove all members
</span><span class="lines">@@ -159,12 +132,12 @@
</span><span class="cx">         # Non-existent groupID
</span><span class="cx">         self.failUnlessFailure(txn.groupByID(42), NotFoundError)
</span><span class="cx"> 
</span><del>-        guid = UUID(&quot;49b350c69611477b94d95516b13856ab&quot;)
-        hash = &quot;4b0e162f2937f0f3daa6d10e5a6a6c33&quot;
-        yield self.groupCacher.refreshGroup(txn, guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(guid))
</del><ins>+        uid = u&quot;__top_group_1__&quot;
+        hash = &quot;553eb54e3bbb26582198ee04541dbee4&quot;
+        yield self.groupCacher.refreshGroup(txn, uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(uid))
</ins><span class="cx">         results = (yield txn.groupByID(groupID))
</span><del>-        self.assertEquals([guid, &quot;Top Group 1&quot;, hash], results)
</del><ins>+        self.assertEquals((uid, u&quot;Top Group 1&quot;, hash), results)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -177,32 +150,31 @@
</span><span class="cx">         self.assertEquals(oldExternalAssignments, {})
</span><span class="cx"> 
</span><span class="cx">         newAssignments = {
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;):
-            (None, UUID(&quot;49B350C6-9611-477B-94D9-5516B13856AB&quot;))
</del><ins>+            u&quot;__wsanchez1__&quot;: (None, u&quot;__top_group_1__&quot;)
</ins><span class="cx">         }
</span><span class="cx">         yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
</span><span class="cx">         oldExternalAssignments = (yield txn.externalDelegates())
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             oldExternalAssignments,
</span><span class="cx">             {
</span><del>-                UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;):
</del><ins>+                u&quot;__wsanchez1__&quot;:
</ins><span class="cx">                 (
</span><span class="cx">                     None,
</span><del>-                    UUID(&quot;49B350C6-9611-477B-94D9-5516B13856AB&quot;)
</del><ins>+                    u&quot;__top_group_1__&quot;
</ins><span class="cx">                 )
</span><span class="cx">             }
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         newAssignments = {
</span><del>-            UUID(&quot;7D45CB10-479E-456B-B54D-528958C5734B&quot;):
</del><ins>+            u&quot;__cdaboo1__&quot;:
</ins><span class="cx">             (
</span><del>-                UUID(&quot;86144F73-345A-4097-82F1-B782672087C7&quot;),
</del><ins>+                u&quot;__sub_group_1__&quot;,
</ins><span class="cx">                 None
</span><span class="cx">             ),
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;):
</del><ins>+            u&quot;__wsanchez1__&quot;:
</ins><span class="cx">             (
</span><del>-                UUID(&quot;86144F73-345A-4097-82F1-B782672087C7&quot;),
-                UUID(&quot;49B350C6-9611-477B-94D9-5516B13856AB&quot;)
</del><ins>+                u&quot;__sub_group_1__&quot;,
+                u&quot;__top_group_1__&quot;
</ins><span class="cx">             ),
</span><span class="cx">         }
</span><span class="cx">         yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
</span><span class="lines">@@ -210,14 +182,14 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             oldExternalAssignments,
</span><span class="cx">             {
</span><del>-                UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
</del><ins>+                u&quot;__wsanchez1__&quot;:
</ins><span class="cx">                 (
</span><del>-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
-                    UUID('49b350c6-9611-477b-94d9-5516b13856ab')
</del><ins>+                    u&quot;__sub_group_1__&quot;,
+                    u&quot;__top_group_1__&quot;
</ins><span class="cx">                 ),
</span><del>-                UUID('7d45cb10-479e-456b-b54d-528958c5734b'):
</del><ins>+                u&quot;__cdaboo1__&quot;:
</ins><span class="cx">                 (
</span><del>-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
</del><ins>+                    u&quot;__sub_group_1__&quot;,
</ins><span class="cx">                     None
</span><span class="cx">                 )
</span><span class="cx">             }
</span><span class="lines">@@ -228,44 +200,44 @@
</span><span class="cx">             allGroupDelegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('49b350c6-9611-477b-94d9-5516b13856ab'),
-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
</del><ins>+                    u&quot;__top_group_1__&quot;,
+                    u&quot;__sub_group_1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Fault in the read-only group
</span><del>-        yield self.groupCacher.refreshGroup(txn, UUID('86144f73-345a-4097-82f1-b782672087c7'))
</del><ins>+        yield self.groupCacher.refreshGroup(txn, u&quot;__sub_group_1__&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Wilfredo should have Sagen and Daboo as read-only delegates
</span><span class="cx">         delegates = (yield txn.delegates(
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;), False)
</del><ins>+            u&quot;__wsanchez1__&quot;, False, expanded=True)
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             delegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b')
</del><ins>+                    u&quot;__sagen1__&quot;,
+                    u&quot;__cdaboo1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Fault in the read-write group
</span><del>-        yield self.groupCacher.refreshGroup(txn, UUID('49b350c6-9611-477b-94d9-5516b13856ab'))
</del><ins>+        yield self.groupCacher.refreshGroup(txn, u&quot;__top_group_1__&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Wilfredo should have 4 users as read-write delegates
</span><span class="cx">         delegates = (yield txn.delegates(
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;), True)
</del><ins>+            u&quot;__wsanchez1__&quot;, True, expanded=True)
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             delegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'),
-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b'),
-                    UUID('9064df91-1dbc-4e07-9c2b-6839b0953876')
</del><ins>+                    u&quot;__wsanchez1__&quot;,
+                    u&quot;__sagen1__&quot;,
+                    u&quot;__cdaboo1__&quot;,
+                    u&quot;__glyph1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="lines">@@ -275,9 +247,9 @@
</span><span class="cx">         # Now, remove some external assignments
</span><span class="cx">         #
</span><span class="cx">         newAssignments = {
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;):
</del><ins>+            u&quot;__wsanchez1__&quot;:
</ins><span class="cx">             (
</span><del>-                UUID(&quot;86144F73-345A-4097-82F1-B782672087C7&quot;),
</del><ins>+                u&quot;__sub_group_1__&quot;,
</ins><span class="cx">                 None
</span><span class="cx">             ),
</span><span class="cx">         }
</span><span class="lines">@@ -286,9 +258,9 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             oldExternalAssignments,
</span><span class="cx">             {
</span><del>-                UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
</del><ins>+                u&quot;__wsanchez1__&quot;:
</ins><span class="cx">                 (
</span><del>-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
</del><ins>+                    u&quot;__sub_group_1__&quot;,
</ins><span class="cx">                     None
</span><span class="cx">                 ),
</span><span class="cx">             }
</span><span class="lines">@@ -299,28 +271,28 @@
</span><span class="cx">             allGroupDelegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
</del><ins>+                    u&quot;__sub_group_1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Wilfredo should have Sagen and Daboo as read-only delegates
</span><span class="cx">         delegates = (yield txn.delegates(
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;), False)
</del><ins>+            u&quot;__wsanchez1__&quot;, False, expanded=True)
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             delegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b')
</del><ins>+                    u&quot;__sagen1__&quot;,
+                    u&quot;__cdaboo1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Wilfredo should have no read-write delegates
</span><span class="cx">         delegates = (yield txn.delegates(
</span><del>-            UUID(&quot;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&quot;), True)
</del><ins>+            u&quot;__wsanchez1__&quot;, True, expanded=True)
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             delegates,
</span><span class="lines">@@ -333,7 +305,7 @@
</span><span class="cx">             allGroupDelegates,
</span><span class="cx">             set(
</span><span class="cx">                 [
</span><del>-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
</del><ins>+                    u&quot;__sub_group_1__&quot;
</ins><span class="cx">                 ]
</span><span class="cx">             )
</span><span class="cx">         )
</span><span class="lines">@@ -417,81 +389,3 @@
</span><span class="cx">                 {&quot;D&quot;: (&quot;7&quot;, &quot;8&quot;), &quot;C&quot;: (&quot;4&quot;, &quot;5&quot;), &quot;A&quot;: (&quot;1&quot;, &quot;2&quot;)},
</span><span class="cx">             )
</span><span class="cx">         )
</span><del>-
-testXMLConfig = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
-
-&lt;directory realm=&quot;xyzzy&quot;&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__wsanchez__&lt;/uid&gt;
-    &lt;guid&gt;3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F&lt;/guid&gt;
-    &lt;short-name&gt;wsanchez&lt;/short-name&gt;
-    &lt;short-name&gt;wilfredo_sanchez&lt;/short-name&gt;
-    &lt;full-name&gt;Wilfredo Sanchez&lt;/full-name&gt;
-    &lt;password&gt;zehcnasw&lt;/password&gt;
-    &lt;email&gt;wsanchez@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;wsanchez@devnull.twistedmatrix.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__glyph__&lt;/uid&gt;
-    &lt;guid&gt;9064DF91-1DBC-4E07-9C2B-6839B0953876&lt;/guid&gt;
-    &lt;short-name&gt;glyph&lt;/short-name&gt;
-    &lt;full-name&gt;Glyph Lefkowitz&lt;/full-name&gt;
-    &lt;password&gt;hpylg&lt;/password&gt;
-    &lt;email&gt;glyph@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;glyph@devnull.twistedmatrix.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__sagen__&lt;/uid&gt;
-    &lt;guid&gt;4AD155CB-AE9B-475F-986C-E08A7537893E&lt;/guid&gt;
-    &lt;short-name&gt;sagen&lt;/short-name&gt;
-    &lt;full-name&gt;Morgen Sagen&lt;/full-name&gt;
-    &lt;password&gt;negas&lt;/password&gt;
-    &lt;email&gt;sagen@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;shared@example.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__cdaboo__&lt;/uid&gt;
-    &lt;guid&gt;7D45CB10-479E-456B-B54D-528958C5734B&lt;/guid&gt;
-    &lt;short-name&gt;cdaboo&lt;/short-name&gt;
-    &lt;full-name&gt;Cyrus Daboo&lt;/full-name&gt;
-    &lt;password&gt;suryc&lt;/password&gt;
-    &lt;email&gt;cdaboo@bitbucket.calendarserver.org&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;user&quot;&gt;
-    &lt;uid&gt;__dre__&lt;/uid&gt;
-    &lt;guid&gt;CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B&lt;/guid&gt;
-    &lt;short-name&gt;dre&lt;/short-name&gt;
-    &lt;full-name&gt;Andre LaBranche&lt;/full-name&gt;
-    &lt;password&gt;erd&lt;/password&gt;
-    &lt;email&gt;dre@bitbucket.calendarserver.org&lt;/email&gt;
-    &lt;email&gt;shared@example.com&lt;/email&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;group&quot;&gt;
-    &lt;uid&gt;__top_group_1__&lt;/uid&gt;
-    &lt;guid&gt;49B350C6-9611-477B-94D9-5516B13856AB&lt;/guid&gt;
-    &lt;short-name&gt;top-group-1&lt;/short-name&gt;
-    &lt;full-name&gt;Top Group 1&lt;/full-name&gt;
-    &lt;email&gt;topgroup1@example.com&lt;/email&gt;
-    &lt;member-uid&gt;__wsanchez__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__glyph__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__sub_group_1__&lt;/member-uid&gt;
-  &lt;/record&gt;
-
-  &lt;record type=&quot;group&quot;&gt;
-    &lt;uid&gt;__sub_group_1__&lt;/uid&gt;
-    &lt;guid&gt;86144F73-345A-4097-82F1-B782672087C7&lt;/guid&gt;
-    &lt;short-name&gt;sub-group-1&lt;/short-name&gt;
-    &lt;full-name&gt;Sub Group 1&lt;/full-name&gt;
-    &lt;email&gt;subgroup1@example.com&lt;/email&gt;
-    &lt;member-uid&gt;__sagen__&lt;/member-uid&gt;
-    &lt;member-uid&gt;__cdaboo__&lt;/member-uid&gt;
-  &lt;/record&gt;
-
-&lt;/directory&gt;
-&quot;&quot;&quot;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_utilpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhotesttest_utilpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_util.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/test/test_util.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_util.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,164 @@
</span><ins>+##
+# Copyright (c) 2013 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;
+txdav.who.util tests
+&quot;&quot;&quot;
+
+import os
+
+from txdav.who.util import directoryFromConfig
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial.unittest import TestCase
+from twistedcaldav.config import ConfigDict
+from twisted.python.filepath import FilePath
+from txdav.who.augment import AugmentedDirectoryService
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
+from twext.who.xml import DirectoryService as XMLDirectoryService
+from txdav.who.delegates import (
+    DirectoryService as DelegateDirectoryService,
+    RecordType as DelegateRecordType
+)
+from twext.who.idirectory import RecordType
+from txdav.who.idirectory import RecordType as CalRecordType
+from txdav.who.wiki import (
+    DirectoryService as WikiDirectoryService,
+    RecordType as WikiRecordType,
+)
+
+
+class StubStore(object):
+    pass
+
+
+
+class UtilTest(TestCase):
+
+    def setUp(self):
+        sourceDir = FilePath(__file__).parent().child(&quot;accounts&quot;)
+        self.serverRoot = os.path.abspath(self.mktemp())
+        os.mkdir(self.serverRoot)
+        self.dataRoot = os.path.join(self.serverRoot, &quot;data&quot;)
+        if not os.path.exists(self.dataRoot):
+            os.makedirs(self.dataRoot)
+        destDir = FilePath(self.dataRoot)
+
+        accounts = destDir.child(&quot;accounts.xml&quot;)
+        sourceAccounts = sourceDir.child(&quot;accounts.xml&quot;)
+        accounts.setContent(sourceAccounts.getContent())
+
+        resources = destDir.child(&quot;resources.xml&quot;)
+        sourceResources = sourceDir.child(&quot;resources.xml&quot;)
+        resources.setContent(sourceResources.getContent())
+
+        augments = destDir.child(&quot;augments.xml&quot;)
+        sourceAugments = sourceDir.child(&quot;augments.xml&quot;)
+        augments.setContent(sourceAugments.getContent())
+
+
+    @inlineCallbacks
+    def test_directoryFromConfig(self):
+
+        config = ConfigDict(
+            {
+                &quot;DataRoot&quot;: self.dataRoot,
+                &quot;Authentication&quot;: {
+                    &quot;Wiki&quot;: {
+                        &quot;Enabled&quot;: True,
+                        &quot;CollabHost&quot;: &quot;localhost&quot;,
+                        &quot;CollabPort&quot;: 4444,
+                    },
+                },
+                &quot;DirectoryService&quot;: {
+                    &quot;Enabled&quot;: True,
+                    &quot;type&quot;: &quot;XML&quot;,
+                    &quot;params&quot;: {
+                        &quot;xmlFile&quot;: &quot;accounts.xml&quot;,
+                        &quot;recordTypes&quot;: [&quot;users&quot;, &quot;groups&quot;],
+                    },
+                },
+                &quot;ResourceService&quot;: {
+                    &quot;Enabled&quot;: True,
+                    &quot;type&quot;: &quot;XML&quot;,
+                    &quot;params&quot;: {
+                        &quot;xmlFile&quot;: &quot;resources.xml&quot;,
+                        &quot;recordTypes&quot;: [&quot;locations&quot;, &quot;resources&quot;, &quot;addresses&quot;],
+                    },
+                },
+                &quot;AugmentService&quot;: {
+                    &quot;Enabled&quot;: True,
+                    # FIXME: This still uses an actual class name:
+                    &quot;type&quot;: &quot;twistedcaldav.directory.augment.AugmentXMLDB&quot;,
+                    &quot;params&quot;: {
+                        &quot;xmlFiles&quot;: [&quot;augments.xml&quot;],
+                    },
+                },
+            }
+        )
+
+        store = StubStore()
+        service = directoryFromConfig(config, store=store)
+
+        # Inspect the directory service structure
+        self.assertTrue(isinstance(service, AugmentedDirectoryService))
+        self.assertTrue(isinstance(service._directory, AggregateDirectoryService))
+        self.assertEquals(len(service._directory.services), 4)
+        self.assertTrue(
+            isinstance(service._directory.services[0], XMLDirectoryService)
+        )
+        self.assertEquals(
+            set(service._directory.services[0].recordTypes()),
+            set([RecordType.user, RecordType.group])
+        )
+        self.assertTrue(
+            isinstance(service._directory.services[1], XMLDirectoryService)
+        )
+        self.assertEquals(
+            set(service._directory.services[1].recordTypes()),
+            set(
+                [
+                    CalRecordType.location,
+                    CalRecordType.resource,
+                    CalRecordType.address
+                ]
+            )
+        )
+        self.assertTrue(
+            isinstance(service._directory.services[2], DelegateDirectoryService)
+        )
+        self.assertEquals(
+            set(service._directory.services[2].recordTypes()),
+            set(
+                [
+                    DelegateRecordType.readDelegateGroup,
+                    DelegateRecordType.writeDelegateGroup,
+                    DelegateRecordType.readDelegatorGroup,
+                    DelegateRecordType.writeDelegatorGroup,
+                ]
+            )
+        )
+        self.assertTrue(
+            isinstance(service._directory.services[3], WikiDirectoryService)
+        )
+        self.assertEquals(
+            set(service._directory.services[3].recordTypes()),
+            set([WikiRecordType.macOSXServerWiki])
+        )
+
+
+        # And make sure it's functional:
+        record = yield service.recordWithUID(&quot;group07&quot;)
+        self.assertEquals(record.fullNames, [u'Group 07'])
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhotesttest_wikipyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhotesttest_wikipy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_wiki.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/test/test_wiki.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_wiki.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/test/test_wiki.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,116 @@
</span><ins>+##
+# Copyright (c) 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 __future__ import print_function
+from __future__ import absolute_import
+
+&quot;&quot;&quot;
+Tests for L{txdav.who.wiki}.
+&quot;&quot;&quot;
+
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks, succeed
+from twistedcaldav.test.util import StoreTestCase
+
+from ..wiki import DirectoryService, WikiAccessLevel
+import txdav.who.wiki
+
+
+class WikiIndividualServiceTestCase(unittest.TestCase):
+    &quot;&quot;&quot;
+    Instantiate a wiki service directly
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def test_service(self):
+        service = DirectoryService(&quot;realm&quot;, &quot;localhost&quot;, 4444)
+        record = yield service.recordWithUID(u&quot;[wiki]test&quot;)
+        self.assertEquals(
+            record.shortNames[0],
+            u&quot;test&quot;
+        )
+
+
+
+class WikiAggregateServiceTestCase(StoreTestCase):
+    &quot;&quot;&quot;
+    Get a wiki service as part of directoryFromConfig
+    &quot;&quot;&quot;
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Override configuration hook to turn on wiki service.
+        &quot;&quot;&quot;
+        from twistedcaldav.config import config
+
+        super(WikiAggregateServiceTestCase, self).configure()
+        self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
+
+
+    @inlineCallbacks
+    def test_service(self):
+        record = yield self.directory.recordWithUID(u&quot;[wiki]test&quot;)
+        self.assertEquals(
+            record.shortNames[0],
+            u&quot;test&quot;
+        )
+
+
+
+class AccessForRecordTestCase(StoreTestCase):
+    &quot;&quot;&quot;
+    Exercise accessForRecord
+    &quot;&quot;&quot;
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Override configuration hook to turn on wiki service.
+        &quot;&quot;&quot;
+        from twistedcaldav.config import config
+
+        super(AccessForRecordTestCase, self).configure()
+        self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
+        self.patch(
+            txdav.who.wiki,
+            &quot;accessForUserToWiki&quot;,
+            self.stubAccessForUserToWiki
+        )
+
+
+    def stubAccessForUserToWiki(self, *args, **kwds):
+        return succeed(self.access)
+
+
+    @inlineCallbacks
+    def test_accessForRecord(self):
+        record = yield self.directory.recordWithUID(u&quot;[wiki]test&quot;)
+
+        self.access = &quot;no-access&quot;
+        access = yield record.accessForRecord(None)
+        self.assertEquals(access, WikiAccessLevel.none)
+
+        self.access = &quot;read&quot;
+        access = yield record.accessForRecord(None)
+        self.assertEquals(access, WikiAccessLevel.read)
+
+        self.access = &quot;write&quot;
+        access = yield record.accessForRecord(None)
+        self.assertEquals(access, WikiAccessLevel.write)
+
+        self.access = &quot;admin&quot;
+        access = yield record.accessForRecord(None)
+        self.assertEquals(access, WikiAccessLevel.write)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhoutilpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhoutilpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/util.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/util.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,203 @@
</span><ins>+##
+# 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.
+##
+
+
+import os
+from twext.python.log import Logger
+from twisted.cred.credentials import UsernamePassword
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
+from txdav.who.augment import AugmentedDirectoryService
+
+from calendarserver.tap.util import getDBPool, storeFromConfig
+from twext.who.idirectory import (
+    RecordType, DirectoryConfigurationError
+)
+from twext.who.ldap import DirectoryService as LDAPDirectoryService
+from twext.who.util import ConstantsContainer
+from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedClass
+from twistedcaldav.config import fullServerPath
+from txdav.who.delegates import DirectoryService as DelegateDirectoryService
+from txdav.who.idirectory import (
+    RecordType as CalRecordType,
+    FieldName as CalFieldName
+)
+from txdav.who.xml import DirectoryService as XMLDirectoryService
+from txdav.who.wiki import DirectoryService as WikiDirectoryService
+
+
+log = Logger()
+
+
+def directoryFromConfig(config, store=None):
+    &quot;&quot;&quot;
+    Return a directory service based on the config.  If you want to go through
+    AMP to talk to one of these as a client, instantiate
+    txdav.dps.client.DirectoryService
+    &quot;&quot;&quot;
+
+    # MOVE2WHO FIXME: this needs to talk to its own separate database.  In
+    # fact, don't pass store=None if you already have called storeFromConfig()
+    # within this process.  Pass the existing store in here.
+
+    # TODO: use proxyForInterface to ensure we're only using the DPS related
+    # store API.  Also define an IDirectoryProxyStore Interface
+    if store is None:
+        pool, txnFactory = getDBPool(config)
+        store = storeFromConfig(config, txnFactory, None)
+
+    aggregatedServices = []
+
+
+    for serviceKey in (&quot;DirectoryService&quot;, &quot;ResourceService&quot;):
+        serviceValue = config.get(serviceKey, None)
+
+        if not serviceValue.Enabled:
+            continue
+
+        directoryType = serviceValue.type.lower()
+        params = serviceValue.params
+
+        # TODO: add a &quot;test&quot; directory service that produces test records
+        # from code -- no files needed.
+
+        if &quot;xml&quot; in directoryType:
+            xmlFile = params.xmlFile
+            xmlFile = fullServerPath(config.DataRoot, xmlFile)
+            if not xmlFile or not os.path.exists(xmlFile):
+                log.error(&quot;Path not found for XML directory: {p}&quot;, p=xmlFile)
+            fp = FilePath(xmlFile)
+            directory = XMLDirectoryService(fp)
+
+        elif &quot;opendirectory&quot; in directoryType:
+            from twext.who.opendirectory import (
+                DirectoryService as ODDirectoryService
+            )
+            directory = ODDirectoryService()
+
+        elif &quot;ldap&quot; in directoryType:
+            if params.credentials.dn and params.credentials.password:
+                creds = UsernamePassword(
+                    params.credentials.dn,
+                    params.credentials.password
+                )
+            else:
+                creds = None
+            directory = LDAPDirectoryService(
+                params.uri,
+                params.rdnSchema.base,
+                creds=creds
+            )
+
+        else:
+            log.error(&quot;Invalid DirectoryType: {dt}&quot;, dt=directoryType)
+            raise DirectoryConfigurationError
+
+        # Set the appropriate record types on each service
+        types = []
+        fieldNames = []
+        for recordTypeName in params.recordTypes:
+            recordType = {
+                &quot;users&quot;: RecordType.user,
+                &quot;groups&quot;: RecordType.group,
+                &quot;locations&quot;: CalRecordType.location,
+                &quot;resources&quot;: CalRecordType.resource,
+                &quot;addresses&quot;: CalRecordType.address,
+            }.get(recordTypeName, None)
+
+            if recordType is None:
+                log.error(&quot;Invalid Record Type: {rt}&quot;, rt=recordTypeName)
+                raise DirectoryConfigurationError
+
+            if recordType in types:
+                log.error(&quot;Duplicate Record Type: {rt}&quot;, rt=recordTypeName)
+                raise DirectoryConfigurationError
+
+            types.append(recordType)
+
+        directory.recordType = ConstantsContainer(types)
+        directory.fieldName = ConstantsContainer(
+            (directory.fieldName, CalFieldName)
+        )
+        fieldNames.append(directory.fieldName)
+        aggregatedServices.append(directory)
+
+    #
+    # Setup the Augment Service
+    #
+    if config.AugmentService.type:
+        augmentClass = namedClass(config.AugmentService.type)
+        log.info(
+            &quot;Configuring augment service of type: {augmentClass}&quot;,
+            augmentClass=augmentClass
+        )
+        try:
+            augmentService = augmentClass(**config.AugmentService.params)
+        except IOError:
+            log.error(&quot;Could not start augment service&quot;)
+            raise
+    else:
+        augmentService = None
+
+    userDirectory = None
+    for directory in aggregatedServices:
+        if RecordType.user in directory.recordTypes():
+            userDirectory = directory
+            break
+    else:
+        log.error(&quot;No directory service set up for users&quot;)
+        raise DirectoryConfigurationError
+
+    # Delegate service
+    delegateDirectory = DelegateDirectoryService(
+        userDirectory.realmName,
+        store
+    )
+    aggregatedServices.append(delegateDirectory)
+
+    # Wiki service
+    if config.Authentication.Wiki.Enabled:
+        aggregatedServices.append(
+            WikiDirectoryService(
+                userDirectory.realmName,
+                config.Authentication.Wiki.CollabHost,
+                config.Authentication.Wiki.CollabPort
+            )
+        )
+
+    # Aggregate service
+    aggregateDirectory = AggregateDirectoryService(
+        userDirectory.realmName, aggregatedServices
+    )
+
+    # Augment service
+    try:
+        fieldNames.append(CalFieldName)
+        augmented = AugmentedDirectoryService(
+            aggregateDirectory, store, augmentService
+        )
+        augmented.fieldName = ConstantsContainer(fieldNames)
+
+        # The delegate directory needs a way to look up user/group records
+        # so hand it a reference to the augmented directory.
+        # FIXME: is there a better pattern to use here?
+        delegateDirectory.setMasterDirectory(augmented)
+
+    except Exception as e:
+        log.error(&quot;Could not create directory service&quot;, error=e)
+        raise
+
+    return augmented
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhovcardpyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhovcardpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/vcard.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/vcard.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/vcard.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/vcard.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,330 @@
</span><ins>+##
+# 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;
+    Utilities to converting a Record to a vCard
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;vCardFromRecord&quot;
+]
+
+from pycalendar.vcard.adr import Adr
+from pycalendar.vcard.n import N
+from twext.python.log import Logger
+from twext.who.idirectory import FieldName, RecordType
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twistedcaldav.config import config
+from twistedcaldav.vcard import Component, Property, vCardProductID
+from txdav.who.idirectory import FieldName as CalFieldName, \
+    RecordType as CalRecordType
+from txweb2.dav.util import joinURL
+
+log = Logger()
+
+
+recordTypeToVCardKindMap = {
+   RecordType.user: &quot;individual&quot;,
+   RecordType.group: &quot;group&quot;,
+   CalRecordType.location: &quot;location&quot;,
+   CalRecordType.resource: &quot;device&quot;,
+}
+
+vCardKindToRecordTypeMap = {
+   &quot;individual&quot; : RecordType.user,
+   &quot;group&quot;: RecordType.group,
+   &quot;org&quot;: RecordType.group,
+   &quot;location&quot;: CalRecordType.location,
+   &quot;device&quot;: CalRecordType.resource,
+}
+
+
+# all possible generated parameters.
+vCardPropToParamMap = {
+    #&quot;PHOTO&quot;: {&quot;ENCODING&quot;: (&quot;B&quot;,), &quot;TYPE&quot;: (&quot;JPEG&quot;,), },
+    &quot;ADR&quot;: {&quot;TYPE&quot;: (&quot;WORK&quot;, &quot;PREF&quot;, &quot;POSTAL&quot;, &quot;PARCEL&quot;,), },
+    #&quot;LABEL&quot;: {&quot;TYPE&quot;: (&quot;POSTAL&quot;, &quot;PARCEL&quot;,)},
+    #&quot;TEL&quot;: {&quot;TYPE&quot;: None, },  # None means param can contain can be anything
+    &quot;EMAIL&quot;: {&quot;TYPE&quot;: None, },
+    #&quot;KEY&quot;: {&quot;ENCODING&quot;: (&quot;B&quot;,), &quot;TYPE&quot;: (&quot;PGPPUBILICKEY&quot;, &quot;USERCERTIFICATE&quot;, &quot;USERPKCS12DATA&quot;, &quot;USERSMIMECERTIFICATE&quot;,)},
+    #&quot;URL&quot;: {&quot;TYPE&quot;: (&quot;WEBLOG&quot;, &quot;HOMEPAGE&quot;,)},
+    #&quot;IMPP&quot;: {&quot;TYPE&quot;: (&quot;PREF&quot;,), &quot;X-SERVICE-TYPE&quot;: None, },
+    #&quot;X-ABRELATEDNAMES&quot;: {&quot;TYPE&quot;: None, },
+    #&quot;X-AIM&quot;: {&quot;TYPE&quot;: (&quot;PREF&quot;,), },
+    #&quot;X-JABBER&quot;: {&quot;TYPE&quot;: (&quot;PREF&quot;,), },
+    #&quot;X-MSN&quot;: {&quot;TYPE&quot;: (&quot;PREF&quot;,), },
+    #&quot;X-ICQ&quot;: {&quot;TYPE&quot;: (&quot;PREF&quot;,), },
+}
+
+
+vCardConstantProperties = {
+    #===================================================================
+    # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
+    #===================================================================
+    # 3.6.3 PRODID
+    &quot;PRODID&quot;: vCardProductID,
+    # 3.6.9 VERSION
+    &quot;VERSION&quot;: &quot;3.0&quot;,
+}
+
+
+@inlineCallbacks
+def vCardFromRecord(record, forceKind=None, addProps=None, parentURI=None):
+
+    def isUniqueProperty(newProperty, ignoredParameters={}):
+        existingProperties = vcard.properties(newProperty.name())
+        for existingProperty in existingProperties:
+            if ignoredParameters:
+                existingProperty = existingProperty.duplicate()
+                for paramName, paramValues in ignoredParameters.iteritems():
+                    for paramValue in paramValues:
+                        existingProperty.removeParameterValue(paramName, paramValue)
+            if existingProperty == newProperty:
+                return False
+        return True
+
+
+    def addUniqueProperty(newProperty, ignoredParameters=None):
+        if isUniqueProperty(newProperty, ignoredParameters):
+            vcard.addProperty(newProperty)
+        else:
+            log.info(
+                &quot;Ignoring property {prop!r} it is a duplicate&quot;,
+                prop=newProperty
+            )
+
+    #=======================================================================
+    # start
+    #=======================================================================
+
+    log.debug(&quot;vCardFromRecord: record={record}, forceKind={forceKind}, addProps={addProps}, parentURI={parentURI}&quot;,
+                   record=record, forceKind=forceKind, addProps=addProps, parentURI=parentURI)
+
+    if forceKind is None:
+        kind = recordTypeToVCardKindMap.get(record.recordType, &quot;individual&quot;)
+    else:
+        kind = forceKind
+
+    constantProperties = vCardConstantProperties.copy()
+    if addProps:
+        for key, value in addProps.iteritems():
+            if key not in constantProperties:
+                constantProperties[key] = value
+
+    # create vCard
+    vcard = Component(&quot;VCARD&quot;)
+
+    # add constant properties
+    for key, value in constantProperties.items():
+        vcard.addProperty(Property(key, value))
+
+    #===========================================================================
+    # 2.1 Predefined Type Usage
+    #===========================================================================
+    # 2.1.4 SOURCE Type http://tools.ietf.org/html/rfc2426#section-2.1.4
+    if parentURI:
+        uri = joinURL(parentURI, record.fields[FieldName.uid].encode(&quot;utf-8&quot;) + &quot;.vcf&quot;)
+
+        # seems like this should be in some standard place.
+        if config.EnableSSL and config.SSLPort:
+            if config.SSLPort == 443:
+                source = &quot;https://{server}{uri}&quot;.format(server=config.ServerHostName, uri=uri)
+            else:
+                source = &quot;https://{server}:{port}{uri}&quot;.format(server=config.ServerHostName, port=config.SSLPort, uri=uri)
+        else:
+            if config.HTTPPort == 80:
+                source = &quot;https://{server}{uri}&quot;.format(server=config.ServerHostName, uri=uri)
+            else:
+                source = &quot;https://{server}:{port}{uri}&quot;.format(server=config.ServerHostName, port=config.HTTPPort, uri=uri)
+        vcard.addProperty(Property(&quot;SOURCE&quot;, source))
+
+    #===================================================================
+    # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
+    #===================================================================
+    # 3.1.1 FN
+    vcard.addProperty(Property(&quot;FN&quot;, record.fields[FieldName.fullNames][0].encode(&quot;utf-8&quot;)))
+
+    # 3.1.2 N
+    # TODO: Better parsing
+    fullNameParts = record.fields[FieldName.fullNames][0].split()
+    first = fullNameParts[0] if len(fullNameParts) &gt;= 2 else None
+    last = fullNameParts[len(fullNameParts) - 1]
+    middle = fullNameParts[1] if len(fullNameParts) == 3 else None
+    prefix = None
+    suffix = None
+
+    nameObject = N(
+        first=first.encode(&quot;utf-8&quot;) if first else None,
+        last=last.encode(&quot;utf-8&quot;) if last else None,
+        middle=middle.encode(&quot;utf-8&quot;) if middle else None,
+        prefix=prefix.encode(&quot;utf-8&quot;) if prefix else None,
+        suffix=suffix.encode(&quot;utf-8&quot;) if suffix else None,
+    )
+    vcard.addProperty(Property(&quot;N&quot;, nameObject))
+
+    # 3.1.3 NICKNAME
+    nickname = record.fields.get(CalFieldName.abbreviatedName)
+    if nickname:
+        vcard.addProperty(Property(&quot;NICKNAME&quot;, nickname.encode(&quot;utf-8&quot;)))
+
+    # UNIMPLEMENTED
+    #     3.1.4 PHOTO
+    #     3.1.5 BDAY
+
+    #===========================================================================
+    # 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
+    #===========================================================================
+    # 3.2.1 ADR
+
+    extended = record.fields.get(CalFieldName.floor)
+
+    #TODO: parse !
+    street = record.fields.get(CalFieldName.streetAddress)
+    city = None
+    region = None
+    postalcode = None
+    country = None
+
+    if extended or street or city or region or postalcode or country:
+        vcard.addProperty(
+            Property(
+                &quot;ADR&quot;, Adr(
+                    #pobox = box,
+                    extended=extended.encode(&quot;utf-8&quot;) if extended else None,
+                    street=street.encode(&quot;utf-8&quot;) if street else None,
+                    locality=city.encode(&quot;utf-8&quot;) if city else None,
+                    region=region.encode(&quot;utf-8&quot;) if region else None,
+                    postalcode=postalcode.encode(&quot;utf-8&quot;) if postalcode else None,
+                    country=country.encode(&quot;utf-8&quot;) if country else None,
+                ),
+                params={&quot;TYPE&quot;: (&quot;WORK&quot;, &quot;PREF&quot;, &quot;POSTAL&quot;, &quot;PARCEL&quot;,), }
+            )
+        )
+
+    # UNIMPLEMENTED
+    #     3.2.2 LABEL
+
+    #===================================================================
+    # 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
+    #===================================================================
+    #
+    # UNIMPLEMENTED
+    #     3.3.1 TEL
+
+    # 3.3.2 EMAIL
+    preferredWorkParams = {&quot;TYPE&quot;: (&quot;WORK&quot;, &quot;PREF&quot;, &quot;INTERNET&quot;,), }
+    workParams = {&quot;TYPE&quot;: (&quot;WORK&quot;, &quot;INTERNET&quot;,), }
+    params = preferredWorkParams
+    for emailAddress in record.fields.get(FieldName.emailAddresses, []):
+        addUniqueProperty(Property(&quot;EMAIL&quot;, emailAddress.encode(&quot;utf-8&quot;), params=params), ignoredParameters={&quot;TYPE&quot;: (&quot;PREF&quot;,)})
+        params = workParams
+
+    # UNIMPLEMENTED:
+    #     3.3.3 MAILER
+    #
+    #===================================================================
+    # 3.4 GEOGRAPHICAL TYPES http://tools.ietf.org/html/rfc2426#section-3.4
+    #===================================================================
+    #
+    # UNIMPLEMENTED:
+    #     3.4.1 TZ
+    #
+    # 3.4.2 GEO
+    geographicLocation = record.fields.get(CalFieldName.geographicLocation)
+    if geographicLocation:
+        vcard.addProperty(Property(&quot;GEO&quot;, geographicLocation.encode(&quot;utf-8&quot;)))
+
+    #===================================================================
+    # 3.5 ORGANIZATIONAL TYPES http://tools.ietf.org/html/rfc2426#section-3.5
+    #===================================================================
+    #
+    # UNIMPLEMENTED:
+    #     3.5.1 TITLE
+    #     3.5.2 ROLE
+    #     3.5.3 LOGO
+    #     3.5.4 AGENT
+    #     3.5.5 ORG
+    #
+    #===================================================================
+    # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
+    #===================================================================
+    #
+    # UNIMPLEMENTED:
+    #     3.6.1 CATEGORIES
+    #     3.6.2 NOTE
+    #
+    # ADDED WITH CONTSTANT PROPERTIES:
+    #     3.6.3 PRODID
+    #
+    # UNIMPLEMENTED:
+    #     3.6.5 SORT-STRING
+    #     3.6.6 SOUND
+
+    # 3.6.7 UID
+    vcard.addProperty(Property(&quot;UID&quot;, record.fields[FieldName.uid].encode(&quot;utf-8&quot;)))
+
+    # UNIMPLEMENTED:
+    #     3.6.8 URL
+
+    # ADDED WITH CONTSTANT PROPERTIES:
+    #     3.6.9 VERSION
+
+    #===================================================================
+    # 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
+    #===================================================================
+    # UNIMPLEMENTED:
+    #     3.7.1 CLASS
+    #     3.7.2 KEY
+
+    #===================================================================
+    # X Properties
+    #===================================================================
+    # UNIMPLEMENTED:
+    #    X-&lt;instant messaging type&gt; such as:
+    #        &quot;AIM&quot;, &quot;FACEBOOK&quot;, &quot;GAGU-GAGU&quot;, &quot;GOOGLE TALK&quot;, &quot;ICQ&quot;, &quot;JABBER&quot;, &quot;MSN&quot;, &quot;QQ&quot;, &quot;SKYPE&quot;, &quot;YAHOO&quot;,
+    #    X-MAIDENNAME
+    #    X-PHONETIC-FIRST-NAME
+    #    X-PHONETIC-MIDDLE-NAME
+    #    X-PHONETIC-LAST-NAME
+    #    X-ABRELATEDNAMES
+
+    # X-ADDRESSBOOKSERVER-KIND
+    if kind == &quot;group&quot;:
+        vcard.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-KIND&quot;, kind))
+
+    # add members
+    # FIXME:  members() is a deferred, so all of vCardFromRecord is deferred.
+    for memberRecord in (yield record.members()):
+        vcard.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;, &quot;urn:uuid:&quot; + memberRecord.fields[FieldName.uid].encode(&quot;utf-8&quot;)))
+
+    #===================================================================
+    # vCard 4.0  http://tools.ietf.org/html/rfc6350
+    #===================================================================
+    # UNIMPLEMENTED:
+    #     6.4.3 IMPP http://tools.ietf.org/html/rfc6350#section-6.4.3
+    #
+    # 6.1.4 KIND http://tools.ietf.org/html/rfc6350#section-6.1.4
+    #
+    # see also: http://www.iana.org/assignments/vcard-elements/vcard-elements.xml
+    #
+    vcard.addProperty(Property(&quot;KIND&quot;, kind))
+
+    # one more X- related to kind
+    if kind == &quot;org&quot;:
+        vcard.addProperty(Property(&quot;X-ABShowAs&quot;, &quot;COMPANY&quot;))
+
+    log.debug(&quot;vCardFromRecord: vcard=\n{vcard}&quot;, vcard=vcard)
+    returnValue(vcard)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhowikipyfromrev13157CalendarServerbranchesuserssagenmove2who4txdavwhowikipy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/sagen/move2who-5/txdav/who/wiki.py (from rev 13157, CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py) (0 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/wiki.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/wiki.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -0,0 +1,426 @@
</span><ins>+##
+# 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;
+Mac OS X Server Wiki directory service.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;WikiAccessLevel&quot;,
+    &quot;DirectoryService&quot;,
+]
+
+import json
+from twext.internet.adaptendpoint import connect
+from twext.internet.gaiendpoint import GAIEndpoint
+from twext.internet.gaiendpoint import MultiFailure
+from twext.python.log import Logger
+from twext.who.directory import (
+    DirectoryService as BaseDirectoryService,
+    DirectoryRecord as BaseDirectoryRecord
+)
+from twext.who.idirectory import FieldName as BaseFieldName
+from twext.who.util import ConstantsContainer
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.python.constants import Names, NamedConstant
+from twisted.web.client import HTTPPageGetter, HTTPClientFactory
+from twisted.web.error import Error as WebError
+from txdav.who.directory import CalendarDirectoryRecordMixin
+from txdav.who.idirectory import FieldName
+from txdav.xml import element as davxml
+from txweb2 import responsecode
+from txweb2.auth.wrapper import UnauthorizedResponse
+from txweb2.dav.resource import TwistedACLInheritable
+from txweb2.http import HTTPError, StatusResponse
+
+
+log = Logger()
+
+
+# FIXME: Should this be Flags?
+class WikiAccessLevel(Names):
+    none  = NamedConstant()
+    read  = NamedConstant()
+    write = NamedConstant()
+
+
+
+class RecordType(Names):
+    macOSXServerWiki = NamedConstant()
+    macOSXServerWiki.description = u&quot;Mac OS X Server Wiki&quot;
+
+
+
+class DirectoryService(BaseDirectoryService):
+    &quot;&quot;&quot;
+    Mac OS X Server Wiki directory service.
+    &quot;&quot;&quot;
+
+    uidPrefix = u&quot;[wiki]&quot;
+
+    recordType = RecordType
+
+    fieldName = ConstantsContainer((
+        BaseFieldName,
+        FieldName,
+    ))
+
+
+    def __init__(self, realmName, wikiHost, wikiPort):
+        BaseDirectoryService.__init__(self, realmName)
+        self.wikiHost = wikiHost
+        self.wikiPort = wikiPort
+        self._recordsByName = {}
+
+
+    # This directory service is rather limited in its skills.
+    # We don't attempt to implement any expression handling (ie.
+    # recordsFromNonCompoundExpression), and only support a couple of the
+    # recordWith* convenience methods.
+
+    def _recordWithName(self, name):
+        record = self._recordsByName.get(name)
+
+        if record is not None:
+            return succeed(record)
+
+        # FIXME: RPC to the wiki and check for existance of a wiki with the
+        # given name...
+        #
+        # NOTE: Don't use the config module here; pass whatever info we need to
+        # __init__().
+        wikiExists = True
+
+        if wikiExists:
+            record = DirectoryRecord(
+                self,
+                {
+                    self.fieldName.uid: u&quot;{}{}&quot;.format(self.uidPrefix, name),
+                    self.fieldName.recordType: RecordType.macOSXServerWiki,
+                    self.fieldName.shortNames: [name],
+                    self.fieldName.fullNames: [u&quot;Wiki: {}&quot;.format(name)],
+                }
+            )
+            self._recordsByName[name] = record
+            return succeed(record)
+
+        return succeed(None)
+
+
+    def recordWithUID(self, uid):
+        if uid.startswith(self.uidPrefix):
+            return self._recordWithName(uid[len(self.uidPrefix):])
+        return succeed(None)
+
+
+    def recordWithShortName(self, recordType, shortName):
+        if recordType is RecordType.macOSXServerWiki:
+            return self._recordWithName(shortName)
+        return succeed(None)
+
+
+    def recordsFromExpression(self, expression, records=None):
+        return succeed(())
+
+
+
+class DirectoryRecord(BaseDirectoryRecord, CalendarDirectoryRecordMixin):
+    &quot;&quot;&quot;
+    Mac OS X Server Wiki directory record.
+    &quot;&quot;&quot;
+
+    log = Logger()
+
+
+    @property
+    def name(self):
+        return self.shortNames[0]
+
+
+    @inlineCallbacks
+    def accessForRecord(self, record):
+        &quot;&quot;&quot;
+        Look up the access level for a record in this wiki.
+
+        @param user: The record to check access for.  A value of None means
+            unauthenticated
+        &quot;&quot;&quot;
+        if record is None:
+            uid = u&quot;unauthenticated&quot;
+        else:
+            uid = record.uid
+
+        try:
+            # FIXME: accessForUserToWiki() API is lame.
+            # There are no other callers except the old directory API, so
+            # nuke it from the originating module and move that logic here
+            # once the old API is removed.
+            # When we do that note: isn't there a getPage() in twisted.web?
+
+            access = yield accessForUserToWiki(
+                uid, self.shortNames[0],
+                host=self.service.wikiHost,
+                port=self.service.wikiPort,
+            )
+
+        except MultiFailure as e:
+            self.log.error(
+                &quot;Unable to look up access for record {record} &quot;
+                &quot;in wiki {log_source}: {error}&quot;,
+                record=record, error=e
+            )
+
+        except WebError as e:
+            status = int(e.status)
+
+            if status == responsecode.FORBIDDEN:  # Unknown user
+                self.log.debug(
+                    &quot;No such record (according to wiki): {record}&quot;,
+                    record=record, error=e
+                )
+                returnValue(WikiAccessLevel.none)
+
+            if status == responsecode.NOT_FOUND:  # Unknown wiki
+                self.log.error(
+                    &quot;No such wiki: {log_source.name}&quot;,
+                    record=record, error=e
+                )
+                returnValue(WikiAccessLevel.none)
+
+            self.log.error(
+                &quot;Unable to look up wiki access: {error}&quot;,
+                record=record, error=e
+            )
+
+        try:
+            returnValue({
+                &quot;no-access&quot;: WikiAccessLevel.none,
+                &quot;read&quot;: WikiAccessLevel.read,
+                &quot;write&quot;: WikiAccessLevel.write,
+                &quot;admin&quot;: WikiAccessLevel.write,
+            }[access])
+
+        except KeyError:
+            self.log.error(&quot;Unknown wiki access level: {level}&quot;, level=access)
+            returnValue(WikiAccessLevel.none)
+
+
+@inlineCallbacks
+def getWikiACL(resource, request):
+    &quot;&quot;&quot;
+    Ask the wiki server we're paired with what level of access the authnUser
+    has.
+
+    Returns an ACL.
+
+    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
+
+    if (
+        not hasattr(resource, &quot;record&quot;) or
+        resource.record.recordType != RecordType.macOSXServerWiki
+    ):
+        returnValue(None)
+
+    if hasattr(request, 'wikiACL'):
+        returnValue(request.wikiACL)
+
+    wikiRecord = resource.record
+    wikiID = wikiRecord.shortNames[0]
+    userRecord = None
+
+    try:
+        url = str(request.authnUser.children[0])
+        principal = (yield request.locateResource(url))
+        if isinstance(principal, DirectoryPrincipalResource):
+            userRecord = principal.record
+    except:
+        # TODO: better error handling
+        pass
+
+    try:
+        access = yield wikiRecord.accessForRecord(userRecord)
+
+        # The ACL we returns has ACEs for the end-user and the wiki principal
+        # in case authzUser is the wiki principal.
+        if access == WikiAccessLevel.read:
+            request.wikiACL = davxml.ACL(
+                davxml.ACE(
+                    request.authnUser,
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+
+                        # 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/{}/&quot;.format(wikiID)
+                        )
+                    ),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    ),
+                    TwistedACLInheritable(),
+                )
+            )
+            returnValue(request.wikiACL)
+
+        elif access == WikiAccessLevel.write:
+            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/{}/&quot;.format(wikiID)
+                        )
+                    ),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                        davxml.Privilege(davxml.Write()),
+                    ),
+                    TwistedACLInheritable(),
+                )
+            )
+            returnValue(request.wikiACL)
+
+        else:  # &quot;no-access&quot;:
+
+            if userRecord is None:
+                # Return a 401 so they have an opportunity to log in
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr,
+                ))
+                raise HTTPError(response)
+
+            raise HTTPError(
+                StatusResponse(
+                    responsecode.FORBIDDEN,
+                    &quot;You are not allowed to access this wiki&quot;
+                )
+            )
+
+    except HTTPError:
+        # pass through the HTTPError we might have raised above
+        raise
+
+    except Exception as e:
+        log.error(&quot;Wiki ACL lookup failed: {error}&quot;, error=e)
+        raise HTTPError(StatusResponse(
+            responsecode.SERVICE_UNAVAILABLE, &quot;Wiki ACL lookup failed&quot;
+        ))
+
+
+
+class WebAuthError(RuntimeError):
+    &quot;&quot;&quot;
+    Error in web auth
+    &quot;&quot;&quot;
+
+
+
+@inlineCallbacks
+def uidForAuthToken(token, host=&quot;localhost&quot;, port=80):
+    &quot;&quot;&quot;
+    Send a GET request to the web auth service to retrieve the user record
+    uid associated with the provided auth token.
+
+    @param token: An auth token, usually passed in via cookie when webcal
+        makes a request.
+    @type token: C{str}
+    @return: deferred returning a uid (C{str}) if successful, or
+        will raise WebAuthError otherwise.
+    &quot;&quot;&quot;
+    url = &quot;http://%s:%d/auth/verify?auth_token=%s&quot; % (host, port, token,)
+    jsonResponse = (yield _getPage(url, host, port))
+    try:
+        response = json.loads(jsonResponse)
+    except Exception, e:
+        log.error(
+            &quot;Error parsing JSON response from webauth: {resp} {error}&quot;,
+            resp=jsonResponse, error=str(e)
+        )
+        raise WebAuthError(&quot;Could not look up token: %s&quot; % (token,))
+    if response[&quot;succeeded&quot;]:
+        returnValue(response[&quot;generated_uid&quot;])
+    else:
+        raise WebAuthError(&quot;Could not look up token: %s&quot; % (token,))
+
+
+
+def accessForUserToWiki(user, wiki, host=&quot;localhost&quot;, port=4444):
+    &quot;&quot;&quot;
+    Send a GET request to the wiki collabd service to retrieve the access level
+    the given user (uid) has to the given wiki (in wiki short-name
+    form).
+
+    @param user: The UID of the user
+    @type user: C{str}
+    @param wiki: The short name of the wiki
+    @type wiki: C{str}
+    @return: deferred returning a access level (C{str}) if successful, or
+        if the user is not recognized a twisted.web.error.Error with
+        status FORBIDDEN will errBack; an unknown wiki will have a status
+        of NOT_FOUND
+    &quot;&quot;&quot;
+    url = &quot;http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s&quot; % (
+        host, port, user, wiki
+    )
+    return _getPage(url, host, port)
+
+
+
+# FIXME: Why don't we use twisted.web.
+def _getPage(url, host, port):
+    &quot;&quot;&quot;
+    Fetch the body of the given url via HTTP, connecting to the given host
+    and port.
+
+    @param url: The URL to GET
+    @type url: C{str}
+    @param host: The hostname to connect to
+    @type host: C{str}
+    @param port: The port number to connect to
+    @type port: C{int}
+    @return: A deferred; upon 200 success the body of the response is returned,
+        otherwise a twisted.web.error.Error is the result.
+    &quot;&quot;&quot;
+    factory = HTTPClientFactory(url)
+    factory.protocol = HTTPPageGetter
+    connect(GAIEndpoint(reactor, host, port), factory)
+    return factory.deferred
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txdavwhoxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txdav/who/xml.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txdav/who/xml.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txdav/who/xml.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -105,8 +105,30 @@
</span><span class="cx">         AutoScheduleMode.acceptIfFreeDeclineIfBusy
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    # For &quot;locations&quot;, i.e., scheduled spaces:
</ins><span class="cx"> 
</span><ins>+    capacity = ValueConstant(u&quot;capacity&quot;)
+    capacity.fieldName = FieldName.capacity
</ins><span class="cx"> 
</span><ins>+    floor = ValueConstant(u&quot;floor&quot;)
+    floor.fieldName = FieldName.floor
+
+    associatedAddress = ValueConstant(u&quot;associated-address&quot;)
+    associatedAddress.fieldName = FieldName.associatedAddress
+
+    # For &quot;addresses&quot;, i.e., non-scheduled areas containing locations:
+
+    abbreviatedName = ValueConstant(u&quot;abbreviated-name&quot;)
+    abbreviatedName.fieldName = FieldName.abbreviatedName
+
+    streetAddress = ValueConstant(u&quot;street-address&quot;)
+    streetAddress.fieldName = FieldName.streetAddress
+
+    geographicLocation = ValueConstant(u&quot;geographic-location&quot;)
+    geographicLocation.fieldName = FieldName.geographicLocation
+
+
+
</ins><span class="cx"> class Attribute(Values):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     XML calendar and contacts attribute names.
</span><span class="lines">@@ -143,10 +165,14 @@
</span><span class="cx">         (BaseDirectoryService.recordType, RecordType)
</span><span class="cx">     )
</span><span class="cx"> 
</span><del>-    fieldName = ConstantsContainer(
-        (BaseDirectoryService.fieldName, FieldName)
-    )
</del><ins>+    # MOVE2WHO: Wilfredo had added augment fields into xml, which does make
+    # some sense, but for backwards compatibility right now I will take those
+    # out, and rely on a separate augment service
</ins><span class="cx"> 
</span><ins>+    # fieldName = ConstantsContainer(
+    #     (BaseDirectoryService.fieldName, FieldName)
+    # )
+
</ins><span class="cx">     # XML schema constants
</span><span class="cx"> 
</span><span class="cx">     element = ConstantsContainer(
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txweb2channelhttppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txweb2/channel/http.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txweb2/channel/http.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txweb2/channel/http.py        2014-04-04 17:20:27 UTC (rev 13158)
</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="CalendarServerbranchesuserssagenmove2who5txweb2davmethodreport_expandpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/method/report_expand.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/method/report_expand.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/method/report_expand.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -8,10 +8,10 @@
</span><span class="cx"> # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
</span><span class="cx"> # copies of the Software, and to permit persons to whom the Software is
</span><span class="cx"> # furnished to do so, subject to the following conditions:
</span><del>-# 
</del><ins>+#
</ins><span class="cx"> # The above copyright notice and this permission notice shall be included in all
</span><span class="cx"> # copies or substantial portions of the Software.
</span><del>-# 
</del><ins>+#
</ins><span class="cx"> # THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
</span><span class="cx"> # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
</span><span class="cx"> # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
</span><span class="lines">@@ -48,7 +48,7 @@
</span><span class="cx"> def report_DAV__expand_property(self, request, expand_property):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Generate an expand-property REPORT. (RFC 3253, section 3.8)
</span><del>-    
</del><ins>+
</ins><span class="cx">     TODO: for simplicity we will only support one level of expansion.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     # Verify root element
</span><span class="lines">@@ -61,7 +61,7 @@
</span><span class="cx">     if depth != &quot;0&quot;:
</span><span class="cx">         log.error(&quot;Non-zero depth is not allowed: %s&quot; % (depth,))
</span><span class="cx">         raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Depth %s not allowed&quot; % (depth,)))
</span><del>-    
</del><ins>+
</ins><span class="cx">     #
</span><span class="cx">     # Get top level properties to expand and make sure we only have one level
</span><span class="cx">     #
</span><span class="lines">@@ -70,7 +70,7 @@
</span><span class="cx">     for property in expand_property.children:
</span><span class="cx">         namespace = property.attributes.get(&quot;namespace&quot;, dav_namespace)
</span><span class="cx">         name      = property.attributes.get(&quot;name&quot;, &quot;&quot;)
</span><del>-        
</del><ins>+
</ins><span class="cx">         # Make sure children have no children
</span><span class="cx">         props_to_find = []
</span><span class="cx">         for child in property.children:
</span><span class="lines">@@ -93,14 +93,14 @@
</span><span class="cx">         responsecode.OK        : [],
</span><span class="cx">         responsecode.NOT_FOUND : [],
</span><span class="cx">     }
</span><del>-    
</del><ins>+
</ins><span class="cx">     filteredaces = None
</span><span class="cx">     lastParent = None
</span><span class="cx"> 
</span><span class="cx">     for qname in properties.iterkeys():
</span><span class="cx">         try:
</span><span class="cx">             prop = (yield self.readProperty(qname, request))
</span><del>-            
</del><ins>+
</ins><span class="cx">             # Form the PROPFIND-style DAV:prop element we need later
</span><span class="cx">             props_to_return = element.PropertyContainer(*properties[qname])
</span><span class="cx"> 
</span><span class="lines">@@ -108,27 +108,27 @@
</span><span class="cx">             responses = []
</span><span class="cx">             for href in prop.children:
</span><span class="cx">                 if isinstance(href, element.HRef):
</span><del>-                    
</del><ins>+
</ins><span class="cx">                     # Locate the Href resource and its parent
</span><span class="cx">                     resource_uri = str(href)
</span><span class="cx">                     child = (yield request.locateResource(resource_uri))
</span><del>-    
</del><ins>+
</ins><span class="cx">                     if not child or not child.exists():
</span><span class="cx">                         responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.NOT_FOUND)))
</span><span class="cx">                         continue
</span><span class="cx">                     parent = (yield request.locateResource(parentForURL(resource_uri)))
</span><del>-    
</del><ins>+
</ins><span class="cx">                     # Check privileges on parent - must have at least DAV:read
</span><span class="cx">                     try:
</span><span class="cx">                         yield parent.checkPrivileges(request, (element.Read(),))
</span><span class="cx">                     except AccessDeniedError:
</span><span class="cx">                         responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                         continue
</span><del>-                    
</del><ins>+
</ins><span class="cx">                     # Cache the last parent's inherited aces for checkPrivileges optimization
</span><span class="cx">                     if lastParent != parent:
</span><span class="cx">                         lastParent = parent
</span><del>-                
</del><ins>+
</ins><span class="cx">                         # Do some optimisation of access control calculation by determining any inherited ACLs outside of
</span><span class="cx">                         # the child resource loop and supply those to the checkPrivileges on each child.
</span><span class="cx">                         filteredaces = (yield parent.inheritedACEsforChildren(request))
</span><span class="lines">@@ -139,7 +139,7 @@
</span><span class="cx">                     except AccessDeniedError:
</span><span class="cx">                         responses.append(element.StatusResponse(href, element.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                         continue
</span><del>-            
</del><ins>+
</ins><span class="cx">                     # Now retrieve all the requested properties on the HRef resource
</span><span class="cx">                     yield prop_common.responseForHref(
</span><span class="cx">                         request,
</span><span class="lines">@@ -149,13 +149,16 @@
</span><span class="cx">                         prop_common.propertyListForResource,
</span><span class="cx">                         props_to_return,
</span><span class="cx">                     )
</span><del>-            
</del><ins>+
</ins><span class="cx">             prop.children = responses
</span><span class="cx">             properties_by_status[responsecode.OK].append(prop)
</span><span class="cx">         except:
</span><span class="cx">             f = Failure()
</span><span class="cx"> 
</span><del>-            log.error(&quot;Error reading property %r for resource %s: %s&quot; % (qname, request.uri, f.value))
</del><ins>+            log.error(
+                &quot;Error reading property {qname} for resource {req}: {failure}&quot;,
+                qname=qname, req=request.uri, failure=f.value
+            )
</ins><span class="cx"> 
</span><span class="cx">             status = statusForFailure(f, &quot;getting property: %s&quot; % (qname,))
</span><span class="cx">             if status not in properties_by_status: properties_by_status[status] = []
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txweb2davresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/resource.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/resource.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/resource.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -997,6 +997,7 @@
</span><span class="cx">             if the authentication scheme is unsupported, or the
</span><span class="cx">             credentials provided by the request are not valid.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">         # Bypass normal authentication if its already been done (by SACL check)
</span><span class="cx">         if (
</span><span class="cx">             hasattr(request, &quot;authnUser&quot;) and
</span><span class="lines">@@ -1134,7 +1135,7 @@
</span><span class="cx">         # The default behaviour is no ACL; we should inherit from the parent
</span><span class="cx">         # collection.
</span><span class="cx">         #
</span><del>-        return element.ACL()
</del><ins>+        return succeed(element.ACL())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def setAccessControlList(self, acl):
</span><span class="lines">@@ -1360,6 +1361,7 @@
</span><span class="cx">         @return: a L{Deferred} that callbacks with C{None} or errbacks
</span><span class="cx">             with an L{AccessDeniedError}
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">         if principal is None:
</span><span class="cx">             principal = self.currentPrincipal(request)
</span><span class="cx"> 
</span><span class="lines">@@ -1509,7 +1511,7 @@
</span><span class="cx">                 # If we get to the root without any ACLs, then use the default.
</span><span class="cx">                 acl = self.defaultRootAccessControlList()
</span><span class="cx">             else:
</span><del>-                acl = self.defaultAccessControlList()
</del><ins>+                acl = yield self.defaultAccessControlList()
</ins><span class="cx"> 
</span><span class="cx">         # Dynamically update privileges for those ace's that are inherited.
</span><span class="cx">         if inheritance:
</span><span class="lines">@@ -1618,6 +1620,7 @@
</span><span class="cx">         return []
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def principalsForAuthID(self, request, authid):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return authentication and authorization principal identifiers
</span><span class="lines">@@ -1637,16 +1640,16 @@
</span><span class="cx">             HTTPError(responsecode.FORBIDDEN) if the principal isn't
</span><span class="cx">             found.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        authnPrincipal = self.findPrincipalForAuthID(authid)
</del><ins>+        authnPrincipal = yield self.findPrincipalForAuthID(authid)
</ins><span class="cx"> 
</span><span class="cx">         if authnPrincipal is None:
</span><del>-            return succeed((None, None))
</del><ins>+            returnValue((None, None))
</ins><span class="cx"> 
</span><del>-        d = self.authorizationPrincipal(request, authid, authnPrincipal)
-        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
-        return d
</del><ins>+        authzPrincipal = yield self.authorizationPrincipal(request, authid, authnPrincipal)
+        returnValue((authnPrincipal, authzPrincipal))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def findPrincipalForAuthID(self, authid):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return authentication and authorization principal identifiers
</span><span class="lines">@@ -1662,10 +1665,10 @@
</span><span class="cx">             found return None.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for collection in self.principalCollections():
</span><del>-            principal = collection.principalForUser(authid)
</del><ins>+            principal = yield collection.principalForUser(authid)
</ins><span class="cx">             if principal is not None:
</span><del>-                return principal
-        return None
</del><ins>+                returnValue(principal)
+        returnValue(None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def authorizationPrincipal(self, request, authid, authnPrincipal):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txweb2davutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/util.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/util.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txweb2/dav/util.py        2014-04-04 17:20:27 UTC (rev 13158)
</span><span class="lines">@@ -41,9 +41,8 @@
</span><span class="cx">     &quot;bindMethods&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-import urllib
</del><span class="cx"> from urlparse import urlsplit, urlunsplit
</span><del>-import posixpath # Careful; this module is not documented as public API
</del><ins>+import posixpath  # Careful; this module is not documented as public API
</ins><span class="cx"> 
</span><span class="cx"> from twisted.python.failure import Failure
</span><span class="cx"> from twisted.internet.defer import succeed
</span><span class="lines">@@ -125,13 +124,13 @@
</span><span class="cx">                 count += 1
</span><span class="cx">             path = path[count - 1:]
</span><span class="cx"> 
</span><del>-        return path
</del><ins>+        return path.encode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><span class="cx">     (scheme, host, path, query, fragment) = urlsplit(cleanup(url))
</span><span class="cx"> 
</span><del>-    path = cleanup(posixpath.normpath(urllib.unquote(path)))
</del><ins>+    path = cleanup(posixpath.normpath(path))
</ins><span class="cx"> 
</span><del>-    return urlunsplit((scheme, host, urllib.quote(path), query, fragment))
</del><ins>+    return urlunsplit((scheme, host, path, query, fragment))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who5txweb2serverpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-5/txweb2/server.py (13157 => 13158)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-5/txweb2/server.py        2014-04-04 17:05:48 UTC (rev 13157)
+++ CalendarServer/branches/users/sagen/move2who-5/txweb2/server.py        2014-04-04 17:20:27 UTC (rev 13158)
</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>