<!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, "authzUser") 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("/")
- uid = uid[uid.rfind("/") + 1:]
- record = request.site.resource.getDirectory().recordWithUID(uid)
- if record:
- if record.recordType == DirectoryService.recordType_users:
- return record.shortNames[0]
- else:
- return "(%s)%s" % (record.recordType, record.shortNames[0],)
- else:
- return uid
</del><ins>+ # def convertUIDtoShortName(uid):
+ # uid = uid.rstrip("/")
+ # uid = uid[uid.rfind("/") + 1:]
+ # record = request.site.resource.getDirectory().recordWithUID(uid)
+ # if record:
+ # if record.recordType == DirectoryService.recordType_users:
+ # return record.shortNames[0]
+ # else:
+ # return "(%s)%s" % (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 = '"%s as %s"' % (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["serverInstance"] = config.LogID if config.LogID else "0"
</span><span class="cx">
</span><del>- format += ' or=%(outstandingRequests)s'
- formatArgs["outstandingRequests"] = request.chanRequest.channel.factory.outstandingRequests
</del><ins>+ if request.chanRequest: # This can be None during tests
+ format += ' or=%(outstandingRequests)s'
+ formatArgs["outstandingRequests"] = 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 "License");
-# 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 "AS IS" 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="localhost", port=80):
- """
- 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.
- """
- url = "http://%s:%d/auth/verify?auth_token=%s" % (host, port, token,)
- jsonResponse = (yield _getPage(url, host, port))
- try:
- response = json.loads(jsonResponse)
- except Exception, e:
- log.error("Error parsing JSON response from webauth: %s (%s)" %
- (jsonResponse, str(e)))
- raise WebAuthError("Could not look up token: %s" % (token,))
- if response["succeeded"]:
- returnValue(response["generated_uid"])
- else:
- raise WebAuthError("Could not look up token: %s" % (token,))
-
-
-
-def accessForUserToWiki(user, wiki, host="localhost", port=4444):
- """
- 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
- """
- url = "http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s" % (host, port,
- user, wiki)
- return _getPage(url, host, port)
-
-
-
-def _getPage(url, host, port):
- """
- 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.
- """
- factory = HTTPClientFactory(url)
- factory.protocol = HTTPPageGetter
- connect(GAIEndpoint(reactor, host, port), factory)
- return factory.deferred
-
-
-
-class WebAuthError(RuntimeError):
- """
- Error in web auth
- """
</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"> """
</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>- "addressbooks" : ("addressbook",),
- "calendars" : ("calendar",),
- "directory" : ("addressbook",),
- "principals" : ("addressbook", "calendar"),
- "webcal" : ("calendar",),
</del><ins>+ "addressbooks": ("addressbook",),
+ "calendars": ("calendar",),
+ "directory": ("addressbook",),
+ "principals": ("addressbook", "calendar"),
+ "webcal": ("calendar",),
</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>- "webcal" : True,
</del><ins>+ "webcal": 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, "_dead_properties"):
</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("User %r is not enabled with the %r SACL(s)" % (username, saclServices,))
</del><ins>+ log.warn(
+ "User {user!r} is not enabled with the {sacl!r} SACL(s)",
+ 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 != "unauthenticated":
</span><del>- log.debug("Wiki sessionID cookie value: %s" % (token,))
</del><ins>+ log.debug(
+ "Wiki sessionID cookie value: {token}", 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["URL"])
- username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
- directory = request.site.resource.getDirectory()
- record = directory.recordWithShortName("users", username)
- if record is not None:
- guid = record.guid
- else:
- guid = (yield guidForAuthToken(token))
- if guid == "unauthenticated":
- guid = None
</del><ins>+ uid = yield uidForAuthToken(token)
+ if uid == "unauthenticated":
+ 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("Unknown wiki token: %s" % (token,))
</del><ins>+ log.debug(
+ "Unknown wiki token: {token}", token=token
+ )
</ins><span class="cx"> else:
</span><del>- log.error("Failed to look up wiki token %s: %s" %
- (token, w.message,))
</del><ins>+ log.error(
+ "Failed to look up wiki token {token}: "
+ "{message}",
+ token=token, message=w.message
+ )
</ins><span class="cx">
</span><del>- except Exception, e:
- log.error("Failed to look up wiki token (%s)" % (e,))
- guid = None
</del><ins>+ except Exception as e:
+ log.error(
+ "Failed to look up wiki token: {error}",
+ error=e
+ )
+ uid = None
</ins><span class="cx">
</span><del>- if guid is not None:
- log.debug("Wiki lookup returned guid: %s" % (guid,))
</del><ins>+ if uid is not None:
+ log.debug(
+ "Wiki lookup returned uid: {uid}", 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("Wiki user record for user %s : %s" % (username, record))
</del><ins>+ log.debug(
+ "Wiki user record for user {user}: {record}",
+ 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("Wiki-authenticated principal %s being assigned to authnUser and authzUser" % (record.uid,))
- request.authzUser = request.authnUser = davxml.Principal(
- davxml.HRef.fromString("/principals/__uids__/%s/" % (record.uid,))
</del><ins>+ log.debug(
+ "Wiki-authenticated principal {record.uid} "
+ "being assigned to authnUser and authzUser",
+ record=record
</ins><span class="cx"> )
</span><ins>+ request.authzUser = request.authnUser = (
+ davxml.Principal(
+ davxml.HRef.fromString(
+ "/principals/__uids__/{}/"
+ .format(record.uid)
+ )
+ )
+ )
</ins><span class="cx">
</span><span class="cx"> if not hasattr(request, "authzUser") and config.WebCalendarAuthPath:
</span><span class="cx"> topLevel = request.path.strip("/").split("/")[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("x-forwarded-host",
- [config.ServerHostName])[-1].split(",")[-1].strip()
</del><ins>+ host = request.headers.getRawHeaders(
+ "x-forwarded-host",
+ [config.ServerHostName]
+ )[-1].split(",")[-1].strip()
</ins><span class="cx"> port = 443 if config.EnableSSL else 80
</span><span class="cx"> scheme = "https" if config.EnableSSL else "http"
</span><span class="cx">
</span><span class="cx"> response = RedirectResponse(
</span><del>- request.unparseURL(
- host=host,
- port=port,
- scheme=scheme,
- path=config.WebCalendarAuthPath,
- querystring="redirect=%s://%s%s" % (
- scheme,
- host,
- request.path
- )
- ),
- temporary=True
- )
</del><ins>+ request.unparseURL(
+ host=host,
+ port=port,
+ scheme=scheme,
+ path=config.WebCalendarAuthPath,
+ querystring="redirect={}://{}{}".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 ("inbox", "timezones"):
</span><span class="cx"> request.checkedSACL = True
</span><span class="cx">
</span><del>- elif (len(segments) > 2 and segments[0] in ("calendars", "principals") and
</del><ins>+ elif (
</ins><span class="cx"> (
</span><del>- segments[1] == "wikis" or
- (segments[1] == "__uids__" and segments[2].startswith("wiki-"))
</del><ins>+ len(segments) > 2 and
+ segments[0] in ("calendars", "principals") and
+ (
+ segments[1] == "wikis" or
+ (
+ segments[1] == "__uids__" 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("Wiki principal %s being assigned to authzUser" % (wikiName,))
</del><ins>+ log.debug(
+ "Wiki principal {name} being assigned to authzUser",
+ name=wikiName
+ )
</ins><span class="cx"> request.authzUser = davxml.Principal(
</span><del>- davxml.HRef.fromString("/principals/wikis/%s/" % (wikiName,))
</del><ins>+ davxml.HRef.fromString(
+ "/principals/wikis/{}/".format(wikiName)
+ )
</ins><span class="cx"> )
</span><span class="cx">
</span><del>- elif self.useSacls and not hasattr(request, "checkedSACL") and not hasattr(request, "checkingSACL"):
</del><ins>+ elif (
+ self.useSacls and
+ not hasattr(request, "checkedSACL") and
+ not hasattr(request, "checkingSACL")
+ ):
</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("Rejecting user-agent: %s" % (agent,))
</del><ins>+ log.info("Rejecting user-agent: {agent}", agent=agent)
</ins><span class="cx"> raise HTTPError(StatusResponse(
</span><span class="cx"> responsecode.FORBIDDEN,
</span><del>- "Your client software (%s) is not allowed to access this service." % (agent,)
</del><ins>+ "Your client software ({}) is not allowed to "
+ "access this service."
+ .format(agent)
</ins><span class="cx"> ))
</span><span class="cx">
</span><del>- if config.EnableResponseCache and request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
</del><ins>+ if (
+ config.EnableResponseCache and
+ request.method == "PROPFIND" and
+ not getattr(request, "notInCache", False) and
+ len(segments) > 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, "checkingCache", 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("Not found in cache.")
</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={"calendar": ["dreid"]})
</span><span class="cx">
</span><del>- directory = XMLDirectoryService(
- {
- "xmlFile" : xmlFile,
- "augmentService" :
- augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
- }
- )
</del><span class="cx">
</span><del>- principals = DirectoryPrincipalProvisioningResource(
- "/principals/",
- directory
- )
</del><span class="cx">
</span><del>- root = RootResource(self.docroot, principalCollections=[principals])
-
- root.putChild("principals",
- principals)
-
- portal = Portal(auth.DavRealm())
- portal.registerChecker(directory)
-
- self.root = auth.AuthenticationWrapper(
- root,
- portal,
- (basic.BasicCredentialFactory("Test realm"),),
- (basic.BasicCredentialFactory("Test realm"),),
- loginInterfaces=(auth.IPrincipal,))
-
- self.site = server.Site(self.root)
-
-
-
</del><span class="cx"> class ComplianceTests(RootTests):
</span><span class="cx"> """
</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"> """
</span><del>- request = SimpleRequest(self.site, method, ("/".join([""] + segments)))
- rsrc = self.root
</del><ins>+ request = SimpleStoreRequest(self, method, ("/".join([""] + 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"> """
</span><del>- self.root.resource.useSacls = False
</del><ins>+ self.actualRoot.useSacls = False
</ins><span class="cx">
</span><del>- request = SimpleRequest(self.site,
- "GET",
- "/principals/")
</del><ins>+ request = SimpleStoreRequest(self, "GET", "/principals/")
</ins><span class="cx">
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- self.root.locateChild, request, ["principals"]
- ))
-
</del><span class="cx"> resrc, segments = (yield maybeDeferred(
</span><del>- resrc.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</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"> """
</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"dreid"
+ )
+ request = SimpleStoreRequest(
+ self,
</ins><span class="cx"> "GET",
</span><span class="cx"> "/principals/",
</span><del>- headers=http_headers.Headers({
- "Authorization": [
- "basic",
- "%s" % ("dreid:dierd".encode("base64"),)
- ]
- })
</del><ins>+ authRecord=record
</ins><span class="cx"> )
</span><span class="cx">
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- self.root.locateChild, request, ["principals"]
- ))
-
</del><span class="cx"> resrc, segments = (yield maybeDeferred(
</span><del>- resrc.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</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"> """
</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"wsanchez"
+ )
+
+ request = SimpleStoreRequest(
+ self,
</ins><span class="cx"> "GET",
</span><span class="cx"> "/principals/",
</span><del>- headers=http_headers.Headers({
- "Authorization": [
- "basic",
- "%s" % ("wsanchez:zehcnasw".encode("base64"),)
- ]
- })
</del><ins>+ authRecord=record
</ins><span class="cx"> )
</span><span class="cx">
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- self.root.locateChild, request, ["principals"]
- ))
-
</del><span class="cx"> try:
</span><span class="cx"> resrc, _ignore_segments = (yield maybeDeferred(
</span><del>- resrc.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</ins><span class="cx"> ))
</span><ins>+ raise AssertionError(
+ "RootResource.locateChild did not return an error"
+ )
</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"> """
</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"> "GET",
</span><span class="cx"> "/principals/"
</span><span class="cx"> )
</span><span class="cx">
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- self.root.locateChild, request, ["principals"]
- ))
-
</del><span class="cx"> try:
</span><span class="cx"> resrc, _ignore_segments = (yield maybeDeferred(
</span><del>- resrc.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</ins><span class="cx"> ))
</span><span class="cx"> raise AssertionError(
</span><span class="cx"> "RootResource.locateChild did not return an error"
</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"> """
</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"> "GET",
</span><span class="cx"> "/principals/",
</span><del>- headers=http_headers.Headers({
- "Authorization": ["basic", "%s" % (
- "dreid:dreid".encode("base64"),)]}))
</del><ins>+ headers=http_headers.Headers(
+ {
+ "Authorization": [
+ "basic", "%s" % ("dreid:dreid".encode("base64"),)
+ ]
+ }
+ )
+ )
</ins><span class="cx">
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- self.root.locateChild, request, ["principals"]
- ))
-
</del><span class="cx"> try:
</span><span class="cx"> resrc, _ignore_segments = (yield maybeDeferred(
</span><del>- resrc.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</ins><span class="cx"> ))
</span><ins>+ raise AssertionError(
+ "RootResource.locateChild did not return an error"
+ )
</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("Incorrect response for DELETE /: %s"
</span><span class="cx"> % (response.code,))
</span><span class="cx">
</span><del>- request = SimpleRequest(self.site, "DELETE", "/")
</del><ins>+ request = SimpleStoreRequest(self, "DELETE", "/")
</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("Incorrect response for COPY /: %s"
</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"> "COPY",
</span><span class="cx"> "/",
</span><span class="cx"> headers=http_headers.Headers({"Destination": "/copy/"})
</span><span class="lines">@@ -342,8 +291,8 @@
</span><span class="cx"> self.fail("Incorrect response for MOVE /: %s"
</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"> "MOVE",
</span><span class="cx"> "/",
</span><span class="cx"> headers=http_headers.Headers({"Destination": "/copy/"})
</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 = """<?xml version="1.0" encoding="utf-8" ?>
</span><span class="cx"> <D:propfind xmlns:D="DAV:">
</span><span class="lines">@@ -387,48 +338,46 @@
</span><span class="cx"> </D:prop>
</span><span class="cx"> </D:propfind>
</span><span class="cx"> """
</span><ins>+ record = yield self.directory.recordWithShortName(
+ RecordType.user,
+ u"dreid"
+ )
</ins><span class="cx">
</span><del>- request = SimpleRequest(
- self.site,
</del><ins>+ request = SimpleStoreRequest(
+ self,
</ins><span class="cx"> "PROPFIND",
</span><span class="cx"> "/principals/users/dreid/",
</span><span class="cx"> headers=http_headers.Headers({
</span><del>- 'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
- 'Content-Type': 'application/xml; charset="utf-8"',
</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("Incorrect response for PROPFIND /principals/: %s" % (response.code,))
</del><ins>+ if response.code != responsecode.MULTI_STATUS:
+ self.fail("Incorrect response for PROPFIND /principals/: %s" % (response.code,))
</ins><span class="cx">
</span><del>- request = SimpleRequest(
- self.site,
- "PROPFIND",
- "/principals/users/dreid/",
- headers=http_headers.Headers({
- 'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
- 'Content-Type': 'application/xml; charset="utf-8"',
- 'Depth': '1',
- }),
- content=body
- )
</del><ins>+ request = SimpleStoreRequest(
+ self,
+ "PROPFIND",
+ "/principals/users/dreid/",
+ 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("Incorrect response for PROPFIND /principals/: %s" % (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("Incorrect response for PROPFIND /principals/: %s" % (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"> """
</span><span class="cx">
</span><del>- request = SimpleRequest(self.site, "GET", "/principals/")
</del><ins>+ request = SimpleStoreRequest(self, "GET", "/principals/")
</ins><span class="cx">
</span><span class="cx"> resrc, _ignore_segments = (yield maybeDeferred(
</span><del>- self.root.locateChild, request, ["principals"]
</del><ins>+ self.actualRoot.locateChild, request, ["principals"]
</ins><span class="cx"> ))
</span><del>- resrc, _ignore_segments = (yield maybeDeferred(
- resrc.locateChild, request, ["principals"]
- ))
</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"> """
</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 = "Invalid request: bad 'token' %s" % (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 != ""
- ):
</del><ins>+ if config.DirectoryProxy.Enabled:
</ins><span class="cx"> log.info("Adding directory proxy service")
</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"> """
</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"> "manhole_tap could not be imported"
</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"> """
</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"> """
</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 "Server" 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"> "Unknown database type {}".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(
+ "unix:{path}:mode=660".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"> """
</span><span class="cx"> Test various parameters of our usage.Options subclass
</span><span class="cx"> """
</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):
- """
- Utility class for ServiceMaker tests.
- """
- configOptions = None
-
- @inlineCallbacks
- def setUp(self):
- yield super(BaseServiceMakerTests, self).setUp()
- self.options = TestCalDAVOptions()
- self.options.parent = Options()
- self.options.parent["gid"] = None
- self.options.parent["uid"] = None
- self.options.parent["nodaemon"] = None
-
- self.config = ConfigDict(DEFAULT_CONFIG)
-
- accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
- resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml")
- augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml")
- pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
-
- self.config["DirectoryService"] = {
- "params": {"xmlFile": accountsFile},
- "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService"
- }
-
- self.config["ResourceService"] = {
- "params": {"xmlFile": resourcesFile},
- }
-
- self.config["AugmentService"] = {
- "params": {"xmlFiles": [augmentsFile]},
- "type": "twistedcaldav.directory.augment.AugmentXMLDB"
- }
-
- self.config.UseDatabase = False
- self.config.ServerRoot = self.mktemp()
- self.config.ConfigRoot = "config"
- self.config.ProcessType = "Single"
- 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):
- """
- Flush self.config out to self.configFile
- """
- writePlist(self.config, self.configFile)
-
-
- def makeService(self, patcher=passthru):
- """
- Create a service by calling into CalDAVServiceMaker with
- self.configFile
- """
- self.options.parseOptions(["-f", self.configFile])
-
- maker = CalDAVServiceMaker()
- maker = patcher(maker)
- return maker.makeService(self.options)
-
-
- def getSite(self):
- """
- Get the server.Site from the service by finding the HTTPFactory.
- """
- 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("No site found.")
-
-
-
</del><span class="cx"> def inServiceHierarchy(svc, predicate):
</span><span class="cx"> """
</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):
- """
- Test the service maker's behavior
- """
</del><ins>+# Tests for the various makeService_ flavors:
</ins><span class="cx">
</span><del>- def test_makeServiceDispatcher(self):
- """
- Test the default options of the dispatching makeService
- """
- validServices = ["Slave", "Combined"]
</del><ins>+class CalDAVServiceMakerTestBase(StoreTestCase):
</ins><span class="cx">
</span><del>- self.config["HTTPPort"] = 0
</del><ins>+ @inlineCallbacks
+ def setUp(self):
+ yield super(CalDAVServiceMakerTestBase, self).setUp()
+ self.options = TestCalDAVOptions()
+ self.options.parent = Options()
+ self.options.parent["gid"] = None
+ self.options.parent["uid"] = None
+ self.options.parent["nodaemon"] = None
</ins><span class="cx">
</span><del>- for service in validServices:
- self.config["ProcessType"] = service
- self.writeConfig()
- self.makeService()
</del><span class="cx">
</span><del>- self.config["ProcessType"] = "Unknown Service"
- 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 = "Single"
</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 = "Slave"
+
+ def test_makeService(self):
+ CalDAVServiceMaker().makeService(self.options)
+ # No error
+
+
+class CalDAVServiceMakerTestUnknown(CalDAVServiceMakerTestBase):
+
+ def configure(self):
+ super(CalDAVServiceMakerTestUnknown, self).configure()
+ config.ProcessType = "Unknown"
+
+ def test_makeService(self):
+ self.assertRaises(UsageError, CalDAVServiceMaker().makeService, self.options)
+ # error
+
+
+
+class ModesOnUNIXSocketsTests(CalDAVServiceMakerTestBase):
+
+ def configure(self):
+ super(ModesOnUNIXSocketsTests, self).configure()
+ config.ProcessType = "Combined"
+ 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"> """
</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"> """
</span><del>-
- self.config["HTTPPort"] = 0 # Don't conflict with the test above.
- alternateGroup = determineAppropriateGroupID()
- self.config.GroupName = grp.getgrgid(alternateGroup).gr_name
-
- self.config["ProcessType"] = "Combined"
- 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("660", 8),
</span><span class="cx"> "Wrong mode on %s: %s" % (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 ["unix-stats"]:
</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("660", 8),
</span><span class="cx"> "Wrong mode on %s: %s" % (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 = "Combined"
+
</ins><span class="cx"> def test_processMonitor(self):
</span><span class="cx"> """
</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"> """
</span><del>- self.config["ProcessType"] = "Combined"
- 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 = "Combined"
+
+
</ins><span class="cx"> def test_storeQueuerSetInMaster(self):
</span><span class="cx"> """
</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"> """
</span><del>- self.config["ProcessType"] = "Combined"
- 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, "storageService", 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"> """
</span><span class="cx"> Test various configurations of the Slave service
</span><span class="cx"> """
</span><span class="cx">
</span><del>- configOptions = {
- "HTTPPort": 8008,
- "SSLPort": 8443,
- }
</del><ins>+ def configure(self):
+ super(SlaveServiceTests, self).configure()
+ config.ProcessType = "Slave"
+ config.HTTPPort = 8008
+ config.SSLPort = 8443
+ pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+ 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"> """
</span><span class="cx"> Test the value of a Slave service in it's simplest
</span><span class="cx"> configuration.
</span><span class="cx"> """
</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"> """
</span><span class="cx"> # Note: the listeners are bundled within a MultiService named "ConnectionService"
</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["HTTPPort"]),
- (MaxAcceptSSLServer, self.config["SSLPort"]),
</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"> """
</span><del>- service = self.makeService().getServiceNamed(CalDAVService.connectionServiceName)
</del><ins>+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ 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["SSLPrivateKey"],
</del><ins>+ config.SSLPrivateKey,
</ins><span class="cx"> context.privateKeyFileName
</span><span class="cx"> )
</span><span class="cx"> self.assertEquals(
</span><del>- self.config["SSLCertificate"],
</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 = "Slave"
+ config.HTTPPort = 8008
+ # pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+ # config.SSLPrivateKey = pemFile
+ # config.SSLCertificate = pemFile
+ # config.EnableSSL = True
+
</ins><span class="cx"> def test_noSSL(self):
</span><span class="cx"> """
</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"> """
</span><del>- del self.config["SSLPort"]
- self.writeConfig()
</del><ins>+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ 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 = "Slave"
+ config.SSLPort = 8443
+ pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+ config.SSLPrivateKey = pemFile
+ config.SSLCertificate = pemFile
+ config.EnableSSL = True
+
</ins><span class="cx"> def test_noHTTP(self):
</span><span class="cx"> """
</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"> """
</span><del>- del self.config["HTTPPort"]
- self.writeConfig()
</del><ins>+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ 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 = "Slave"
+ config.HTTPPort = 8008
+ config.BindAddresses = ["127.0.0.1"]
+
</ins><span class="cx"> def test_singleBindAddresses(self):
</span><span class="cx"> """
</span><span class="cx"> Test that the TCPServer and SSLServers are bound to the proper address
</span><span class="cx"> """
</span><del>- self.config.BindAddresses = ["127.0.0.1"]
- self.writeConfig()
</del><ins>+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ 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["interface"], "127.0.0.1")
</span><span class="cx">
</span><span class="cx">
</span><ins>+class MultipleBindAddressesTests(CalDAVServiceMakerTestBase):
+
+ def configure(self):
+ super(MultipleBindAddressesTests, self).configure()
+ config.ProcessType = "Slave"
+ config.HTTPPort = 8008
+ config.SSLPort = 8443
+ pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+ config.SSLPrivateKey = pemFile
+ config.SSLCertificate = pemFile
+ config.EnableSSL = True
+ config.BindAddresses = [
+ "127.0.0.1",
+ "10.0.0.2",
+ "172.53.13.123",
+ ]
+
</ins><span class="cx"> def test_multipleBindAddresses(self):
</span><span class="cx"> """
</span><span class="cx"> Test that the TCPServer and SSLServers are bound to the proper
</span><span class="cx"> addresses.
</span><span class="cx"> """
</span><del>- self.config.BindAddresses = [
- "127.0.0.1",
- "10.0.0.2",
- "172.53.13.123",
- ]
</del><ins>+ # Note: the listeners are bundled within a MultiService named "ConnectionService"
+ 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["interface"] == 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 = "Slave"
+ config.ListenBacklog = 1024
+ config.HTTPPort = 8008
+ config.SSLPort = 8443
+ pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
+ config.SSLPrivateKey = pemFile
+ config.SSLCertificate = pemFile
+ config.EnableSSL = True
+ config.BindAddresses = [
+ "127.0.0.1",
+ "10.0.0.2",
+ "172.53.13.123",
+ ]
+
</ins><span class="cx"> def test_listenBacklog(self):
</span><span class="cx"> """
</span><span class="cx"> Test that the backlog arguments is set in TCPServer and SSLServers
</span><span class="cx"> """
</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 "ConnectionService"
+ 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):
- """
- Test the configuration of the initial resource hierarchy of the
- single service
- """
- configOptions = {"HTTPPort": 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 = "http/hello@bob"
+ config.Authentication.Basic.Enabled = True
+
+
</ins><span class="cx"> def test_AuthWrapperAllEnabled(self):
</span><span class="cx"> """
</span><span class="cx"> Test the configuration of the authentication wrapper
</span><span class="cx"> when all schemes are enabled.
</span><span class="cx"> """
</span><del>- self.config.Authentication.Digest.Enabled = True
- self.config.Authentication.Kerberos.Enabled = True
- self.config.Authentication.Kerberos.ServicePrincipal = "http/hello@bob"
- 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 = ["negotiate", "digest", "basic"]
</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["negotiate"]
</ins><span class="cx">
</span><ins>+ self.assertEquals(ncf.service, "http@HELLO")
+ self.assertEquals(ncf.realm, "bob")
+
+
+
+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 = ""
+ config.Authentication.Basic.Enabled = True
+
</ins><span class="cx"> def test_servicePrincipalNone(self):
</span><span class="cx"> """
</span><span class="cx"> Test that the Kerberos principal look is attempted if the principal is empty.
</span><span class="cx"> """
</span><del>- self.config.Authentication.Kerberos.ServicePrincipal = ""
- 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("negotiate" in authWrapper.credentialFactories)
</span><span class="cx">
</span><span class="cx">
</span><del>- def test_servicePrincipal(self):
- """
- Test that the kerberos realm is the realm portion of a principal
- in the form proto/host@realm
- """
- self.config.Authentication.Kerberos.ServicePrincipal = "http/hello@bob"
- self.config.Authentication.Kerberos.Enabled = True
- self.writeConfig()
- site = self.getSite()
</del><span class="cx">
</span><del>- authWrapper = site.resource.resource
- ncf = authWrapper.credentialFactories["negotiate"]
</del><ins>+class AuthWrapperPartialEnabledTests(CalDAVServiceMakerTestBase):
</ins><span class="cx">
</span><del>- self.assertEquals(ncf.service, "http@HELLO")
- self.assertEquals(ncf.realm, "bob")
</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"> """
</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 = ["digest"]
</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"> """
</span><span class="cx"> Test the configuration of the log wrapper
</span><span class="cx"> """
</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):
+ """
+ Test the configuration of the auth wrapper
+ """
+ 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"> """
</span><span class="cx"> Test the root resource
</span><span class="cx"> """
</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"> """
</span><span class="cx"> Test the principal resource
</span><span class="cx"> """
</span><del>- site = self.getSite()
- root = site.resource.resource.resource
-
</del><span class="cx"> self.failUnless(isinstance(
</span><del>- root.getChild("principals"),
</del><ins>+ (yield self.actualRoot.getChild("principals")),
</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"> """
</span><span class="cx"> Test the calendar resource
</span><span class="cx"> """
</span><del>- site = self.getSite()
- root = site.resource.resource.resource
-
</del><span class="cx"> self.failUnless(isinstance(
</span><del>- root.getChild("calendars"),
</del><ins>+ (yield self.actualRoot.getChild("calendars")),
</ins><span class="cx"> DirectoryCalendarHomeProvisioningResource
</span><span class="cx"> ))
</span><span class="cx">
</span><span class="cx">
</span><del>-
-class DirectoryServiceTest(BaseServiceMakerTests):
- """
- Tests of the directory service
- """
-
- configOptions = {"HTTPPort": 8008}
-
</del><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_sameDirectory(self):
</span><span class="cx"> """
</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"> """
</span><del>- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- calendars = site.resource.resource.resource.getChild("calendars")
</del><ins>+ principals = yield self.actualRoot.getChild("principals")
+ calendars = yield self.actualRoot.getChild("calendars")
</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):
- """
- Assert that the base directory service is actually
- an AggregateDirectoryService
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
</del><span class="cx">
</span><del>- self.failUnless(isinstance(directory, AggregateDirectoryService))
-
-
- def test_configuredDirectoryService(self):
- """
- Test that the real directory service is the directory service
- set in the configuration file.
- """
- site = self.getSite()
- principals = site.resource.resource.resource.getChild("principals")
- directory = principals.directory
-
- realDirectory = directory.serviceForRecordType("users")
-
- configuredDirectory = namedAny(self.config.DirectoryService.type)
-
- self.failUnless(isinstance(realDirectory, configuredDirectory))
-
-
-
</del><span class="cx"> class DummyProcessObject(object):
</span><span class="cx"> """
</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"> """
</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(["[Dummy] x",
</span><span class="cx"> "[Dummy] y",
</span><del>- "[Dummy] y", # final segment
</del><ins>+ "[Dummy] y", # final segment
</ins><span class="cx"> "[Dummy] z"],
</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"> ("a", ["a"]),
</span><span class="cx"> ("abcde", ["abcde"]),
</span><span class="cx"> ("abcdefghij", ["abcdefghij"]),
</span><del>- ("abcdefghijk",
- ["abcdefghij (truncated, continued)",
- "k"
</del><ins>+ (
+ "abcdefghijk",
+ [
+ "abcdefghij (truncated, continued)",
+ "k"
</ins><span class="cx"> ]
</span><span class="cx"> ),
</span><del>- ("abcdefghijklmnopqrst",
- ["abcdefghij (truncated, continued)",
- "klmnopqrst"
</del><ins>+ (
+ "abcdefghijklmnopqrst",
+ [
+ "abcdefghij (truncated, continued)",
+ "klmnopqrst"
</ins><span class="cx"> ]
</span><span class="cx"> ),
</span><del>- ("abcdefghijklmnopqrstuv",
- ["abcdefghij (truncated, continued)",
- "klmnopqrst (truncated, continued)",
- "uv"]
</del><ins>+ (
+ "abcdefghijklmnopqrstuv",
+ [
+ "abcdefghij (truncated, continued)",
+ "klmnopqrst (truncated, continued)",
+ "uv"
+ ]
</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("twistd")[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, "HUP")
</span><span class="cx"> reactor.callLater(6, proc.signalProcess, "TERM")
</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>- "getpwnam" : _getpwnam,
- "getgrnam" : _getgrnam,
- "getuid" : _getuid,
- "getgid" : _getgid,
- "KeyError" : KeyError,
- "ConfigurationError" : ConfigurationError,
</del><ins>+ "getpwnam": _getpwnam,
+ "getgrnam": _getgrnam,
+ "getuid": _getuid,
+ "getgid": _getgid,
+ "KeyError": KeyError,
+ "ConfigurationError": 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"> """
</span><del>- self.assertRaises(ConfigurationError, self._wrappedFunction(),
- "nonexistent", "exists")
</del><ins>+ self.assertRaises(
+ ConfigurationError, self._wrappedFunction(),
+ "nonexistent", "exists"
+ )
</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"> """
</span><del>- self.assertRaises(ConfigurationError, self._wrappedFunction(),
- "exists", "nonexistent")
</del><ins>+ self.assertRaises(
+ ConfigurationError, self._wrappedFunction(),
+ "exists", "nonexistent"
+ )
</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, "store",
- None, "storageService", reactor=self.clock)
</del><ins>+ self.pps = PreProcessingService(
+ self.fakeServiceCreator, None, "store",
+ None, "storageService", 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("")
</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):
- """
- Ensure augments service is on by default
- """
- dir = directoryFromConfig(config)
- for service in dir._recordTypes.values():
- # all directory services belonging to the aggregate have
- # augmentService set to AugmentXMLDB
- if hasattr(service, "augmentService"):
- 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"> """
</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["cache"] = 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 = "https://%s:%s" % (config.ServerHostName, config.SSLPort,)
</del><ins>+ uri = "https://{config.ServerHostName}:{config.SSLPort}".format(config=config)
</ins><span class="cx"> else:
</span><del>- uri = "http://%s:%s" % (config.ServerHostName, config.HTTPPort,)
</del><ins>+ uri = "https://{config.ServerHostName}:{config.HTTPPort}".format(config=config)
</ins><span class="cx"> attachments_uri = uri + "/calendars/__uids__/%(home)s/dropbox/%(dropbox_id)s/%(name)s"
</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):
- """
- Create an L{AggregateDirectoryService} from the given configuration.
- """
- #
- # Setup the Augment Service
- #
- if config.AugmentService.type:
- augmentClass = namedClass(config.AugmentService.type)
- log.info("Configuring augment service of type: {augmentClass}",
- augmentClass=augmentClass)
- try:
- augmentService = augmentClass(**config.AugmentService.params)
- except IOError:
- log.error("Could not start augment service")
- 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(
+ "No such user: {user}".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(
+ "User not allowed to log in: {user}".format(
+ user=credentials.credentials.username
+ )
+ )
</ins><span class="cx">
</span><del>- log.info("Configuring directory service of type: {directoryType}",
- 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(
+ "Incorrect credentials for user: {user}".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("Configuring resource service of type: {resourceClass}",
- 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("utf-8")
- directory.setRealm(realmName)
- except ImportError:
- pass
- log.info("Setting up principal collection: {cls}", cls=principalResourceClass)
- principalResourceClass("/principals/", directory)
- return directory
-
-
-
</del><span class="cx"> def getRootResource(config, newStore, resources=None):
</span><span class="cx"> """
</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("/principals/", 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("Configuring proxydb service of type: {cls}", 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("Could not start proxydb service")
- raise
</del><ins>+ # log.info("Configuring proxydb service of type: {cls}", cls=proxydbClass)
</ins><span class="cx">
</span><ins>+ # try:
+ # calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+ # except IOError:
+ # log.error("Could not start proxydb service")
+ # 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 ""
</del><ins>+ realm = directory.realmName.encode("utf-8") or ""
</ins><span class="cx">
</span><span class="cx"> log.info("Configuring authentication for realm: {realm}", realm=realm)
</span><span class="cx">
</span><span class="lines">@@ -491,7 +460,7 @@
</span><span class="cx"> #
</span><span class="cx"> log.info("Setting up document root at: {root}", 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("Setting up calendar collection: {cls}", 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("Setting up directory address book: {cls}",
</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("/", config.DirectoryAddressBook.name, "/")
</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("after", "startup", 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("Deleted: {path}", 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("Configuring authentication wrapper")
</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"> """
</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 "caldavd -t Agent" 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 "caldavd -t Agent" which ends up creating
+this service. Requests are made using HTTP POSTS to /gateway, and are
+authenticated by OpenDirectory.
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> from __future__ import print_function
</span><span class="cx">
</span><ins>+__all__ = [
+ "makeAgentService",
+]
+
</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:
- """
- A checker that knows how to ask OpenDirectory to authenticate via Digest
- """
- implements(ICredentialsChecker)
-
- credentialInterfaces = (IUsernameHashedPassword,)
-
- from calendarserver.platform.darwin.od import opendirectory
- directoryModule = opendirectory
-
- def __init__(self, node):
- """
- @param node: the name of the OpenDirectory node to use, e.g. /Local/Default
- """
- 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 "algorithm" not in credentials.fields:
- credentials.fields["algorithm"] = "md5"
-
- challenge = 'Digest realm="%(realm)s", nonce="%(nonce)s", algorithm=%(algorithm)s' % credentials.fields
-
- response = (
- 'Digest username="%(username)s", '
- 'realm="%(realm)s", '
- 'nonce="%(nonce)s", '
- 'uri="%(uri)s", '
- 'response="%(response)s",'
- 'algorithm=%(algorithm)s'
- ) % credentials.fields
-
- except KeyError as e:
- log.error(
- "OpenDirectory (node=%s) error while performing digest authentication for user %s: "
- "missing digest response field: %s in: %s"
- % (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("Failed digest auth with response: %s" % (response,))
- return fail(UnauthorizedLogin())
- except Exception as e:
- log.error(
- "OpenDirectory error while performing digest authentication for user %s: %s"
- % (credentials.username, e)
- )
- return fail(UnauthorizedLogin())
-
- else:
- return fail(UnauthorizedLogin())
-
-
-
-class CustomDigestCredentialFactory(DigestCredentialFactory):
- """
- DigestCredentialFactory without qop, to interop with OD.
- """
-
- def getChallenge(self, address):
- result = DigestCredentialFactory.getChallenge(self, address)
- del result["qop"]
- return result
-
-
-
</del><span class="cx"> class AgentRealm(object):
</span><span class="cx"> """
</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"> """
</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"> """
</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>- "Error" : message,
- "Traceback" : tbString,
</del><ins>+ "Error": message,
+ "Traceback": tbString,
</ins><span class="cx"> }
</span><del>- log.error("command failed %s" % (failure,))
</del><ins>+ log.error("command failed {error}", 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("Agent inactive; shutting down")
</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("gateway", AgentGatewayResource(store,
- davRootResource, directory, inactivityDetector))
</del><ins>+ root.putChild(
+ "gateway",
+ AgentGatewayResource(
+ store, davRootResource, directory, inactivityDetector
+ )
+ )
</ins><span class="cx">
</span><del>- realmName = "/Local/Default"
- portal = Portal(AgentRealm(root, ["com.apple.calendarserver"]),
- [DirectoryServiceChecker(realmName)])
- credentialFactory = CustomDigestCredentialFactory("md5", realmName)
</del><ins>+ directory = OpenDirectoryDirectoryService("/Local/Default")
+
+ portal = Portal(
+ AgentRealm(root, ["com.apple.calendarserver"]),
+ [HTTPDigestCredentialChecker(directory)]
+ )
+ credentialFactory = NoQOPDigestCredentialFactory(
+ "md5", "CalendarServer Agent Realm"
+ )
</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 > 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 = {
- "Error" : str(e),
- }
</del><ins>+ error = {"Error": 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 = """<?xml version="1.0" encoding="UTF-8"?>
</span><del>-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</del><ins>+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</ins><span class="cx"> <plist version="1.0">
</span><span class="cx"> <dict>
</span><span class="cx"> <key>command</key>
</span><span class="lines">@@ -414,6 +358,7 @@
</span><span class="cx"> </dict>
</span><span class="cx"> </plist>"""
</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"> """
</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"> """
</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(("Owner UID", "Calendar Objects"))
</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"> "%s/%s (%s)" % (record.recordType if record else "-", record.shortNames[0] if record else "-", 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(("Owner UID", "Calendar Objects"))
</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"> "%s/%s (%s)" % (record.recordType if record else "-", record.shortNames[0] if record else "-", uid,),
</span><span class="cx"> count,
</span><span class="lines">@@ -1152,7 +1147,7 @@
</span><span class="cx"> table.addHeader(("Owner", "Event UID", "RID", "Problem",))
</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"> "%s/%s (%s)" % (owner_record.recordType if owner_record else "-", owner_record.shortNames[0] if owner_record else "-", 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("Duplicate VALARMS")
</span><del>- self.noPrincipalPathCUAddresses(component, doFix=False)
</del><ins>+ yield self.noPrincipalPathCUAddresses(component, doFix=False)
</ins><span class="cx"> if self.options["ical"]:
</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("__uids__") != -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("http"):
</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("iCalendar ORGANIZER starts with 'http(s)'")
</span><span class="cx"> elif cuaddr.startswith("mailto:"):
</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("iCalendar ORGANIZER starts with 'mailto:' and record exists")
</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("mailto:%s" % (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("SCHEDULE-AGENT", "SERVER") == "NONE":
</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("http"):
</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("iCalendar ATTENDEE starts with 'http(s)'")
</span><span class="cx"> elif cuaddr.startswith("mailto:"):
</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("iCalendar ATTENDEE starts with 'mailto:' and record exists")
</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("mailto:%s" % (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("iCalendar ATTENDEE missing mailto:")
</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["ical"]:
</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("Number of organizer events to process", len(self.organized), self.total)
</span><span class="cx"> self.logResult("Number of attendee events to process", 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"> """
</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"> """
</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"> """
</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"> "%s/%s (%s)" % (organizer_record.recordType if organizer_record else "-", organizer_record.shortNames[0] if organizer_record else "-", organizer,),
</span><span class="cx"> "%s/%s (%s)" % (attendee_record.recordType if attendee_record else "-", attendee_record.shortNames[0] if attendee_record else "-", 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"> "%s/%s (%s)" % (organizer_record.recordType if organizer_record else "-", organizer_record.shortNames[0] if organizer_record else "-", organizer,),
</span><span class="cx"> "%s/%s (%s)" % (attendee_record.recordType if attendee_record else "-", attendee_record.shortNames[0] if attendee_record else "-", 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("Reloaded missing organizer data: %s" % (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 = "%s/%s (%s)" % (organizerRecord.recordType if organizerRecord else "-", organizerRecord.shortNames[0] if organizerRecord else "-", 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"> "%s/%s (%s)" % (attendeeRecord.recordType if attendeeRecord else "-", attendeeRecord.shortNames[0] if attendeeRecord else "-", 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 = "%s/%s (%s)" % (organizerRecord.recordType if organizerRecord else "-", organizerRecord.shortNames[0] if organizerRecord else "-", 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"> "%s/%s (%s)" % (attendeeRecord.recordType if attendeeRecord else "-", attendeeRecord.shortNames[0] if attendeeRecord else "-", 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, "autoScheduleMode", 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["uuid"], ]
</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, "autoSchedule", AutoScheduleMode.none)
</ins><span class="cx">
</span><span class="cx"> if len(uuids) > 1 and not self.options["summary"]:
</span><span class="cx"> self.output.write("\n\n-----------------------------\n")
</span><span class="lines">@@ -2205,7 +2205,7 @@
</span><span class="cx"> if not self.options["summary"]:
</span><span class="cx"> self.logResult("UUID to process", uuid)
</span><span class="cx"> self.logResult("Record name", rname)
</span><del>- self.logResult("Auto-schedule", "True" if auto else "False")
</del><ins>+ self.logResult("Auto-schedule-mode", autoScheduleMode.description)
</ins><span class="cx"> self.addSummaryBreak()
</span><span class="cx"> self.logResult("Number of events to process", 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["summary"]:
</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["verbose"]:
</span><span class="cx"> self.output.write("%d uuids to check\n" % (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["uuid"], ]
</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) > 1 and not self.options["summary"]:
</span><span class="cx"> self.output.write("\n\n-----------------------------\n")
</span><span class="lines">@@ -2602,9 +2602,10 @@
</span><span class="cx"> if self.options["no-organizer"]:
</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("urn:uuid:"):
</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["invalid-organizer"]:
</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"> """
</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"> """
</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"> """
</span><span class="cx"> Retrieve the home UID.
</span><span class="cx"> """
</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 "Locations", i.e. scheduled spaces
+ 'Capacity': {'attr': 'capacity', },
+ 'Floor': {'attr': 'floor', },
+ 'AssociatedAddress': {'attr': 'associatedAddress', },
+
+ # For "Addresses", 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"> """
</span><span class="cx"> Create/run a Runner to execute the commands
</span><span class="cx"> """
</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, ["locations"])
</del><ins>+ return self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
</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):
+ """
+ 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, "locations", **kwargs))
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
</del><ins>+ @param typeName: one of "locations", "resources", "addresses"; 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}
+ """
</ins><span class="cx">
</span><del>- readProxies = command.get("ReadProxies", None)
- writeProxies = command.get("WriteProxies", 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, ["locations"])
</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("Principal not found: %s" % (guid,))
- return
- recordDict = recordToDict(record)
- principal = principalForPrincipalID(guid, directory=self.dir)
- if principal is None:
- self.respondWithError("Principal not found: %s" % (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("utf-8"))
+ else:
+ newList.append(item)
+ value = newList
+ elif isinstance(value, str):
+ value = value.decode("utf-8")
</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', "")))
-
- kwargs = {}
- for key, info in attrMap.iteritems():
- if key in command:
- kwargs[info['attr']] = command[key]
- try:
- record = (yield updateRecord(False, self.dir, "locations", **kwargs))
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
-
</del><span class="cx"> readProxies = command.get("ReadProxies", 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("WriteProxies", 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("locations", **kwargs)
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
- self.respondWithRecordsOfTypes(self.dir, command, ["locations"])
</del><span class="cx">
</span><ins>+ def command_createLocation(self, command):
+ return self._saveRecord("locations", 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, ["resources"])
-
-
- @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("resources", CalRecordType.resource, command)
</ins><span class="cx">
</span><del>- try:
- record = (yield updateRecord(True, self.dir, "resources", **kwargs))
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
</del><span class="cx">
</span><del>- readProxies = command.get("ReadProxies", None)
- writeProxies = command.get("WriteProxies", 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("addresses", CalRecordType.address, command)
</ins><span class="cx">
</span><del>- self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
</del><span class="cx">
</span><ins>+ @inlineCallbacks
+ def command_setLocationAttributes(self, command):
+ uid = command['GeneratedUID']
+ record = yield self.dir.recordWithUID(uid)
+ yield self._saveRecord(
+ "locations",
+ 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(
+ "resources",
+ 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', "")))
</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, "resources", **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(
+ "addresses",
+ 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("Principal not found: %s" % (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("ReadProxies", None)
- writeProxies = command.get("WriteProxies", 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("resources", **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, ["resources"])
</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, ["locations", "resources"])
</del><ins>+ return self.respondWithRecordsOfTypes(self.dir, command, ["locations", "resources"])
</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, ["addresses"])
</del><ins>+ return self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
</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, "addresses", **kwargs)
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
</del><span class="cx">
</span><del>- self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
</del><ins>+ def command_deleteLocation(self, command):
+ return self._delete("locations", 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("Principal not found: %s" % (guid,))
- return
- recordDict = recordToDict(record)
- self.respond(command, recordDict)
- return succeed(None)
</del><ins>+ def command_deleteResource(self, command):
+ return self._delete("resources", 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, "addresses", **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("addresses", **kwargs)
- except DirectoryError, e:
- self.respondWithError(str(e))
- return
- self.respondWithRecordsOfTypes(self.dir, command, ["addresses"])
</del><ins>+ return self._delete("addresses", 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("Principal not found: %s" % (command['Principal'],))
- return
- (yield self.respondWithProxies(self.dir, command, principal, "write"))
</del><ins>+ return self._listProxies(command, "write")
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ def command_listReadProxies(self, command):
+ return self._listProxies(command, "read")
+
</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("Principal not found: %s" % (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("Proxy not found: %s" % (command['Proxy'],))
- return
- try:
- (yield addProxy(self.root, self.dir, self.store, principal, "write", proxy))
- except ProxyError, e:
- self.respondWithError(str(e))
- return
- except ProxyWarning, e:
- pass
- (yield self.respondWithProxies(self.dir, command, principal, "write"))
</del><span class="cx">
</span><ins>+ def command_addReadProxy(self, command):
+ return self._addProxy(command, "read")
</ins><span class="cx">
</span><ins>+
+ def command_addWriteProxy(self, command):
+ return self._addProxy(command, "write")
+
+
</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("Principal not found: %s" % (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("Proxy not found: %s" % (command['Proxy'],))
</span><del>- return
- try:
- (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=("write",)))
- except ProxyError, e:
- self.respondWithError(str(e))
- return
- except ProxyWarning, e:
- pass
- (yield self.respondWithProxies(self.dir, command, principal, "write"))
</del><ins>+ returnValue(None)
</ins><span class="cx">
</span><ins>+ txn = self.store.newTransaction()
+ yield addDelegate(txn, record, proxyRecord, (proxyType == "write"))
+ 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("Principal not found: %s" % (command['Principal'],))
- return
- (yield self.respondWithProxies(self.dir, command, principal, "read"))
</del><span class="cx">
</span><ins>+ def command_removeReadProxy(self, command):
+ return self._removeProxy(command, "read")
</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("Principal not found: %s" % (command['Principal'],))
- return
- proxy = principalForPrincipalID(command['Proxy'], directory=self.dir)
- if proxy is None:
- self.respondWithError("Proxy not found: %s" % (command['Proxy'],))
- return
- try:
- (yield addProxy(self.root, self.dir, self.store, principal, "read", proxy))
- except ProxyError, e:
- self.respondWithError(str(e))
- return
- except ProxyWarning, e:
- pass
- (yield self.respondWithProxies(self.dir, command, principal, "read"))
</del><span class="cx">
</span><ins>+ def command_removeWriteProxy(self, command):
+ return self._removeProxy(command, "write")
</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("Principal not found: %s" % (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("Proxy not found: %s" % (command['Proxy'],))
</span><del>- return
- try:
- (yield removeProxy(self.root, self.dir, self.store, principal, proxy, proxyTypes=("read",)))
- except ProxyError, e:
- self.respondWithError(str(e))
- return
- except ProxyWarning, e:
- pass
- (yield self.respondWithProxies(self.dir, command, principal, "read"))
</del><ins>+ returnValue(None)
</ins><span class="cx">
</span><ins>+ txn = self.store.newTransaction()
+ yield removeDelegate(txn, record, proxyRecord, (proxyType == "write"))
+ 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"> """
</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, "RetainDays" : retainDays})
</del><ins>+ self.respond(command, {'EventsRemoved': eventCount, "RetainDays": 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 = {
+ "read": DelegateRecordType.readDelegateGroup,
+ "write": 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 == "RealName":
+ value = value[0]
</ins><span class="cx"> if isinstance(value, str):
</span><span class="cx"> value = value.decode("utf-8")
</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("%s\n" % (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("upgradeData(c)", globals(), {"c" : config}, "/tmp/upgrade.prof")
</del><ins>+ cProfile.runctx("upgradeData(c)", globals(), {"c": config}, "/tmp/upgrade.prof")
</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 = {
+ "default": None,
+ "none": AutoScheduleMode.none,
+ "accept-always": AutoScheduleMode.accept,
+ "decline-always": AutoScheduleMode.decline,
+ "accept-if-free": AutoScheduleMode.acceptIfFree,
+ "decline-if-busy": AutoScheduleMode.declineIfBusy,
+ "automatic": 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("Valid record types:")
- for recordType in config.directory.recordTypes():
- print(" %s" % (recordType,))
-
</del><span class="cx"> print(e)
</span><span class="cx"> print("")
</span><span class="cx">
</span><span class="lines">@@ -74,20 +75,18 @@
</span><span class="cx"> print(" --search <search-string>: search for matching principals")
</span><span class="cx"> print(" --list-principal-types: list all of the known principal types")
</span><span class="cx"> print(" --list-principals type: list all principals of the given type")
</span><del>- print(" --read-property=property: read DAV property (eg.: {DAV:}group-member-set)")
</del><span class="cx"> print(" --list-read-proxies: list proxies with read-only access")
</span><span class="cx"> print(" --list-write-proxies: list proxies with read-write access")
</span><span class="cx"> print(" --list-proxies: list all proxies")
</span><ins>+ print(" --list-proxy-for: principals this principal is a proxy for")
</ins><span class="cx"> print(" --add-read-proxy=principal: add a read-only proxy")
</span><span class="cx"> print(" --add-write-proxy=principal: add a read-write proxy")
</span><span class="cx"> print(" --remove-proxy=principal: remove a proxy")
</span><del>- print(" --set-auto-schedule={true|false}: set auto-accept state")
- print(" --get-auto-schedule: read auto-schedule state")
</del><span class="cx"> print(" --set-auto-schedule-mode={default|none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic}: set auto-schedule mode")
</span><span class="cx"> print(" --get-auto-schedule-mode: read auto-schedule mode")
</span><span class="cx"> print(" --set-auto-accept-group=principal: set auto-accept-group")
</span><span class="cx"> print(" --get-auto-accept-group: read auto-accept-group")
</span><del>- print(" --add {locations|resources|addresses} 'full name' [record name] [GUID]: add a principal")
</del><ins>+ print(" --add {locations|resources|addresses} full-name record-name UID: add a principal")
</ins><span class="cx"> print(" --remove: remove a principal")
</span><span class="cx"> print(" --set-geo=url: set the geo: url for an address (e.g. geo:37.331741,-122.030333)")
</span><span class="cx"> print(" --get-geo: get the geo: url for an address")
</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"> """
</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 "params".
</span><span class="cx"> """
</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 "Locations", i.e. scheduled spaces
- 'Capacity' : { 'extras' : True, 'attr' : 'capacity', },
- 'Floor' : { 'extras' : True, 'attr' : 'floor', },
- 'AssociatedAddress' : { 'extras' : True, 'attr' : 'associatedAddress', },
-
- # For "Addresses", 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"> "search=",
</span><span class="cx"> "list-principal-types",
</span><span class="cx"> "list-principals=",
</span><del>- "read-property=",
</del><span class="cx"> "list-read-proxies",
</span><span class="cx"> "list-write-proxies",
</span><span class="cx"> "list-proxies",
</span><ins>+ "list-proxy-for",
</ins><span class="cx"> "add-read-proxy=",
</span><span class="cx"> "add-write-proxy=",
</span><span class="cx"> "remove-proxy=",
</span><del>- "set-auto-schedule=",
- "get-auto-schedule",
</del><span class="cx"> "set-auto-schedule-mode=",
</span><span class="cx"> "get-auto-schedule-mode",
</span><span class="cx"> "set-auto-accept-group=",
</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("utf-8")
+
</ins><span class="cx"> if opt in ("-h", "--help"):
</span><span class="cx"> usage()
</span><span class="cx">
</span><span class="lines">@@ -217,13 +194,6 @@
</span><span class="cx"> elif opt in ("", "--search"):
</span><span class="cx"> searchPrincipals = arg
</span><span class="cx">
</span><del>- elif opt in ("", "--read-property"):
- try:
- qname = decodeXMLName(arg)
- except ValueError, e:
- abort(e)
- principalActions.append((action_readProperty, qname))
-
</del><span class="cx"> elif opt in ("", "--list-read-proxies"):
</span><span class="cx"> principalActions.append((action_listProxies, "read"))
</span><span class="cx">
</span><span class="lines">@@ -233,6 +203,9 @@
</span><span class="cx"> elif opt in ("-L", "--list-proxies"):
</span><span class="cx"> principalActions.append((action_listProxies, "read", "write"))
</span><span class="cx">
</span><ins>+ elif opt in ("--list-proxy-for"):
+ principalActions.append((action_listProxyFor, "read", "write"))
+
</ins><span class="cx"> elif opt in ("--add-read-proxy", "--add-write-proxy"):
</span><span class="cx"> if "read" in opt:
</span><span class="cx"> proxyType = "read"
</span><span class="lines">@@ -240,38 +213,17 @@
</span><span class="cx"> proxyType = "write"
</span><span class="cx"> else:
</span><span class="cx"> raise AssertionError("Unknown proxy type")
</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 ("", "--remove-proxy"):
</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 ("", "--set-auto-schedule"):
- try:
- autoSchedule = booleanArgument(arg)
- except ValueError, e:
- abort(e)
-
- principalActions.append((action_setAutoSchedule, autoSchedule))
-
- elif opt in ("", "--get-auto-schedule"):
- principalActions.append((action_getAutoSchedule,))
-
</del><span class="cx"> elif opt in ("", "--set-auto-schedule-mode"):
</span><span class="cx"> try:
</span><span class="cx"> if arg not in allowedAutoScheduleModes:
</span><del>- raise ValueError("Unknown auto-schedule mode: %s" % (arg,))
- autoScheduleMode = arg
</del><ins>+ raise ValueError("Unknown auto-schedule mode: {mode}".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 ("", "--set-auto-accept-group"):
</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 ("", "--get-auto-accept-group"):
</span><span class="cx"> principalActions.append((action_getAutoAcceptGroup,))
</span><span class="cx">
</span><span class="cx"> elif opt in ("", "--set-geo"):
</span><del>- principalActions.append((action_setValue, "Geo", arg))
</del><ins>+ principalActions.append((action_setValue, u"geographicLocation", arg))
</ins><span class="cx">
</span><span class="cx"> elif opt in ("", "--get-geo"):
</span><del>- principalActions.append((action_getValue, "Geo"))
</del><ins>+ principalActions.append((action_getValue, u"geographicLocation"))
</ins><span class="cx">
</span><span class="cx"> elif opt in ("", "--set-street-address"):
</span><del>- principalActions.append((action_setValue, "StreetAddress", arg))
</del><ins>+ principalActions.append((action_setValue, u"streetAddress", arg))
</ins><span class="cx">
</span><span class="cx"> elif opt in ("", "--get-street-address"):
</span><del>- principalActions.append((action_getValue, "StreetAddress"))
</del><ins>+ principalActions.append((action_getValue, u"streetAddress"))
</ins><span class="cx">
</span><span class="cx"> elif opt in ("", "--set-address"):
</span><del>- principalActions.append((action_setValue, "AssociatedAddress", arg))
</del><ins>+ principalActions.append((action_setValue, u"associatedAddress", arg))
</ins><span class="cx">
</span><span class="cx"> elif opt in ("", "--get-address"):
</span><del>- principalActions.append((action_getValue, "AssociatedAddress"))
</del><ins>+ principalActions.append((action_getValue, u"associatedAddress"))
</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, ["locations", "resources", "addresses"])
</del><ins>+ addType = matchStrings(
+ addType,
+ [
+ "locations", "resources", "addresses", "users", "groups"
+ ]
+ )
</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, ["users", "groups",
- "locations", "resources", "addresses"])
</del><ins>+ listPrincipals = matchStrings(
+ listPrincipals,
+ ["users", "groups", "locations", "resources", "addresses"]
+ )
</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("No principals specified.")
</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("utf-8") 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("No records of type %s" % (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("Invalid principal ID: %s\n" % (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("")
</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 ("fullName", "firstName", "lastName", "emailAddresses"):
</del><ins>+ for fieldName in ("fullNames", "emailAddresses"):
</ins><span class="cx"> fields.append((fieldName, searchTerm, True, "contains"))
</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("%d matches found:" % (len(records),))
</del><ins>+ records.sort(key=operator.attrgetter('fullNames'))
+ print("{n} matches found:".format(n=len(records)))
</ins><span class="cx"> for record in records:
</span><del>- print("\n%s (%s)" % (record.fullName,
- {"users" : "User",
- "groups" : "Group",
- "locations" : "Place",
- "resources" : "Resource",
- "addresses" : "Address",
- }.get(record.recordType),
- ))
- print(" GUID: %s" % (record.guid,))
- print(" Record name(s): %s" % (", ".join(record.shortNames),))
- if record.authIDs:
- print(" Auth ID(s): %s" % (", ".join(record.authIDs),))
- if record.emailAddresses:
- print(" Email(s): %s" % (", ".join(record.emailAddresses),))
</del><ins>+ print(
+ "\n{d} ({rt})".format(
+ d=record.displayName,
+ rt=record.recordType.name
+ )
+ )
+ print(" UID: {u}".format(u=record.uid,))
+ print(
+ " Record name{plural}: {names}".format(
+ plural=("s" if len(record.shortNames) > 1 else ""),
+ names=(", ".join(record.shortNames))
+ )
+ )
+ try:
+ if record.emailAddresses:
+ print(
+ " Email{plural}: {emails}".format(
+ plural=("s" if len(record.emailAddresses) > 1 else ""),
+ emails=(", ".join(record.emailAddresses))
+ )
+ )
+ except AttributeError:
+ pass
</ins><span class="cx"> else:
</span><span class="cx"> print("No matches found")
</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("Added '%s'" % (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("UID already in use: {uid}".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("Record name already in use: {name}".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("Added '{name}'".format(name=fullNames[0]))
</ins><span class="cx">
</span><del>- directory.destroyRecord(record.recordType, guid=guid)
- print("Removed '%s' %s %s" % (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("%r on %s:" % (encodeXMLName(*qname), resource))
- print("")
- print(property.toxml())
</del><ins>+def action_removePrincipal(store, record):
+ directory = store.directoryService()
+ fullName = record.displayName
+ shortNames = ",".join(record.shortNames)
</ins><span class="cx">
</span><ins>+ yield directory.removeRecords([record.uid])
+ print(
+ "Removed '{full}' {shorts} {uid}".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("No %s proxies for %s" % (proxyType,
- prettyPrincipal(principal)))
- continue
</del><span class="cx">
</span><del>- membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
</del><ins>+ groupRecordType = {
+ "read": directory.recordType.readDelegateGroup,
+ "write": 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("%s proxies for %s:" % (
</span><span class="cx"> {"read": "Read-only", "write": "Read/write"}[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("")
</ins><span class="cx"> else:
</span><del>- print("No %s proxies for %s" % (proxyType,
- prettyPrincipal(principal)))
</del><ins>+ print("No %s proxies for %s" % (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 = {
+ "read": directory.recordType.readDelegatorGroup,
+ "write": directory.recordType.writeDelegatorGroup,
+ }.get(proxyType)
+
+ pseudoGroup = yield directory.recordWithShortName(
+ groupRecordType,
+ record.uid
+ )
+ proxies = yield pseudoGroup.members()
+ if proxies:
+ print("%s is a %s proxy for:" % (
+ prettyRecord(record),
+ {"read": "Read-only", "write": "Read/write"}[proxyType]
+ ))
+ printRecordList(proxies)
+ print("")
+ else:
+ print(
+ "{r} is not a {t} proxy for anyone".format(
+ r=prettyRecord(record),
+ t={"read": "Read-only", "write": "Read/write"}[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 == "write")
</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("Invalid principal ID: %s" % (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(
+ "{msg} {proxy} as a {proxyType} proxy for {record}".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("Added", 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("Removed", removeDelegate, store, record, "write", *proxyIDs)
+ # Read
+ yield _addRemoveProxy("Removed", removeDelegate, store, record, "read", *proxyIDs)
+
+
+
+@inlineCallbacks
+def setProxies(record, readProxyRecords, writeProxyRecords):
</ins><span class="cx"> """
</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"> """
</span><span class="cx">
</span><span class="cx"> proxyTypes = [
</span><del>- ("read", readProxyPrincipals),
- ("write", 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("Unable to edit %s proxies for %s\n" % (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"> """
</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"> """
</span><span class="cx">
</span><del>- proxies = {
- "read" : [],
- "write" : [],
</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("Invalid principal ID: %s" % (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(
+ "Auto-schedule mode for {record} is {mode}".format(
+ record=prettyRecord(record),
+ mode=(
+ record.autoScheduleMode.description if record.autoScheduleMode
+ else "Default"
+ )
+ )
+ )
</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 == "groups":
- print("Enabling auto-schedule for %s is not allowed." % (principal,))
</del><ins>+def action_setAutoScheduleMode(store, record, autoScheduleMode):
+ if record.recordType == RecordType.group:
+ print(
+ "Setting auto-schedule-mode for {record} is not allowed.".format(
+ record=prettyRecord(record)
+ )
+ )
</ins><span class="cx">
</span><del>- elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
- print("Enabling auto-schedule for %s is not allowed." % (principal,))
</del><ins>+ elif (
+ record.recordType == RecordType.user and
+ not config.Scheduling.Options.AutoSchedule.AllowUsers
+ ):
+ print(
+ "Setting auto-schedule-mode for {record} is not allowed.".format(
+ record=prettyRecord(record)
+ )
+ )
</ins><span class="cx">
</span><span class="cx"> else:
</span><del>- print("Setting auto-schedule to %s for %s" % (
- {True: "true", False: "false"}[autoSchedule],
- prettyPrincipal(principal),
- ))
</del><ins>+ print(
+ "Setting auto-schedule-mode to {mode} for {record}".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("Auto-schedule for %s is %s" % (
- prettyPrincipal(principal),
- {True: "true", False: "false"}[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 == "groups":
- print("Setting auto-schedule mode for %s is not allowed." % (principal,))
</del><ins>+def action_setAutoAcceptGroup(store, record, autoAcceptGroup):
+ if record.recordType == RecordType.group:
+ print(
+ "Setting auto-accept-group for {record} is not allowed.".format(
+ record=prettyRecord(record)
+ )
+ )
</ins><span class="cx">
</span><del>- elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
- print("Setting auto-schedule mode for %s is not allowed." % (principal,))
</del><ins>+ elif (
+ record.recordType == RecordType.user and
+ not config.Scheduling.Options.AutoSchedule.AllowUsers
+ ):
+ print(
+ "Setting auto-accept-group for {record} is not allowed.".format(
+ record=prettyRecord(record)
+ )
+ )
</ins><span class="cx">
</span><span class="cx"> else:
</span><del>- print("Setting auto-schedule mode to %s for %s" % (
- autoScheduleMode,
- prettyPrincipal(principal),
- ))
</del><ins>+ groupRecord = yield recordForPrincipalID(record.service, autoAcceptGroup)
+ if groupRecord is None or groupRecord.recordType != RecordType.group:
+ print("Invalid principal ID: {id}".format(id=autoAcceptGroup))
+ else:
+ print("Setting auto-accept-group to {group} for {record}".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 = "automatic"
- print("Auto-schedule mode for %s is %s" % (
- 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 == "groups":
- print("Setting auto-accept-group for %s is not allowed." % (principal,))
-
- elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
- print("Setting auto-accept-group for %s is not allowed." % (principal,))
-
- else:
- groupPrincipal = principalForPrincipalID(autoAcceptGroup, directory=directory)
- if groupPrincipal is None or groupPrincipal.record.recordType != "groups":
- print("Invalid principal ID: %s" % (autoAcceptGroup,))
</del><ins>+def action_getAutoAcceptGroup(store, record):
+ if record.autoAcceptGroup:
+ groupRecord = yield record.service.recordWithUID(
+ record.autoAcceptGroup
+ )
+ if groupRecord is not None:
+ print(
+ "Auto-accept-group for {record} is {group}".format(
+ record=prettyRecord(record),
+ group=prettyRecord(groupRecord),
+ )
+ )
</ins><span class="cx"> else:
</span><del>- print("Setting auto-accept-group to %s for %s" % (
- 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("Auto-accept-group for %s is %s" % (
- prettyPrincipal(principal),
- prettyPrincipal(groupPrincipal),
- ))
- return
- print("Invalid auto-accept-group assigned: %s" % (autoAcceptGroup,))
</del><ins>+ print(
+ "Invalid auto-accept-group assigned: {uid}".format(
+ uid=record.autoAcceptGroup
+ )
+ )
</ins><span class="cx"> else:
</span><del>- print("No auto-accept-group assigned to %s" % (prettyPrincipal(principal),))
</del><ins>+ print(
+ "No auto-accept-group assigned to {record}".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("Setting %s to %s for %s" % (
- name, value, prettyPrincipal(principal),
- ))
</del><ins>+def action_setValue(store, record, name, value):
+ print(
+ "Setting {name} to {value} for {record}".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]["attr"]] = 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("%s for %s is %s" % (
- name,
- prettyPrincipal(principal),
- principal.record.extras[attrMap[name]["attr"]]
- ))
</del><ins>+def action_getValue(store, record, name):
+ try:
+ value = record.fields[record.service.fieldName.lookupByName(name)]
+ print(
+ "{name} for {record} is {value}".format(
+ name=name, record=prettyRecord(record), value=value
+ )
+ )
+ except KeyError:
+ print(
+ "{name} is not set for {record}".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("%s\n" % (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"> """
</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"> """
</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(
+ "When adding a principal, you must provide full-name, record-name, "
+ "and UID"
+ )
+ 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("Invalid value for guid")
</del><ins>+ fullName = args[0].decode("utf-8")
+ shortName = args[1].decode("utf-8")
+ uid = args[2].decode("utf-8")
</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 = "%-22s %-17s %s"
- print(format % ("Full name", "Record name", "UUID"))
- print(format % ("---------", "-----------", "----"))
- for fullName, shortName, guid in results:
- print(format % (fullName, shortName, guid))
</del><ins>+ format = "%-22s %-10s %-20s %s"
+ print(format % ("Full name", "Type", "UID", "Short names"))
+ print(format % ("---------", "----", "---", "-----------"))
+ for fullName, recordType, uid, shortNames in results:
+ print(format % (fullName, recordType, uid, u", ".join(shortNames)))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-@inlineCallbacks
-def updateRecord(create, directory, recordType, **kwargs):
- """
- 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.
- """
</del><span class="cx">
</span><del>- assignAutoSchedule = False
- if "autoSchedule" in kwargs:
- assignAutoSchedule = True
- autoSchedule = kwargs["autoSchedule"]
- del kwargs["autoSchedule"]
- elif create:
- assignAutoSchedule = True
- autoSchedule = recordType in ("locations", "resources")
-
- assignAutoScheduleMode = False
- if "autoScheduleMode" in kwargs:
- assignAutoScheduleMode = True
- autoScheduleMode = kwargs["autoScheduleMode"]
- del kwargs["autoScheduleMode"]
- elif create:
- assignAutoScheduleMode = True
- autoScheduleMode = None
-
- assignAutoAcceptGroup = False
- if "autoAcceptGroup" in kwargs:
- assignAutoAcceptGroup = True
- autoAcceptGroup = kwargs["autoAcceptGroup"]
- del kwargs["autoAcceptGroup"]
- elif create:
- assignAutoAcceptGroup = True
- autoAcceptGroup = None
-
- for key, value in kwargs.items():
- if isinstance(value, unicode):
- kwargs[key] = value.encode("utf-8")
- elif isinstance(value, list):
- newValue = [v.encode("utf-8") 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["guid"])
- 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__ == "__main__":
</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("(Dry run) Searching for old events...")
</span><span class="cx"> txn = self.store.newTransaction(label="Find old events")
</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="Remove old events")
</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("(Dry run) Searching for orphaned attachments...")
</span><span class="cx"> txn = self.store.newTransaction(label="Find orphaned attachments")
</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("(Dry run) Searching for old dropbox attachments...")
</span><span class="cx"> txn = self.store.newTransaction(label="Find old dropbox attachments")
</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("(Dry run) Searching for old managed attachments...")
</span><span class="cx"> txn = self.store.newTransaction(label="Find old managed attachments")
</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="Remove orphaned attachments")
</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="Remove old dropbox attachments")
</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="Remove old managed attachments")
</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("Modified or deleted %s" % (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, "users", uid, shortNames=(uid,), enabledForCalendaring=True)
- self.directory._tmpRecords["shortNames"][uid] = record
- self.directory._tmpRecords["uids"][uid] = record
</del><ins>+ # record = DirectoryRecord(self.directory, "users", uid, shortNames=(uid,), enabledForCalendaring=True)
+ # self.directory._tmpRecords["shortNames"][uid] = record
+ # self.directory._tmpRecords["uids"][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 = "urn:uuid:%s" % (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 "completely" 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("Deleting any proxy assignments")
</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("Unsharing: %s" % (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 = "/calendars/__uids__/%s/%s/%s" % (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("Deleting calendar home")
</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("Deleting: %s" % (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("Deleting addressbook: %s" % (abName,))
</span><span class="cx"> if not self.dryrun:
</span><span class="cx"> # Also remove the addressbook collection itself
</span><ins>+<<<<<<< .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())
+>>>>>>> .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("Deleting addressbook home")
</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 ("read", "write"):
-
- proxyFor = (yield principal.proxyFor(proxyType == "write"))
- 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("calendar-proxy-" + proxyType)
- proxies = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
- for other in proxies.children:
- assignments.append((str(other).split("/")[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("users", user)
</del><ins>+ record = yield directory.recordWithShortName(RecordType.user, user)
</ins><span class="cx"> if record is not None:
</span><span class="cx"> print("User %s (%s)..." % (user, record.uid))
</span><span class="cx"> txn = store.newTransaction(label="Display APN Subscriptions")
</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("...is subscribed to a share from %s's %s home" % (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__ = [
+ "migrateResources",
+]
+
+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__ = [
- "migrateResources",
-]
-
-
-
</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("users")
</span><span class="cx"> resourceService = config.directory.serviceForRecordType("resources")
</span><del>- if (not isinstance(userService, OpenDirectoryService) or
- not isinstance(resourceService, XMLDirectoryService)):
- abort("This script only migrates resources and locations from OpenDirectory to XML; this calendar server does not have such a configuration.")
</del><ins>+ if (
+ not isinstance(userService, OpenDirectoryService) or
+ not isinstance(resourceService, XMLDirectoryService)
+ ):
+ abort(
+ "This script only migrates resources and locations from "
+ "OpenDirectory to XML; this calendar server does not have such a "
+ "configuration."
+ )
</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"> """
</span><span class="cx">
</span><span class="cx"> attrs = [
</span><del>- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDS1AttrDistinguishedName,
</del><ins>+ "dsAttrTypeStandard:GeneratedUID",
+ "dsAttrTypeStandard:RealName",
</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>+ ("dsRecTypeStandard:Resources", DirectoryService.recordType_resources),
+ ("dsRecTypeStandard:Places", 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("dsAttrTypeStandard:GeneratedUID", None)
+ fullName = val.get("dsAttrTypeStandard:RealName", 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("Migrating %s (%s)" % (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,
- {
- "guid" : guid,
- "shortNames" : [recordName],
- "fullName" : fullName,
- }
</del><ins>+ augmentRecord = (
+ yield destService.augmentService.getAugmentRecord(
+ guid, recordType
</ins><span class="cx"> )
</span><span class="cx"> )
</span><ins>+ if autoSchedule:
+ augmentRecord.autoScheduleMode = "automatic"
+ else:
+ augmentRecord.autoScheduleMode = "none"
+ augmentRecords.append(augmentRecord)
</ins><span class="cx">
</span><ins>+ directoryRecords.append((
+ recordType,
+ {
+ "guid": guid,
+ "shortNames": [recordName],
+ "fullName": 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("First Name", record.firstName)
</span><span class="cx"> add("Last Name" , record.lastName )
</span><span class="cx">
</span><del>- for email in record.emailAddresses:
- add("Email Address", email)
</del><ins>+ try:
+ for email in record.emailAddresses:
+ add("Email Address", email)
+ except AttributeError:
+ pass
</ins><span class="cx">
</span><del>- for cua in record.calendarUserAddresses:
- add("Calendar User Address", cua)
</del><ins>+ try:
+ for cua in record.calendarUserAddresses:
+ add("Calendar User Address", cua)
+ except AttributeError:
+ pass
</ins><span class="cx">
</span><span class="cx"> add("Server ID" , record.serverID)
</span><span class="cx"> add("Enabled" , record.enabled)
</span><del>- add("Enabled for Calendar", record.enabledForCalendaring)
- add("Enabled for Contacts", record.enabledForAddressBooks)
</del><ins>+ add("Enabled for Calendar", record.hasCalendars)
+ add("Enabled for Contacts", 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"> """
</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("Initializing shell...")
</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 , "thingo").toString(), "thingo")
- self.assertEquals(ListEntry(None, File , "thingo", Foo="foo").toString(), "thingo")
- self.assertEquals(ListEntry(None, Folder, "thingo").toString(), "thingo/")
- self.assertEquals(ListEntry(None, Folder, "thingo", Foo="foo").toString(), "thingo/")
</del><ins>+ self.assertEquals(
+ ListEntry(None, File, "thingo").toString(),
+ "thingo"
+ )
+ self.assertEquals(
+ ListEntry(None, File, "thingo", Foo="foo").toString(),
+ "thingo"
+ )
+ self.assertEquals(
+ ListEntry(None, Folder, "thingo").toString(),
+ "thingo/"
+ )
+ self.assertEquals(
+ ListEntry(None, Folder, "thingo", Foo="foo").toString(),
+ "thingo/"
+ )
</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, "fieldNames")
</span><span class="cx">
</span><del>- self.assertEquals(set(ListEntry(File(None, ()), File, "thingo").fieldNames), set(("Name",)))
</del><ins>+ self.assertEquals(
+ set(ListEntry(File(None, ()), File, "thingo").fieldNames),
+ set(("Name",))
+ )
</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, "thingo", Flavor="Coconut", Style="Hard")
</del><ins>+ return ListEntry(
+ fileClass(None, ()), fileClass, "thingo",
+ Flavor="Coconut", Style="Hard"
+ )
</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, "thingo", Flavor="Coconut", Style="Hard").toFields()),
</del><ins>+ tuple(
+ ListEntry(
+ File(None, ()), File, "thingo",
+ Flavor="Coconut", Style="Hard"
+ ).toFields()
+ ),
</ins><span class="cx"> ("thingo", "Coconut", "Hard")
</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, "thingo", Flavor="Coconut", Style="Hard").toFields())
</del><ins>+ return tuple(
+ ListEntry(
+ fileClass(None, ()), fileClass, "thingo",
+ Flavor="Coconut", Style="Hard"
+ ).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):
- """
- Object which creates a stub L{IDirectoryService}.
- """
- 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"> """
</span><span class="cx"> L{UIDsFolder} contains all principals and is keyed by UID.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- @inlineCallbacks
- def setUp(self):
- """
- Create a L{UIDsFolder}.
- """
- 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):
+ # """
+ # Create a L{UIDsFolder}.
+ # """
+ # 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>- [{"Record Type": "users", "Short Name": "wsanchez",
- "Full Name": "Wilfredo Sanchez", "Name": wsanchez},
- {"Record Type": "users", "Short Name": "dreid",
- "Full Name": "David Reid", "Name": dreid}]
</del><ins>+ [
+ {
+ "Record Type": "users",
+ "Short Name": "wsanchez",
+ "Full Name": "Wilfredo Sanchez",
+ "Name": wsanchez
+ },
+ {
+ "Record Type": "users",
+ "Short Name": "dreid",
+ "Full Name": "David Reid",
+ "Name": dreid
+ },
+ ]
</ins><span class="cx"> )
</span><ins>+
+ test_list.todo = "setup() needs to be reimplemented"
</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"> <augments>
</span><span class="cx"> <record>
</span><del>- <uid>Default</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- </record>
- <record>
</del><span class="cx"> <uid>user01</uid>
</span><span class="cx"> <enable>true</enable>
</span><span class="cx"> <enable-calendar>true</enable-calendar>
</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"> <key>ServerRoot</key>
</span><span class="cx"> <string>%(ServerRoot)s</string>
</span><span class="cx">
</span><ins>+ <!-- Data root -->
+ <key>DataRoot</key>
+ <string>%(DataRoot)s</string>
+
</ins><span class="cx"> <!-- Database root -->
</span><span class="cx"> <key>DatabaseRoot</key>
</span><span class="cx"> <string>%(DatabaseRoot)s</string>
</span><span class="cx">
</span><del>- <!-- Data root -->
- <key>DataRoot</key>
- <string>Data</string>
-
</del><span class="cx"> <!-- Document root -->
</span><span class="cx"> <key>DocumentRoot</key>
</span><del>- <string>Documents</string>
</del><ins>+ <string>%(DocumentRoot)s</string>
</ins><span class="cx">
</span><span class="cx"> <!-- Configuration root -->
</span><span class="cx"> <key>ConfigRoot</key>
</span><del>- <string>config</string>
</del><ins>+ <string>%(ConfigRoot)s</string>
</ins><span class="cx">
</span><span class="cx"> <!-- Log root -->
</span><span class="cx"> <key>LogRoot</key>
</span><del>- <string>Logs</string>
</del><ins>+ <string>%(LogRoot)s</string>
</ins><span class="cx">
</span><span class="cx"> <!-- Run root -->
</span><span class="cx"> <key>RunRoot</key>
</span><del>- <string>Logs/state</string>
</del><ins>+ <string>%(RunRoot)s</string>
</ins><span class="cx">
</span><span class="cx"> <!-- Child aliases -->
</span><span class="cx"> <key>Aliases</key>
</span><span class="lines">@@ -147,7 +147,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -167,7 +167,7 @@
</span><span class="cx"> <true/>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -180,14 +180,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Open Directory Service (Mac OS X) -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>DirectoryService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>node</key>
</span><span class="lines">@@ -211,7 +211,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFiles</key>
</span><span class="lines">@@ -220,14 +220,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Sqlite Augment Service -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>AugmentService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -242,7 +242,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -258,7 +258,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -272,7 +272,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -515,6 +515,65 @@
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><span class="cx">
</span><ins>+ <key>SimpleLineNotifier</key>
+ <dict>
+ <!-- Simple line notification service (for testing) -->
+ <key>Service</key>
+ <string>twistedcaldav.notify.SimpleLineNotifierService</string>
+ <key>Enabled</key>
+ <false/>
+ <key>Port</key>
+ <integer>62308</integer>
+ </dict>
+
+ <key>XMPPNotifier</key>
+ <dict>
+ <!-- XMPP notification service -->
+ <key>Service</key>
+ <string>twistedcaldav.notify.XMPPNotifierService</string>
+ <key>Enabled</key>
+ <false/>
+
+ <!-- XMPP host and port to contact -->
+ <key>Host</key>
+ <string>xmpp.host.name</string>
+ <key>Port</key>
+ <integer>5222</integer>
+
+ <!-- Jabber ID and password for the server -->
+ <key>JID</key>
+ <string>jid@xmpp.host.name/resource</string>
+ <key>Password</key>
+ <string>password_goes_here</string>
+
+ <!-- PubSub service address -->
+ <key>ServiceAddress</key>
+ <string>pubsub.xmpp.host.name</string>
+
+ <key>NodeConfiguration</key>
+ <dict>
+ <key>pubsub#deliver_payloads</key>
+ <string>1</string>
+ <key>pubsub#persist_items</key>
+ <string>1</string>
+ </dict>
+
+ <!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) -->
+ <key>KeepAliveSeconds</key>
+ <integer>120</integer>
+
+ <!-- Sends a pubsub publish to a particular heartbeat node at this interval -->
+ <key>HeartbeatMinutes</key>
+ <integer>30</integer>
+
+ <!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) -->
+ <key>AllowedJIDs</key>
+ <array>
+ <!--
+ <string>*.example.com</string>
+ -->
+ </array>
+ </dict>
</ins><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><span class="cx">
</span><span class="lines">@@ -651,6 +710,7 @@
</span><span class="cx">         <key>UsePackageTimezones</key>
</span><span class="cx">         <true/>
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> <!--
</span><span class="cx"> Miscellaneous items
</span><span class="cx"> -->
</span><span class="lines">@@ -666,7 +726,7 @@
</span><span class="cx"> <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
</span><span class="cx"> <key>ResponseCompression</key>
</span><span class="cx"> <false/>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- The retry-after value (in seconds) to return with a 503 error. -->
</span><span class="cx"> <key>HTTPRetryAfter</key>
</span><span class="cx"> <integer>180</integer>
</span><span class="lines">@@ -705,7 +765,6 @@
</span><span class="cx"> <key>ResponseCacheTimeout</key>
</span><span class="cx"> <integer>30</integer> <!-- in minutes -->
</span><span class="cx">
</span><del>-
</del><span class="cx"> <!-- For unit tests, enable SharedConnectionPool so we don't use up shared memory -->
</span><span class="cx"> <key>SharedConnectionPool</key>
</span><span class="cx"> <true/>
</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"> <!DOCTYPE accounts SYSTEM "accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test Realm">
</del><ins>+<directory realm="Test Realm">
+ <record type="location">
+ <short-name>location01</short-name>
+ <uid>location01</uid>
+ <full-name>Room 01</full-name>
+ </record>
+ <record type="location">
+ <short-name>location02</short-name>
+ <uid>location02</uid>
+ <full-name>Room 02</full-name>
+ </record>
+ <record type="location">
+ <short-name>location03</short-name>
+ <uid>location03</uid>
+ <full-name>Room 03</full-name>
+ </record>
+ <record type="location">
+ <short-name>location04</short-name>
+ <uid>location04</uid>
+ <full-name>Room 04</full-name>
+ </record>
+ <record type="location">
+ <short-name>location05</short-name>
+ <uid>location05</uid>
+ <full-name>Room 05</full-name>
+ </record>
+ <record type="location">
+ <short-name>location06</short-name>
+ <uid>location06</uid>
+ <full-name>Room 06</full-name>
+ </record>
+ <record type="location">
+ <short-name>location07</short-name>
+ <uid>location07</uid>
+ <full-name>Room 07</full-name>
+ </record>
+ <record type="location">
+ <short-name>location08</short-name>
+ <uid>location08</uid>
+ <full-name>Room 08</full-name>
+ </record>
+ <record type="location">
+ <short-name>location09</short-name>
+ <uid>location09</uid>
+ <full-name>Room 09</full-name>
+ </record>
+ <record type="location">
+ <short-name>location10</short-name>
+ <uid>location10</uid>
+ <full-name>Room 10</full-name>
+ </record>
+
+ <record type="resource">
+ <short-name>resource01</short-name>
+ <uid>resource01</uid>
+ <full-name>Resource 01</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource02</short-name>
+ <uid>resource02</uid>
+ <full-name>Resource 02</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource03</short-name>
+ <uid>resource03</uid>
+ <full-name>Resource 03</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource04</short-name>
+ <uid>resource04</uid>
+ <full-name>Resource 04</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource05</short-name>
+ <uid>resource05</uid>
+ <full-name>Resource 05</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource06</short-name>
+ <uid>resource06</uid>
+ <full-name>Resource 06</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource07</short-name>
+ <uid>resource07</uid>
+ <full-name>Resource 07</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource08</short-name>
+ <uid>resource08</uid>
+ <full-name>Resource 08</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource09</short-name>
+ <uid>resource09</uid>
+ <full-name>Resource 09</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource10</short-name>
+ <uid>resource10</uid>
+ <full-name>Resource 10</full-name>
+ </record>
+
+ <!--
</ins><span class="cx"> <location repeat="10">
</span><span class="cx"> <uid>location%02d</uid>
</span><span class="cx"> <guid>location%02d</guid>
</span><span class="lines">@@ -31,4 +134,5 @@
</span><span class="cx"> <password>resource%02d</password>
</span><span class="cx"> <name>Resource %02d</name>
</span><span class="cx"> </resource>
</span><del>-</accounts>
</del><ins>+-->
+</directory>
</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"> <!DOCTYPE accounts SYSTEM "accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test Realm">
</del><ins>+<directory realm="Test Realm">
+ <record type="user">
+ <short-name>user01</short-name>
+ <uid>user01</uid>
+ <password>user01</password>
+ <full-name>User 01</full-name>
+ <email>user01@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user02</short-name>
+ <uid>user02</uid>
+ <password>user02</password>
+ <full-name>User 02</full-name>
+ <email>user02@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user03</short-name>
+ <uid>user03</uid>
+ <password>user03</password>
+ <full-name>User 03</full-name>
+ <email>user03@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user04</short-name>
+ <uid>user04</uid>
+ <password>user04</password>
+ <full-name>User 04</full-name>
+ <email>user04@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user05</short-name>
+ <uid>user05</uid>
+ <password>user05</password>
+ <full-name>User 05</full-name>
+ <email>user05@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user06</short-name>
+ <uid>user06</uid>
+ <password>user06</password>
+ <full-name>User 06</full-name>
+ <email>user06@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user07</short-name>
+ <uid>user07</uid>
+ <password>user07</password>
+ <full-name>User 07</full-name>
+ <email>user07@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user08</short-name>
+ <uid>user08</uid>
+ <password>user08</password>
+ <full-name>User 08</full-name>
+ <email>user08@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user09</short-name>
+ <uid>user09</uid>
+ <password>user09</password>
+ <full-name>User 09</full-name>
+ <email>user09@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user10</short-name>
+ <uid>user10</uid>
+ <password>user10</password>
+ <full-name>User 10</full-name>
+ <email>user10@example.com</email>
+ </record>
+
+ <record type="group">
+ <uid>e5a6142c-4189-4e9e-90b0-9cd0268b314b</uid>
+ <short-name>testgroup1</short-name>
+ <full-name>Group 01</full-name>
+ <member-uid type="users">user01</member-uid>
+ <member-uid type="users">user02</member-uid>
+ </record>
+ <!--
</ins><span class="cx"> <user repeat="10">
</span><span class="cx"> <uid>user%02d</uid>
</span><span class="cx"> <guid>user%02d</guid>
</span><span class="lines">@@ -37,13 +125,5 @@
</span><span class="cx"> <member type="users">user02</member>
</span><span class="cx"> </members>
</span><span class="cx"> </group>
</span><del>- <group>
- <uid>testgroup2</uid>
- <guid>f5a6142c-4189-4e9e-90b0-9cd0268b314b</guid>
- <password>test</password>
- <name>Group 02</name>
- <members>
- <member type="users">user01</member>
- </members>
- </group>
-</accounts>
</del><ins>+ -->
+</directory>
</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"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -159,7 +159,7 @@
</span><span class="cx"> <true/>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFile</key>
</span><span class="lines">@@ -168,17 +168,18 @@
</span><span class="cx"> <array>
</span><span class="cx"> <string>resources</string>
</span><span class="cx"> <string>locations</string>
</span><ins>+ <string>addresses</string>
</ins><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Open Directory Service (Mac OS X) -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>DirectoryService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>node</key>
</span><span class="lines">@@ -202,7 +203,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>xmlFiles</key>
</span><span class="lines">@@ -211,14 +212,14 @@
</span><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </dict>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- Sqlite Augment Service -->
</span><span class="cx"> <!--
</span><span class="cx"> <key>AugmentService</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -233,7 +234,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -249,7 +250,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>dbpath</key>
</span><span class="lines">@@ -263,7 +264,7 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>type</key>
</span><span class="cx"> <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
</span><del>-
</del><ins>+
</ins><span class="cx"> <key>params</key>
</span><span class="cx"> <dict>
</span><span class="cx"> <key>host</key>
</span><span class="lines">@@ -692,7 +693,7 @@
</span><span class="cx"> <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
</span><span class="cx"> <key>ResponseCompression</key>
</span><span class="cx"> <false/>
</span><del>-
</del><ins>+
</ins><span class="cx"> <!-- The retry-after value (in seconds) to return with a 503 error. -->
</span><span class="cx"> <key>HTTPRetryAfter</key>
</span><span class="cx"> <integer>180</integer>
</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"> <!DOCTYPE accounts SYSTEM "accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test Realm">
</del><ins>+<directory realm="Test Realm">
+ <record type="location">
+ <short-name>location01</short-name>
+ <uid>location01</uid>
+ <full-name>Room 01</full-name>
+ </record>
+ <record type="location">
+ <short-name>location02</short-name>
+ <uid>location02</uid>
+ <full-name>Room 02</full-name>
+ </record>
+ <record type="location">
+ <short-name>location03</short-name>
+ <uid>location03</uid>
+ <full-name>Room 03</full-name>
+ </record>
+ <record type="location">
+ <short-name>location04</short-name>
+ <uid>location04</uid>
+ <full-name>Room 04</full-name>
+ </record>
+ <record type="location">
+ <short-name>location05</short-name>
+ <uid>location05</uid>
+ <full-name>Room 05</full-name>
+ </record>
+ <record type="location">
+ <short-name>location06</short-name>
+ <uid>location06</uid>
+ <full-name>Room 06</full-name>
+ </record>
+ <record type="location">
+ <short-name>location07</short-name>
+ <uid>location07</uid>
+ <full-name>Room 07</full-name>
+ </record>
+ <record type="location">
+ <short-name>location08</short-name>
+ <uid>location08</uid>
+ <full-name>Room 08</full-name>
+ </record>
+ <record type="location">
+ <short-name>location09</short-name>
+ <uid>location09</uid>
+ <full-name>Room 09</full-name>
+ </record>
+ <record type="location">
+ <short-name>location10</short-name>
+ <uid>location10</uid>
+ <full-name>Room 10</full-name>
+ </record>
+
+ <record type="resource">
+ <short-name>resource01</short-name>
+ <uid>resource01</uid>
+ <full-name>Resource 01</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource02</short-name>
+ <uid>resource02</uid>
+ <full-name>Resource 02</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource03</short-name>
+ <uid>resource03</uid>
+ <full-name>Resource 03</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource04</short-name>
+ <uid>resource04</uid>
+ <full-name>Resource 04</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource05</short-name>
+ <uid>resource05</uid>
+ <full-name>Resource 05</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource06</short-name>
+ <uid>resource06</uid>
+ <full-name>Resource 06</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource07</short-name>
+ <uid>resource07</uid>
+ <full-name>Resource 07</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource08</short-name>
+ <uid>resource08</uid>
+ <full-name>Resource 08</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource09</short-name>
+ <uid>resource09</uid>
+ <full-name>Resource 09</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource10</short-name>
+ <uid>resource10</uid>
+ <full-name>Resource 10</full-name>
+ </record>
+
+ <!--
</ins><span class="cx"> <location repeat="10">
</span><span class="cx"> <uid>location%02d</uid>
</span><span class="cx"> <guid>location%02d</guid>
</span><span class="lines">@@ -31,4 +134,5 @@
</span><span class="cx"> <password>resource%02d</password>
</span><span class="cx"> <name>Resource %02d</name>
</span><span class="cx"> </resource>
</span><del>-</accounts>
</del><ins>+-->
+</directory>
</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"> <!DOCTYPE accounts SYSTEM "accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test Realm">
</del><ins>+<directory realm="Test Realm">
+ <record type="user">
+ <short-name>user01</short-name>
+ <uid>user01</uid>
+ <password>user01</password>
+ <full-name>User 01</full-name>
+ <email>user01@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user02</short-name>
+ <uid>user02</uid>
+ <password>user02</password>
+ <full-name>User 02</full-name>
+ <email>user02@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user03</short-name>
+ <uid>user03</uid>
+ <password>user03</password>
+ <full-name>User 03</full-name>
+ <email>user03@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user04</short-name>
+ <uid>user04</uid>
+ <password>user04</password>
+ <full-name>User 04</full-name>
+ <email>user04@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user05</short-name>
+ <uid>user05</uid>
+ <password>user05</password>
+ <full-name>User 05</full-name>
+ <email>user05@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user06</short-name>
+ <uid>user06</uid>
+ <password>user06</password>
+ <full-name>User 06</full-name>
+ <email>user06@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user07</short-name>
+ <uid>user07</uid>
+ <password>user07</password>
+ <full-name>User 07</full-name>
+ <email>user07@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user08</short-name>
+ <uid>user08</uid>
+ <password>user08</password>
+ <full-name>User 08</full-name>
+ <email>user08@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user09</short-name>
+ <uid>user09</uid>
+ <password>user09</password>
+ <full-name>User 09</full-name>
+ <email>user09@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user10</short-name>
+ <uid>user10</uid>
+ <password>user10</password>
+ <full-name>User 10</full-name>
+ <email>user10@example.com</email>
+ </record>
+
+ <record type="group">
+ <uid>e5a6142c-4189-4e9e-90b0-9cd0268b314b</uid>
+ <short-name>testgroup1</short-name>
+ <full-name>Group 01</full-name>
+ <member-uid type="users">user01</member-uid>
+ <member-uid type="users">user02</member-uid>
+ </record>
+ <!--
</ins><span class="cx"> <user repeat="10">
</span><span class="cx"> <uid>user%02d</uid>
</span><span class="cx"> <guid>user%02d</guid>
</span><span class="lines">@@ -37,4 +125,5 @@
</span><span class="cx"> <member type="users">user02</member>
</span><span class="cx"> </members>
</span><span class="cx"> </group>
</span><del>-</accounts>
</del><ins>+ -->
+</directory>
</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("md5", "/Local/Default")
- challenge = f.getChallenge(FakeRequest())
- self.assertTrue("qop" not in challenge)
- self.assertEquals(challenge["algorithm"], "md5")
- self.assertEquals(challenge["realm"], "/Local/Default")
-
- @inlineCallbacks
- def test_DirectoryServiceChecker(self):
- c = DirectoryServiceChecker("/Local/Default")
- fakeOpenDirectory = FakeOpenDirectory()
- c.directoryModule = fakeOpenDirectory
-
- fields = {
- "username" : "foo",
- "realm" : "/Local/Default",
- "nonce" : 1,
- "uri" : "/gateway",
- "response" : "abc",
- "algorithm" : "md5",
- }
- creds = FakeCredentials("foo", fields)
-
- # Record does not exist:
- fakeOpenDirectory.returnThisRecord(None)
- try:
- yield c.requestAvatarId(creds)
- except UnauthorizedLogin:
- pass
- else:
- self.fail("Didn't raise UnauthorizedLogin")
-
- # Record exists, but invalid credentials
- fakeOpenDirectory.returnThisRecord("fooRecord")
- fakeOpenDirectory.returnThisAuthResponse(False)
- try:
- yield c.requestAvatarId(creds)
- except UnauthorizedLogin:
- pass
- else:
- self.fail("Didn't raise UnauthorizedLogin")
-
- # Record exists, valid credentials
- fakeOpenDirectory.returnThisRecord("fooRecord")
- fakeOpenDirectory.returnThisAuthResponse(True)
- avatar = (yield c.requestAvatarId(creds))
- self.assertEquals(avatar, "foo")
-
- # Record exists, but missing fields in credentials
- del creds.fields["nonce"]
- fakeOpenDirectory.returnThisRecord("fooRecord")
- fakeOpenDirectory.returnThisAuthResponse(False)
- try:
- yield c.requestAvatarId(creds)
- except UnauthorizedLogin:
- pass
- else:
- self.fail("Didn't raise UnauthorizedLogin")
-
-
</del><span class="cx"> def test_AgentRealm(self):
</span><span class="cx"> realm = AgentRealm("root", ["abc"])
</span><span class="cx">
</span><span class="cx"> # Valid avatar
</span><del>- _ignore_interface, resource, ignored = realm.requestAvatar("abc", None, IResource)
</del><ins>+ _ignore_interface, resource, ignored = realm.requestAvatar(
+ "abc", None, IResource
+ )
</ins><span class="cx"> self.assertEquals(resource, "root")
</span><span class="cx">
</span><span class="cx"> # Not allowed avatar
</span><del>- _ignore_interface, resource, ignored = realm.requestAvatar("def", None, IResource)
</del><ins>+ _ignore_interface, resource, ignored = realm.requestAvatar(
+ "def", 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 = "Error"
</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 = """BEGIN:VCALENDAR
</span><span class="lines">@@ -471,20 +470,7 @@
</span><span class="cx">
</span><span class="cx"> number_to_process = len(requirements["home1"]["calendar_1"])
</span><span class="cx">
</span><del>- def configure(self):
- super(CalVerifyDataTests, self).configure()
- self.patch(config.DirectoryService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "calverify", "accounts.xml"
- )
- )
- self.patch(config.ResourceService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "calverify", "resources.xml"
- )
- )
</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 = "AC478592-7783-44D1-B2AE-52359B4E8415"
</span><span class="cx"> uuidl1 = "75EA36BE-F71B-40F9-81F9-CF59BF40CA8F"
</span><span class="cx">
</span><del>- def configure(self):
- super(CalVerifyMismatchTestsBase, self).configure()
- self.patch(config.DirectoryService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "calverify", "accounts.xml"
- )
- )
- self.patch(config.ResourceService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "calverify", "resources.xml"
- )
- )
- self.patch(config.AugmentService.params, "xmlFiles",
- [os.path.join(
- os.path.dirname(__file__), "calverify", "augments.xml"
- ), ]
- )
</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):
+ """
+ Override the standard StoreTestCase configuration
+ """
+ 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, "Config")
+ if not os.path.exists(configRoot):
+ os.makedirs(configRoot)
+
+ dataRoot = os.path.join(absoluteServerRoot, "Data")
+ if not os.path.exists(dataRoot):
+ os.makedirs(dataRoot)
+
+ documentRoot = os.path.join(absoluteServerRoot, "Documents")
+ if not os.path.exists(documentRoot):
+ os.makedirs(documentRoot)
+
+ logRoot = os.path.join(absoluteServerRoot, "Logs")
+ if not os.path.exists(logRoot):
+ os.makedirs(logRoot)
+
+ runRoot = os.path.join(absoluteServerRoot, "Run")
+ 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__), "gateway")
</span><span class="cx"> templateName = os.path.join(testRoot, "caldavd.plist")
</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("_spawned_scripts_db" + str(os.getpid()))
</span><span class="cx"> newConfig = template % {
</span><del>- "ServerRoot" : os.path.abspath(config.ServerRoot),
- "DatabaseRoot" : databaseRoot,
- "WritablePlist" : os.path.join(os.path.abspath(config.ConfigRoot), "caldavd-writable.plist"),
</del><ins>+ "ServerRoot": absoluteServerRoot,
+ "DataRoot": dataRoot,
+ "DatabaseRoot": databaseRoot,
+ "DocumentRoot": documentRoot,
+ "ConfigRoot": configRoot,
+ "LogRoot": logRoot,
+ "RunRoot": runRoot,
+ "WritablePlist": os.path.join(
+ os.path.abspath(configRoot), "caldavd-writable.plist"
+ ),
</ins><span class="cx"> }
</span><del>- configFilePath = FilePath(os.path.join(config.ConfigRoot, "caldavd.plist"))
</del><ins>+ configFilePath = FilePath(
+ os.path.join(configRoot, "caldavd.plist")
+ )
+
</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__),
- "gateway", "users-groups.xml"))
- copyUsersFile = FilePath(os.path.join(config.DataRoot, "accounts.xml"))
</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__),
+ "gateway",
+ "users-groups.xml"
+ )
+ )
+ copyUsersFile = FilePath(
+ os.path.join(config.DataRoot, "accounts.xml")
+ )
</ins><span class="cx"> origUsersFile.copyTo(copyUsersFile)
</span><span class="cx">
</span><del>- origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
- "gateway", "resources-locations.xml"))
- copyResourcesFile = FilePath(os.path.join(config.DataRoot, "resources.xml"))
</del><ins>+ origResourcesFile = FilePath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "gateway",
+ "resources-locations.xml"
+ )
+ )
+ copyResourcesFile = FilePath(
+ os.path.join(config.DataRoot, "resources.xml")
+ )
</ins><span class="cx"> origResourcesFile.copyTo(copyResourcesFile)
</span><span class="cx">
</span><del>- origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
- "gateway", "augments.xml"))
</del><ins>+ origAugmentFile = FilePath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "gateway",
+ "augments.xml"
+ )
+ )
</ins><span class="cx"> copyAugmentFile = FilePath(os.path.join(config.DataRoot, "augments.xml"))
</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="calendarserver_command_gateway"):
</del><ins>+ def runCommand(
+ self, command, error=False, script="calendarserver_command_gateway"
+ ):
</ins><span class="cx"> """
</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("utf-8")
</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, "bin", script)
</span><span class="cx">
</span><span class="cx"> args = [cmd, "-f", 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["result"]["Capacity"], "40")
- self.assertEquals(results["result"]["Description"], "Test Description")
</del><ins>+ # self.assertEquals(results["result"]["Capacity"], "40")
+ # self.assertEquals(results["result"]["Description"], "Test Description")
</ins><span class="cx"> self.assertEquals(results["result"]["RecordName"], ["createdlocation01"])
</span><span class="cx"> self.assertEquals(results["result"]["RealName"],
</span><span class="cx"> "Created Location 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
</span><del>- self.assertEquals(results["result"]["Comment"], "Test Comment")
- self.assertEquals(results["result"]["AutoSchedule"], True)
</del><ins>+ # self.assertEquals(results["result"]["Comment"], "Test Comment")
+ self.assertEquals(results["result"]["AutoScheduleMode"], u"acceptIfFree")
</ins><span class="cx"> self.assertEquals(results["result"]["AutoAcceptGroup"], "E5A6142C-4189-4E9E-90B0-9CD0268B314B")
</span><span class="cx"> self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03', 'user04']))
</span><span class="cx"> self.assertEquals(set(results["result"]["WriteProxies"]), 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["result"]["Comment"], "Test Comment")
- self.assertEquals(results["result"]["Type"], "Computer")
</del><ins>+ # self.assertEquals(results["result"]["Comment"], "Test Comment")
+ # self.assertEquals(results["result"]["Type"], "Computer")
</ins><span class="cx"> self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03', 'user04']))
</span><span class="cx"> self.assertEquals(set(results["result"]["WriteProxies"]), 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("C701069D-9CA1-4925-A1A9-5CD94767B74B")
</del><ins>+ record = yield self.directory.recordWithUID("C701069D-9CA1-4925-A1A9-5CD94767B74B")
</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("C701069D-9CA1-4925-A1A9-5CD94767B74B")
- self.assertEquals(record.fullName.decode("utf-8"),
- "Created Address 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
</del><ins>+ record = yield self.directory.recordWithUID("C701069D-9CA1-4925-A1A9-5CD94767B74B")
+ self.assertEquals(
+ record.displayName,
+ "Created Address 01 %s %s" % (unichr(208), u"\ud83d\udca3")
+ )
</ins><span class="cx">
</span><del>- self.assertNotEquals(record, None)
</del><span class="cx">
</span><del>- self.assertEquals(record.extras["abbreviatedName"], "Addr1")
- self.assertEquals(record.extras["streetAddress"], "1 Infinite Loop\nCupertino, 95014\nCA")
- self.assertEquals(record.extras["geo"], "geo:37.331,-122.030")
</del><ins>+ self.assertEquals(record.abbreviatedName, "Addr1")
+ self.assertEquals(record.streetAddress, "1 Infinite Loop\nCupertino, 95014\nCA")
+ self.assertEquals(record.geographicLocation, "geo:37.331,-122.030")
</ins><span class="cx">
</span><span class="cx"> results = yield self.runCommand(command_getAddressList)
</span><span class="cx"> self.assertEquals(len(results["result"]), 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["result"]["RealName"], u'Updated Address')
</span><span class="cx"> self.assertEquals(results["result"]["StreetAddress"], u'Updated Street Address')
</span><del>- self.assertEquals(results["result"]["Geo"], u'Updated Geo')
</del><ins>+ self.assertEquals(results["result"]["GeographicLocation"], 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("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
</del><ins>+ record = yield self.directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
</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("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
+ self.assertEquals(record.fullNames[0],
+ u"Created Location 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
</ins><span class="cx">
</span><del>- record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
- self.assertEquals(record.fullName.decode("utf-8"),
- "Created Location 01 %s %s" % (unichr(208), u"\ud83d\udca3"))
-
</del><span class="cx"> self.assertNotEquals(record, None)
</span><del>- self.assertEquals(record.autoSchedule, True)
</del><ins>+ # self.assertEquals(record.autoScheduleMode, "")
</ins><span class="cx">
</span><del>- self.assertEquals(record.extras["comment"], "Test Comment")
- self.assertEquals(record.extras["floor"], "First")
- self.assertEquals(record.extras["capacity"], "40")
</del><ins>+ self.assertEquals(record.floor, u"First")
+ # self.assertEquals(record.extras["capacity"], "40")
</ins><span class="cx">
</span><span class="cx"> results = yield self.runCommand(command_getLocationAttributes)
</span><span class="cx"> self.assertEquals(set(results["result"]["ReadProxies"]), 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("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
</del><ins>+ record = yield self.directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
</ins><span class="cx">
</span><del>- self.assertEquals(record.extras["comment"], "Updated Test Comment")
- self.assertEquals(record.extras["floor"], "Second")
- self.assertEquals(record.extras["capacity"], "41")
- self.assertEquals(record.extras["streetAddress"], "2 Infinite Loop\nCupertino, 95014\nCA")
- self.assertEquals(record.autoSchedule, True)
</del><ins>+ # self.assertEquals(record.extras["comment"], "Updated Test Comment")
+ self.assertEquals(record.floor, "Second")
+ # self.assertEquals(record.extras["capacity"], "41")
+ self.assertEquals(record.autoScheduleMode, AutoScheduleMode.acceptIfFree)
</ins><span class="cx"> self.assertEquals(record.autoAcceptGroup, "F5A6142C-4189-4E9E-90B0-9CD0268B314B")
</span><span class="cx">
</span><span class="cx"> results = yield self.runCommand(command_getLocationAttributes)
</span><del>- self.assertEquals(results["result"]["AutoSchedule"], True)
</del><ins>+ self.assertEquals(results["result"]["AutoScheduleMode"], "acceptIfFree")
</ins><span class="cx"> self.assertEquals(results["result"]["AutoAcceptGroup"], "F5A6142C-4189-4E9E-90B0-9CD0268B314B")
</span><span class="cx"> self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03']))
</span><span class="cx"> self.assertEquals(set(results["result"]["WriteProxies"]), 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["result"]["AssociatedAddress"], "C701069D-9CA1-4925-A1A9-5CD94767B74B")
+ self._flush()
+ record = yield self.directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
+ self.assertEquals(record.associatedAddress, "C701069D-9CA1-4925-A1A9-5CD94767B74B")
+ yield self.runCommand(command_removeAddressFromLocation)
+ results = yield self.runCommand(command_getLocationAttributes)
+ self.assertEquals(results["result"]["AssociatedAddress"], "")
+ self._flush()
+ record = yield self.directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
+ self.assertEquals(record.associatedAddress, u"")
+
+
+ @inlineCallbacks
</ins><span class="cx"> def test_destroyLocation(self):
</span><del>- directory = getDirectory()
</del><span class="cx">
</span><del>- record = directory.recordWithUID("location01")
</del><ins>+ record = yield self.directory.recordWithUID("location01")
</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("location01")
</del><ins>+ # Tell the resources services to flush its cache and re-read XML
+ self._flush()
+
+ record = yield self.directory.recordWithUID("location01")
</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("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
</del><ins>+ record = yield self.directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
</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("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
</del><ins>+ # Tell the resources services to flush its cache and re-read XML
+ self._flush()
+
+ record = yield self.directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
</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("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
- self.assertEquals(record.fullName, "Laptop 1")
</del><ins>+ record = yield self.directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+ self.assertEquals(record.displayName, "Laptop 1")
</ins><span class="cx">
</span><span class="cx"> yield self.runCommand(command_setResourceAttributes)
</span><span class="cx">
</span><del>- directory.flushCaches()
- record = directory.recordWithUID("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
- self.assertEquals(record.fullName, "Updated Laptop 1")
</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("AF575A61-CFA6-49E1-A0F6-B5662C9D9801")
+ self.assertEquals(record.displayName, "Updated Laptop 1")
</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("resource01")
</del><ins>+ record = yield self.directory.recordWithUID("resource01")
</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("resource01")
</del><ins>+ # Tell the resources services to flush its cache and re-read XML
+ self._flush()
+
+ record = yield self.directory.recordWithUID("resource01")
</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"> """
</span><span class="cx"> Verify readConfig returns with only the writable keys
</span><span class="cx"> """
</span><del>- results = yield self.runCommand(command_readConfig,
- script="calendarserver_config")
-
</del><ins>+ results = yield self.runCommand(
+ command_readConfig,
+ script="calendarserver_config"
+ )
</ins><span class="cx"> self.assertEquals(results["result"]["RedirectHTTPToHTTPS"], False)
</span><span class="cx"> self.assertEquals(results["result"]["EnableSearchAddressBook"], False)
</span><span class="cx"> self.assertEquals(results["result"]["EnableCalDAV"], True)
</span><span class="lines">@@ -360,8 +456,10 @@
</span><span class="cx"> """
</span><span class="cx"> Verify writeConfig updates the writable plist file only
</span><span class="cx"> """
</span><del>- results = yield self.runCommand(command_writeConfig,
- script="calendarserver_config")
</del><ins>+ results = yield self.runCommand(
+ command_writeConfig,
+ script="calendarserver_config"
+ )
</ins><span class="cx">
</span><span class="cx"> self.assertEquals(results["result"]["EnableCalDAV"], False)
</span><span class="cx"> self.assertEquals(results["result"]["EnableCardDAV"], False)
</span><span class="lines">@@ -383,9 +481,9 @@
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>addReadProxy</string>
</span><span class="cx"> <key>Principal</key>
</span><del>- <string>locations:location01</string>
</del><ins>+ <string>location01</string>
</ins><span class="cx"> <key>Proxy</key>
</span><del>- <string>users:user03</string>
</del><ins>+ <string>user03</string>
</ins><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="lines">@@ -397,9 +495,9 @@
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>addWriteProxy</string>
</span><span class="cx"> <key>Principal</key>
</span><del>- <string>locations:location01</string>
</del><ins>+ <string>location01</string>
</ins><span class="cx"> <key>Proxy</key>
</span><del>- <string>users:user01</string>
</del><ins>+ <string>user01</string>
</ins><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="lines">@@ -422,7 +520,7 @@
</span><span class="cx"> </array>
</span><span class="cx"> <key>StreetAddress</key>
</span><span class="cx"> <string>1 Infinite Loop\nCupertino, 95014\nCA</string>
</span><del>- <key>Geo</key>
</del><ins>+ <key>GeographicLocation</key>
</ins><span class="cx"> <string>geo:37.331,-122.030</string>
</span><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="lines">@@ -435,8 +533,8 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>createLocation</string>
</span><del>- <key>AutoSchedule</key>
- <true/>
</del><ins>+ <key>AutoScheduleMode</key>
+ <string>acceptIfFree</string>
</ins><span class="cx"> <key>AutoAcceptGroup</key>
</span><span class="cx"> <string>E5A6142C-4189-4E9E-90B0-9CD0268B314B</string>
</span><span class="cx"> <key>GeneratedUID</key>
</span><span class="lines">@@ -453,19 +551,21 @@
</span><span class="cx"> <string>Test Description</string>
</span><span class="cx"> <key>Floor</key>
</span><span class="cx"> <string>First</string>
</span><ins>+ <!--
</ins><span class="cx"> <key>Capacity</key>
</span><span class="cx"> <string>40</string>
</span><ins>+ -->
</ins><span class="cx"> <key>AssociatedAddress</key>
</span><span class="cx"> <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
</span><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user03</string>
- <string>users:user04</string>
</del><ins>+ <string>user03</string>
+ <string>user04</string>
</ins><span class="cx"> </array>
</span><span class="cx"> <key>WriteProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user05</string>
- <string>users:user06</string>
</del><ins>+ <string>user05</string>
+ <string>user06</string>
</ins><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="lines">@@ -478,31 +578,33 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>createResource</string>
</span><del>- <key>AutoSchedule</key>
- <true/>
</del><ins>+ <key>AutoScheduleMode</key>
+ <string>declineIfBusy</string>
</ins><span class="cx"> <key>GeneratedUID</key>
</span><span class="cx"> <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
</span><span class="cx"> <key>RealName</key>
</span><span class="cx"> <string>Laptop 1</string>
</span><ins>+ <!--
</ins><span class="cx"> <key>Comment</key>
</span><span class="cx"> <string>Test Comment</string>
</span><span class="cx"> <key>Description</key>
</span><span class="cx"> <string>Test Description</string>
</span><span class="cx"> <key>Type</key>
</span><span class="cx"> <string>Computer</string>
</span><ins>+ -->
</ins><span class="cx"> <key>RecordName</key>
</span><span class="cx"> <array>
</span><span class="cx"> <string>laptop1</string>
</span><span class="cx"> </array>
</span><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user03</string>
- <string>users:user04</string>
</del><ins>+ <string>user03</string>
+ <string>user04</string>
</ins><span class="cx"> </array>
</span><span class="cx"> <key>WriteProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user05</string>
- <string>users:user06</string>
</del><ins>+ <string>user05</string>
+ <string>user06</string>
</ins><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="lines">@@ -617,9 +719,9 @@
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>removeReadProxy</string>
</span><span class="cx"> <key>Principal</key>
</span><del>- <string>locations:location01</string>
</del><ins>+ <string>location01</string>
</ins><span class="cx"> <key>Proxy</key>
</span><del>- <string>users:user03</string>
</del><ins>+ <string>user03</string>
</ins><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="lines">@@ -631,9 +733,9 @@
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>removeWriteProxy</string>
</span><span class="cx"> <key>Principal</key>
</span><del>- <string>locations:location01</string>
</del><ins>+ <string>location01</string>
</ins><span class="cx"> <key>Proxy</key>
</span><del>- <string>users:user01</string>
</del><ins>+ <string>user01</string>
</ins><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="lines">@@ -662,24 +764,53 @@
</span><span class="cx"> <string>Updated Test Description</string>
</span><span class="cx"> <key>Floor</key>
</span><span class="cx"> <string>Second</string>
</span><ins>+ <!--
</ins><span class="cx"> <key>Capacity</key>
</span><span class="cx"> <string>41</string>
</span><del>- <key>StreetAddress</key>
- <string>2 Infinite Loop\nCupertino, 95014\nCA</string>
</del><ins>+ -->
</ins><span class="cx"> <key>ReadProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user03</string>
</del><ins>+ <string>user03</string>
</ins><span class="cx"> </array>
</span><span class="cx"> <key>WriteProxies</key>
</span><span class="cx"> <array>
</span><del>- <string>users:user05</string>
- <string>users:user06</string>
- <string>users:user07</string>
</del><ins>+ <string>user05</string>
+ <string>user06</string>
+ <string>user07</string>
</ins><span class="cx"> </array>
</span><span class="cx"> </dict>
</span><span class="cx"> </plist>
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+command_setAddressOnLocation = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>setLocationAttributes</string>
+ <key>GeneratedUID</key>
+ <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
+ <key>AssociatedAddress</key>
+ <string>C701069D-9CA1-4925-A1A9-5CD94767B74B</string>
+</dict>
+</plist>
+"""
+
+command_removeAddressFromLocation = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>setLocationAttributes</string>
+ <key>GeneratedUID</key>
+ <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
+ <key>AssociatedAddress</key>
+ <string></string>
+</dict>
+</plist>
+"""
+
+
</ins><span class="cx"> command_getLocationAttributes = """<?xml version="1.0" encoding="UTF-8"?>
</span><span class="cx"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
</span><span class="cx"> <plist version="1.0">
</span><span class="lines">@@ -716,7 +847,7 @@
</span><span class="cx"> <string>Updated Address</string>
</span><span class="cx"> <key>StreetAddress</key>
</span><span class="cx"> <string>Updated Street Address</string>
</span><del>- <key>Geo</key>
</del><ins>+ <key>GeographicLocation</key>
</ins><span class="cx"> <string>Updated Geo</string>
</span><span class="cx">
</span><span class="cx"> </dict>
</span><span class="lines">@@ -730,8 +861,8 @@
</span><span class="cx"> <dict>
</span><span class="cx"> <key>command</key>
</span><span class="cx"> <string>setResourceAttributes</string>
</span><del>- <key>AutoSchedule</key>
- <false/>
</del><ins>+ <key>AutoScheduleMode</key>
+ <string>acceptIfFree</string>
</ins><span class="cx"> <key>GeneratedUID</key>
</span><span class="cx"> <string>AF575A61-CFA6-49E1-A0F6-B5662C9D9801</string>
</span><span class="cx"> <key>RealName</key>
</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__), "principals")
</span><span class="cx"> templateName = os.path.join(testRoot, "caldavd.plist")
</span><span class="lines">@@ -49,11 +47,11 @@
</span><span class="cx">
</span><span class="cx"> databaseRoot = os.path.abspath("_spawned_scripts_db" + str(os.getpid()))
</span><span class="cx"> newConfig = template % {
</span><del>- "ServerRoot" : os.path.abspath(config.ServerRoot),
- "DataRoot" : os.path.abspath(config.DataRoot),
- "DatabaseRoot" : databaseRoot,
- "DocumentRoot" : os.path.abspath(config.DocumentRoot),
- "LogRoot" : os.path.abspath(config.LogRoot),
</del><ins>+ "ServerRoot": os.path.abspath(config.ServerRoot),
+ "DataRoot": os.path.abspath(config.DataRoot),
+ "DatabaseRoot": databaseRoot,
+ "DocumentRoot": os.path.abspath(config.DocumentRoot),
+ "LogRoot": os.path.abspath(config.LogRoot),
</ins><span class="cx"> }
</span><span class="cx"> configFilePath = FilePath(os.path.join(config.ConfigRoot, "caldavd.plist"))
</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__),
- "principals", "users-groups.xml"))
</del><ins>+ origUsersFile = FilePath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "principals",
+ "users-groups.xml"
+ )
+ )
</ins><span class="cx"> copyUsersFile = FilePath(os.path.join(config.DataRoot, "accounts.xml"))
</span><span class="cx"> origUsersFile.copyTo(copyUsersFile)
</span><span class="cx">
</span><del>- origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
- "principals", "resources-locations.xml"))
</del><ins>+ origResourcesFile = FilePath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "principals",
+ "resources-locations.xml"
+ )
+ )
</ins><span class="cx"> copyResourcesFile = FilePath(os.path.join(config.DataRoot, "resources.xml"))
</span><span class="cx"> origResourcesFile.copyTo(copyResourcesFile)
</span><span class="cx">
</span><del>- origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
- "principals", "augments.xml"))
</del><ins>+ origAugmentFile = FilePath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "principals",
+ "augments.xml"
+ )
+ )
</ins><span class="cx"> copyAugmentFile = FilePath(os.path.join(config.DataRoot, "augments.xml"))
</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("users" in results)
</span><span class="cx"> self.assertTrue("locations" in results)
</span><span class="cx"> self.assertTrue("resources" in results)
</span><ins>+ self.assertTrue("addresses" 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("--add", "resources", "New Resource",
- "newresource", "edaa6ae6-011b-4d89-ace3-6b688cdd91d9")
</del><ins>+ results = yield self.runCommand(
+ "--add", "resources",
+ "New Resource", "newresource", "newresourceuid"
+ )
</ins><span class="cx"> self.assertTrue("Added 'New Resource'" in results)
</span><span class="cx">
</span><del>- results = yield self.runCommand("--get-auto-schedule",
- "resources:newresource")
- self.assertTrue(results.startswith('Auto-schedule for "New Resource" (resources:newresource) is true'))
</del><ins>+ results = yield self.runCommand(
+ "--get-auto-schedule-mode",
+ "resources:newresource"
+ )
+ self.assertTrue(
+ results.startswith(
+ 'Auto-schedule mode for "New Resource" newresourceuid (resource) newresource is accept if free, decline if busy'
+ )
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--get-auto-schedule-mode",
- "resources:newresource")
- self.assertTrue(results.startswith('Auto-schedule mode for "New Resource" (resources:newresource) is default'))
-
</del><span class="cx"> results = yield self.runCommand("--list-principals=resources")
</span><span class="cx"> self.assertTrue("newresource" in results)
</span><span class="cx">
</span><del>- results = yield self.runCommand("--add", "resources", "New Resource",
- "newresource1", "edaa6ae6-011b-4d89-ace3-6b688cdd91d9")
- self.assertTrue("Duplicate guid" in results)
</del><ins>+ results = yield self.runCommand(
+ "--add", "resources", "New Resource",
+ "newresource1", "newresourceuid"
+ )
+ self.assertTrue("UID already in use: newresourceuid" in results)
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--add", "resources", "New Resource",
- "newresource", "fdaa6ae6-011b-4d89-ace3-6b688cdd91d9")
- self.assertTrue("Duplicate shortName" in results)
</del><ins>+ results = yield self.runCommand(
+ "--add", "resources", "New Resource",
+ "newresource", "uniqueuid"
+ )
+ self.assertTrue("Record name already in use" in results)
</ins><span class="cx">
</span><span class="cx"> results = yield self.runCommand("--remove", "resources:newresource")
</span><span class="cx"> self.assertTrue("Removed 'New Resource'" 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(("full name", None, None),
- parseCreationArgs(("full name",)))
-
- self.assertEquals(("full name", "short name", None),
- parseCreationArgs(("full name", "short name")))
-
- guid = "02C3DE93-E655-4856-47B76B8BB1A7BDCE"
-
- self.assertEquals(("full name", "short name", guid),
- parseCreationArgs(("full name", "short name", guid)))
-
- self.assertEquals(("full name", "short name", guid),
- parseCreationArgs(("full name", guid, "short name")))
-
- self.assertEquals(("full name", None, guid),
- parseCreationArgs(("full name", guid)))
-
- self.assertRaises(
- ValueError,
- parseCreationArgs, ("full name", "non guid", "non guid")
</del><ins>+ self.assertEquals(
+ ("full name", "short name", "uid"),
+ parseCreationArgs(("full name", "short name", "uid"))
</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("abc", matchStrings("a", ("abc", "def")))
</span><span class="cx"> self.assertEquals("def", matchStrings("de", ("abc", "def")))
</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("--add-write-proxy=users:user01",
- "locations:location01")
- self.assertTrue(results.startswith('Added "Test User 01" (users:user01) as a write proxy for "Room 01" (locations:location01)'))
</del><ins>+ results = yield self.runCommand(
+ "--add-write-proxy=users:user01", "locations:location01"
+ )
+ self.assertTrue(
+ results.startswith('Added "User 01" user01 (user) user01 as a write proxy for "Room 01" location01 (location) location01')
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--list-write-proxies",
- "locations:location01")
- self.assertTrue("Test User 01" in results)
</del><ins>+ results = yield self.runCommand(
+ "--list-write-proxies", "locations:location01"
+ )
+ self.assertTrue("User 01" in results)
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--remove-proxy=users:user01",
- "locations:location01")
</del><ins>+ results = yield self.runCommand(
+ "--remove-proxy=users:user01", "locations:location01"
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--list-write-proxies",
- "locations:location01")
- self.assertTrue('No write proxies for "Room 01" (locations:location01)' in results)
</del><ins>+ results = yield self.runCommand(
+ "--list-write-proxies", "locations:location01"
+ )
+ self.assertTrue(
+ 'No write proxies for "Room 01" 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("--add-read-proxy=users:user01",
- "locations:location01")
- self.assertTrue(results.startswith('Added "Test User 01" (users:user01) as a read proxy for "Room 01" (locations:location01)'))
</del><ins>+ results = yield self.runCommand(
+ "--add-read-proxy=users:user01", "locations:location01"
+ )
+ self.assertTrue(
+ results.startswith('Added "User 01" user01 (user) user01 as a read proxy for "Room 01" location01 (location) location01')
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--list-read-proxies",
- "locations:location01")
- self.assertTrue("Test User 01" in results)
</del><ins>+ results = yield self.runCommand(
+ "--list-read-proxies", "locations:location01"
+ )
+ self.assertTrue("User 01" in results)
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--remove-proxy=users:user01",
- "locations:location01")
</del><ins>+ results = yield self.runCommand(
+ "--remove-proxy=users:user01", "locations:location01"
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--list-read-proxies",
- "locations:location01")
- self.assertTrue('No read proxies for "Room 01" (locations:location01)' in results)
</del><ins>+ results = yield self.runCommand(
+ "--list-read-proxies", "locations:location01"
+ )
+ self.assertTrue(
+ 'No read proxies for "Room 01" 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("--get-auto-schedule",
- "locations:location01")
- self.assertTrue(results.startswith('Auto-schedule for "Room 01" (locations:location01) is false'))
-
- results = yield self.runCommand("--set-auto-schedule=true",
- "locations:location01")
- self.assertTrue(results.startswith('Setting auto-schedule to true for "Room 01" (locations:location01)'))
-
- results = yield self.runCommand("--get-auto-schedule",
- "locations:location01")
- self.assertTrue(results.startswith('Auto-schedule for "Room 01" (locations:location01) is true'))
-
- results = yield self.runCommand("--set-auto-schedule=true",
- "users:user01")
- 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("--get-auto-schedule-mode",
- "locations:location01")
- self.assertTrue(results.startswith('Auto-schedule mode for "Room 01" (locations:location01) is default'))
</del><ins>+ results = yield self.runCommand(
+ "--get-auto-schedule-mode", "locations:location01"
+ )
+ self.assertTrue(
+ results.startswith('Auto-schedule mode for "Room 01" location01 (location) location01 is accept if free, decline if busy')
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--set-auto-schedule-mode=accept-if-free",
- "locations:location01")
- self.assertTrue(results.startswith('Setting auto-schedule mode to accept-if-free for "Room 01" (locations:location01)'))
</del><ins>+ results = yield self.runCommand(
+ "--set-auto-schedule-mode=accept-if-free", "locations:location01"
+ )
+ self.assertTrue(
+ results.startswith('Setting auto-schedule-mode to accept if free for "Room 01" location01 (location) location01')
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--get-auto-schedule-mode",
- "locations:location01")
- self.assertTrue(results.startswith('Auto-schedule mode for "Room 01" (locations:location01) is accept-if-free'))
</del><ins>+ results = yield self.runCommand(
+ "--get-auto-schedule-mode",
+ "locations:location01"
+ )
+ self.assertTrue(
+ results.startswith('Auto-schedule mode for "Room 01" location01 (location) location01 is accept if free')
+ )
</ins><span class="cx">
</span><del>- results = yield self.runCommand("--set-auto-schedule-mode=decline-if-busy",
- "users:user01")
- self.assertTrue(results.startswith('Setting auto-schedule mode for (users)user01 is not allowed.'))
</del><ins>+ results = yield self.runCommand(
+ "--set-auto-schedule-mode=decline-if-busy", "users:user01"
+ )
+ self.assertTrue(results.startswith('Setting auto-schedule-mode for "User 01" user01 (user) user01 is not allowed.'))
</ins><span class="cx">
</span><span class="cx"> try:
</span><del>- results = yield self.runCommand("--set-auto-schedule-mode=bogus",
- "users:user01")
</del><ins>+ results = yield self.runCommand(
+ "--set-auto-schedule-mode=bogus",
+ "users:user01"
+ )
</ins><span class="cx"> except ErrorOutput:
</span><span class="cx"> pass
</span><span class="cx"> else:
</span><span class="cx"> self.fail("Expected command failure")
</span><span class="cx">
</span><span class="cx">
</span><del>- @inlineCallbacks
- def test_updateRecord(self):
- directory = directoryFromConfig(config)
- guid = "EEE28807-A8C5-46C8-A558-A08281C558A7"
</del><span class="cx">
</span><del>- (yield updateRecord(True, directory, "locations",
- guid=guid, fullName="Test Location", shortNames=["testlocation", ],)
- )
- try:
- (yield updateRecord(True, directory, "locations",
- guid=guid, fullName="Test Location", shortNames=["testlocation", ],)
- )
- except DirectoryError:
- # We're expecting an error for trying to create a record with
- # an existing GUID
- pass
- else:
- raise self.failureException("Duplicate guid expected")
</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, "Test Location")
- self.assertTrue(record.autoSchedule)
-
- (yield updateRecord(False, directory, "locations",
- guid=guid, fullName="Changed", shortNames=["testlocation", ],)
- )
- record = directory.recordWithGUID(guid)
- self.assertTrue(record is not None)
- self.assertEquals(record.fullName, "Changed")
-
- directory.destroyRecord("locations", guid=guid)
- record = directory.recordWithGUID(guid)
- self.assertTrue(record is None)
-
- # Create a user, change autoSchedule
- guid = "F0DE73A8-39D4-4830-8D32-1FA03ABA3470"
- (yield updateRecord(True, directory, "users",
- guid=guid, fullName="Test User", shortNames=["testuser", ],
- autoSchedule=True)
- )
- record = directory.recordWithGUID(guid)
- self.assertTrue(record is not None)
- self.assertEquals(record.fullName, "Test User")
- self.assertTrue(record.autoSchedule)
-
- (yield updateRecord(False, directory, "users",
- guid=guid, fullName="Test User", shortNames=["testuser", ],
- autoSchedule=False)
- )
- record = directory.recordWithGUID(guid)
- self.assertTrue(record is not None)
- self.assertEquals(record.fullName, "Test User")
- self.assertFalse(record.autoSchedule)
-
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def test_setProxies(self):
</span><span class="cx"> """
</span><span class="cx"> Read and Write proxies can be set en masse
</span><span class="cx"> """
</span><del>- directory = directoryFromConfig(config)
</del><ins>+ directory = self.directory
+ record = yield recordForPrincipalID(directory, "users:user01")
</ins><span class="cx">
</span><del>- principal = principalForPrincipalID("users:user01", 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, ["users:user03", "users:user04"], ["users:user05"], directory=directory))
- readProxies, writeProxies = (yield getProxies(principal, directory=directory))
- self.assertEquals(set(readProxies), set(["user03", "user04"]))
- self.assertEquals(set(writeProxies), set(["user05"]))
</del><ins>+ readProxies = [
+ (yield recordForPrincipalID(directory, "users:user03")),
+ (yield recordForPrincipalID(directory, "users:user04")),
+ ]
+ writeProxies = [
+ (yield recordForPrincipalID(directory, "users:user05")),
+ ]
+ 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(["user03", "user04"]))
+ self.assertEquals(set([r.uid for r in writeProxies]), set(["user05"]))
+
</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(["user05"])) # 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(["user05"])) # 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"> """.replace("\n", "\r\n")
</span><span class="lines">@@ -800,8 +798,8 @@
</span><span class="cx"> """
</span><span class="cx"> Tests for purging the data belonging to a given principal
</span><span class="cx"> """
</span><del>- uid = "6423F94A-6B76-4A3A-815B-D52CFD77935D"
- uid2 = "37DB0C90-4DB1-4932-BC69-3DAB66F374F5"
</del><ins>+ uid = "C76DB741-5A2A-4239-8112-10CF152AFCA4"
+ uid2 = "FFED7B62-2E08-496E-BD32-B2F95FFDDB6B"
</ins><span class="cx">
</span><span class="cx"> metadata = {
</span><span class="cx"> "accessMode": "PUBLIC",
</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("calendar1"))
- event = (yield calendar.calendarObjectWithName("attachment.ics"))
- attachment = (yield event.createAttachmentWithName("attachment.txt"))
</del><ins>+ home = yield txn.calendarHomeWithUID(self.uid)
+ calendar = yield home.calendarWithName("calendar1")
+ event = yield calendar.calendarObjectWithName("attachment.ics")
+ attachment = yield event.createAttachmentWithName("attachment.txt")
</ins><span class="cx"> t = attachment.store(MimeType("text", "x-fixture"))
</span><span class="cx"> t.write("attachment")
</span><span class="cx"> t.write(" text")
</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("calendar2"))
- 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("calendar2")
+ 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, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "purge", "accounts.xml"
- )
- )
- self.patch(config.ResourceService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "purge", "resources.xml"
- )
- )
</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"> """
</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, "FreeBusyIndexDelayedExpand", False)
</span><span class="cx">
</span><del>- self.patch(config.DirectoryService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "purge", "accounts.xml"
- )
- )
- self.patch(config.ResourceService.params, "xmlFile",
- os.path.join(
- os.path.dirname(__file__), "purge", "resources.xml"
- )
- )
</del><ins>+ # self.patch(config.DirectoryService.params, "xmlFile",
+ # os.path.join(
+ # os.path.dirname(__file__), "purge", "accounts.xml"
+ # )
+ # )
+ # self.patch(config.ResourceService.params, "xmlFile",
+ # os.path.join(
+ # os.path.dirname(__file__), "purge", "resources.xml"
+ # )
+ # )
</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, ("home1",), 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, ("home1",), verbose=False, proxies=False, completely=True))
</del><ins>+ total = yield PurgePrincipalService.purgeUIDs(self._sqlCalendarStore, self.directory,
+ self.rootResource, ("home1",), 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 = "dsAttrTypeStandard:GeneratedUID"
+ strName = "dsAttrTypeStandard:RealName"
+
</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["guid"]
</span><del>- record = StubDirectoryRecord(recordType, guid=guid,
- shortNames=recordData['shortNames'],
- fullName=recordData['fullName'])
</del><ins>+ record = StubDirectoryRecord(
+ recordType, guid=guid,
+ shortNames=recordData["shortNames"],
+ fullName=recordData["fullName"]
+ )
</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>+ "dsRecTypeStandard:Resources":
+ [
+ ["projector1", {
+ strGUID: "6C99E240-E915-4012-82FA-99E0F638D7EF",
+ strName: "Projector 1"
+ }],
+ ["projector2", {
+ strGUID: "7C99E240-E915-4012-82FA-99E0F638D7EF",
+ strName: "Projector 2"
+ }],
+ ],
+ "dsRecTypeStandard:Places":
+ [
+ ["office1", {
+ strGUID: "8C99E240-E915-4012-82FA-99E0F638D7EF",
+ strName: "Office 1"
+ }],
+ ],
+ }
</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>+ (
+ "6C99E240-E915-4012-82FA-99E0F638D7EF",
+ DirectoryService.recordType_resources
+ ),
+ (
+ "7C99E240-E915-4012-82FA-99E0F638D7EF",
+ DirectoryService.recordType_resources
+ ),
+ (
+ "8C99E240-E915-4012-82FA-99E0F638D7EF",
+ 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["dsRecTypeStandard:Resources"].append(
+ ["projector3", {
+ strGUID: "9C99E240-E915-4012-82FA-99E0F638D7EF",
+ strName: "Projector 3"
</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["dsRecTypeStandard:Places"].append(
+ ["office2", {
+ strGUID: "AC99E240-E915-4012-82FA-99E0F638D7EF",
+ strName: "Office 2"
</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>+ (
+ "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
+ ),
</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"> "loadConfig",
</span><del>- "getDirectory",
- "dummyDirectoryRecord",
</del><span class="cx"> "UsageError",
</span><span class="cx"> "booleanArgument",
</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"> """
</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, "_principalCollection"):
</del><ins>+# class MyDirectoryService (AggregateDirectoryService):
+# def getPrincipalCollection(self):
+# if not hasattr(self, "_principalCollection"):
</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, "/calendars/", _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, "/calendars/", _newStore)
</ins><span class="cx">
</span><del>- from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
- self._principalCollection = DirectoryPrincipalProvisioningResource("/principals/", self)
</del><ins>+# from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+# self._principalCollection = DirectoryPrincipalProvisioningResource("/principals/", 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("principals", principalCollection)
</del><ins>+# #
+# # Wire up the resource hierarchy
+# #
+# principalCollection = aggregate.getPrincipalCollection()
+# root = RootResource(
+# config.DocumentRoot,
+# principalCollections=(principalCollection,),
+# )
+# root.putChild("principals", 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, "/calendars/",
- _newStore,
- )
- root.putChild("calendars", calendarCollection)
</del><ins>+# from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+# calendarCollection = DirectoryCalendarHomeProvisioningResource(
+# aggregate, "/calendars/",
+# _newStore,
+# )
+# root.putChild("calendars", 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 = ""
- baseGUID = "51856FD4-5023-4890-94FE-4356C4AAC3E4"
- def recordTypes(self):
- return ()
</del><ins>+# class DummyDirectoryService (DirectoryService):
+# realmName = ""
+# baseGUID = "51856FD4-5023-4890-94FE-4356C4AAC3E4"
+# 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="dummy",
- guid="8EF0892F-7CB6-4B8E-B294-7C5A5321136A",
- shortNames=("dummy",),
- fullName="Dummy McDummerson",
- firstName="Dummy",
- lastName="McDummerson",
-)
</del><ins>+# dummyDirectoryRecord = DirectoryRecord(
+# service=DummyDirectoryService(),
+# recordType="dummy",
+# guid="8EF0892F-7CB6-4B8E-B294-7C5A5321136A",
+# shortNames=("dummy",),
+# fullName="Dummy McDummerson",
+# firstName="Dummy",
+# lastName="McDummerson",
+# )
</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("Can't resolve all paths yet")
</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("("):
</span><span class="cx"> try:
</span><span class="cx"> i = principalID.index(")")
</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 "(" 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 ":" 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(":", 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("Invalid principal identifier: %s" % (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("/"):
+ segments = principalID.strip("/").split("/")
+ if (len(segments) == 3 and
+ segments[0] == "principals" and segments[1] == "__uids__"):
+ uid = segments[2]
+ else:
+ raise ValueError("Can't resolve all paths yet")
+
+ if checkOnly:
+ returnValue(None)
+
+ returnValue((yield directory.recordWithUID(uid)))
+
+ if principalID.startswith("("):
+ try:
+ i = principalID.index(")")
+
+ if checkOnly:
+ returnValue(None)
+
+ recordType = directory.oldNameToRecordType(principalID[1:i])
+ shortName = principalID[i + 1:]
+
+ if not recordType or not shortName or "(" in recordType:
+ raise ValueError()
+
+ returnValue((yield directory.recordWithShortName(recordType, shortName)))
+
+ except ValueError:
+ pass
+
+ if ":" in principalID:
+ if checkOnly:
+ returnValue(None)
+
+ recordType, shortName = principalID.split(":", 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("Invalid principal identifier: %s" % (principalID,))
+
+
+
</ins><span class="cx"> def proxySubprincipal(principal, proxyType):
</span><span class="cx"> return principal.getChild("calendar-proxy-" + 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 "\"%s\" (%s:%s)" % (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 "\"{d}\" {uid} ({rt}) {sn}".format(
+ d=record.displayName,
+ rt=record.recordType.name,
+ uid=record.uid,
+ sn=(", ".join(record.shortNames))
+ )
</ins><span class="cx">
</span><ins>+
+
</ins><span class="cx"> class ProxyError(Exception):
</span><span class="cx"> """
</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 == "":
</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"> -->
</span><span class="cx">
</span><del>-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-
-<accounts realm="Test Realm">
- <user>
- <uid>admin</uid>
- <guid>admin</guid>
</del><ins>+<directory realm="Test Realm">
+<record>
+ <uid>0C8BDE62-E600-4696-83D3-8B5ECABDFD2E</uid>
+ <guid>0C8BDE62-E600-4696-83D3-8B5ECABDFD2E</guid>
+ <short-name>admin</short-name>
</ins><span class="cx"> <password>admin</password>
</span><del>- <name>Super User</name>
- <first-name>Super</first-name>
- <last-name>User</last-name>
- </user>
- <user>
- <uid>apprentice</uid>
- <guid>apprentice</guid>
</del><ins>+ <full-name>Super User</full-name>
+ <email>admin@example.com</email>
+</record>
+<record>
+ <uid>29B6C503-11DF-43EC-8CCA-40C7003149CE</uid>
+ <guid>29B6C503-11DF-43EC-8CCA-40C7003149CE</guid>
+ <short-name>apprentice</short-name>
</ins><span class="cx"> <password>apprentice</password>
</span><del>- <name>Apprentice Super User</name>
- <first-name>Apprentice</first-name>
- <last-name>Super User</last-name>
- </user>
- <user>
- <uid>wsanchez</uid>
- <guid>wsanchez</guid>
- <email-address>wsanchez@example.com</email-address>
- <password>test</password>
- <name>Wilfredo Sanchez Vega</name>
- <first-name>Wilfredo</first-name>
- <last-name>Sanchez Vega</last-name>
- </user>
- <user>
- <uid>cdaboo</uid>
- <guid>cdaboo</guid>
- <email-address>cdaboo@example.com</email-address>
- <password>test</password>
- <name>Cyrus Daboo</name>
- <first-name>Cyrus</first-name>
- <last-name>Daboo</last-name>
- </user>
- <user>
- <uid>sagen</uid>
- <guid>sagen</guid>
- <email-address>sagen@example.com</email-address>
- <password>test</password>
- <name>Morgen Sagen</name>
- <first-name>Morgen</first-name>
- <last-name>Sagen</last-name>
- </user>
- <user>
- <uid>dre</uid>
- <guid>andre</guid>
- <email-address>dre@example.com</email-address>
- <password>test</password>
- <name>Andre LaBranche</name>
- <first-name>Andre</first-name>
- <last-name>LaBranche</last-name>
- </user>
- <user>
- <uid>glyph</uid>
- <guid>glyph</guid>
- <email-address>glyph@example.com</email-address>
- <password>test</password>
- <name>Glyph Lefkowitz</name>
- <first-name>Glyph</first-name>
- <last-name>Lefkowitz</last-name>
- </user>
- <user>
- <uid>i18nuser</uid>
- <guid>i18nuser</guid>
- <email-address>i18nuser@example.com</email-address>
</del><ins>+ <full-name>Apprentice Super User</full-name>
+ <email>apprentice@example.com</email>
+</record>
+<record>
+ <uid>860B3EE9-6D7C-4296-9639-E6B998074A78</uid>
+ <guid>860B3EE9-6D7C-4296-9639-E6B998074A78</guid>
+ <short-name>i18nuser</short-name>
</ins><span class="cx"> <password>i18nuser</password>
</span><del>- <name>まだ</name>
- <first-name>ま</first-name>
- <last-name>だ</last-name>
- </user>
- <user repeat="101">
- <uid>user%02d</uid>
- <uid>User %02d</uid>
- <guid>user%02d</guid>
- <password>user%02d</password>
- <name>User %02d</name>
- <first-name>User</first-name>
- <last-name>%02d</last-name>
- <email-address>user%02d@example.com</email-address>
- </user>
- <user repeat="10">
- <uid>public%02d</uid>
- <guid>public%02d</guid>
- <password>public%02d</password>
- <name>Public %02d</name>
- <first-name>Public</first-name>
- <last-name>%02d</last-name>
- </user>
- <group>
- <uid>group01</uid>
- <guid>group01</guid>
- <password>group01</password>
- <name>Group 01</name>
- <members>
- <member type="users">user01</member>
- </members>
- </group>
- <group>
- <uid>group02</uid>
- <guid>group02</guid>
- <password>group02</password>
- <name>Group 02</name>
- <members>
- <member type="users">user06</member>
- <member type="users">user07</member>
- </members>
- </group>
- <group>
- <uid>group03</uid>
- <guid>group03</guid>
- <password>group03</password>
- <name>Group 03</name>
- <members>
- <member type="users">user08</member>
- <member type="users">user09</member>
- </members>
- </group>
- <group>
- <uid>group04</uid>
- <guid>group04</guid>
- <password>group04</password>
- <name>Group 04</name>
- <members>
- <member type="groups">group02</member>
- <member type="groups">group03</member>
- <member type="users">user10</member>
- </members>
- </group>
- <group> <!-- delegategroup -->
- <uid>group05</uid>
- <guid>group05</guid>
- <password>group05</password>
- <name>Group 05</name>
- <members>
- <member type="groups">group06</member>
- <member type="users">user20</member>
- </members>
- </group>
- <group> <!-- delegatesubgroup -->
- <uid>group06</uid>
- <guid>group06</guid>
- <password>group06</password>
- <name>Group 06</name>
- <members>
- <member type="users">user21</member>
- </members>
- </group>
- <group> <!-- readonlydelegategroup -->
- <uid>group07</uid>
- <guid>group07</guid>
- <password>group07</password>
- <name>Group 07</name>
- <members>
- <member type="users">user22</member>
- <member type="users">user23</member>
- <member type="users">user24</member>
- </members>
- </group>
- <group>
- <uid>disabledgroup</uid>
- <guid>disabledgroup</guid>
- <password>disabledgroup</password>
- <name>Disabled Group</name>
- <members>
- <member type="users">user01</member>
- </members>
- </group>
-</accounts>
</del><ins>+ <full-name>まだ</full-name>
+ <email>i18nuser@example.com</email>
+</record>
+<record type="user">
+ <short-name>user01</short-name>
+ <uid>10000000-0000-0000-0000-000000000001</uid>
+ <guid>10000000-0000-0000-0000-000000000001</guid>
+ <password>user01</password>
+ <full-name>User 01</full-name>
+ <email>user01@example.com</email>
+</record>
+<record type="user">
+ <short-name>user02</short-name>
+ <uid>10000000-0000-0000-0000-000000000002</uid>
+ <guid>10000000-0000-0000-0000-000000000002</guid>
+ <password>user02</password>
+ <full-name>User 02</full-name>
+ <email>user02@example.com</email>
+</record>
+<record type="user">
+ <short-name>user03</short-name>
+ <uid>10000000-0000-0000-0000-000000000003</uid>
+ <guid>10000000-0000-0000-0000-000000000003</guid>
+ <password>user03</password>
+ <full-name>User 03</full-name>
+ <email>user03@example.com</email>
+</record>
+<record type="user">
+ <short-name>user04</short-name>
+ <uid>10000000-0000-0000-0000-000000000004</uid>
+ <guid>10000000-0000-0000-0000-000000000004</guid>
+ <password>user04</password>
+ <full-name>User 04</full-name>
+ <email>user04@example.com</email>
+</record>
+<record type="user">
+ <short-name>user05</short-name>
+ <uid>10000000-0000-0000-0000-000000000005</uid>
+ <guid>10000000-0000-0000-0000-000000000005</guid>
+ <password>user05</password>
+ <full-name>User 05</full-name>
+ <email>user05@example.com</email>
+</record>
+<record type="user">
+ <short-name>user06</short-name>
+ <uid>10000000-0000-0000-0000-000000000006</uid>
+ <guid>10000000-0000-0000-0000-000000000006</guid>
+ <password>user06</password>
+ <full-name>User 06</full-name>
+ <email>user06@example.com</email>
+</record>
+<record type="user">
+ <short-name>user07</short-name>
+ <uid>10000000-0000-0000-0000-000000000007</uid>
+ <guid>10000000-0000-0000-0000-000000000007</guid>
+ <password>user07</password>
+ <full-name>User 07</full-name>
+ <email>user07@example.com</email>
+</record>
+<record type="user">
+ <short-name>user08</short-name>
+ <uid>10000000-0000-0000-0000-000000000008</uid>
+ <guid>10000000-0000-0000-0000-000000000008</guid>
+ <password>user08</password>
+ <full-name>User 08</full-name>
+ <email>user08@example.com</email>
+</record>
+<record type="user">
+ <short-name>user09</short-name>
+ <uid>10000000-0000-0000-0000-000000000009</uid>
+ <guid>10000000-0000-0000-0000-000000000009</guid>
+ <password>user09</password>
+ <full-name>User 09</full-name>
+ <email>user09@example.com</email>
+</record>
+<record type="user">
+ <short-name>user10</short-name>
+ <uid>10000000-0000-0000-0000-000000000010</uid>
+ <guid>10000000-0000-0000-0000-000000000010</guid>
+ <password>user10</password>
+ <full-name>User 10</full-name>
+ <email>user10@example.com</email>
+</record>
+<record type="user">
+ <short-name>user11</short-name>
+ <uid>10000000-0000-0000-0000-000000000011</uid>
+ <guid>10000000-0000-0000-0000-000000000011</guid>
+ <password>user11</password>
+ <full-name>User 11</full-name>
+ <email>user11@example.com</email>
+</record>
+<record type="user">
+ <short-name>user12</short-name>
+ <uid>10000000-0000-0000-0000-000000000012</uid>
+ <guid>10000000-0000-0000-0000-000000000012</guid>
+ <password>user12</password>
+ <full-name>User 12</full-name>
+ <email>user12@example.com</email>
+</record>
+<record type="user">
+ <short-name>user13</short-name>
+ <uid>10000000-0000-0000-0000-000000000013</uid>
+ <guid>10000000-0000-0000-0000-000000000013</guid>
+ <password>user13</password>
+ <full-name>User 13</full-name>
+ <email>user13@example.com</email>
+</record>
+<record type="user">
+ <short-name>user14</short-name>
+ <uid>10000000-0000-0000-0000-000000000014</uid>
+ <guid>10000000-0000-0000-0000-000000000014</guid>
+ <password>user14</password>
+ <full-name>User 14</full-name>
+ <email>user14@example.com</email>
+</record>
+<record type="user">
+ <short-name>user15</short-name>
+ <uid>10000000-0000-0000-0000-000000000015</uid>
+ <guid>10000000-0000-0000-0000-000000000015</guid>
+ <password>user15</password>
+ <full-name>User 15</full-name>
+ <email>user15@example.com</email>
+</record>
+<record type="user">
+ <short-name>user16</short-name>
+ <uid>10000000-0000-0000-0000-000000000016</uid>
+ <guid>10000000-0000-0000-0000-000000000016</guid>
+ <password>user16</password>
+ <full-name>User 16</full-name>
+ <email>user16@example.com</email>
+</record>
+<record type="user">
+ <short-name>user17</short-name>
+ <uid>10000000-0000-0000-0000-000000000017</uid>
+ <guid>10000000-0000-0000-0000-000000000017</guid>
+ <password>user17</password>
+ <full-name>User 17</full-name>
+ <email>user17@example.com</email>
+</record>
+<record type="user">
+ <short-name>user18</short-name>
+ <uid>10000000-0000-0000-0000-000000000018</uid>
+ <guid>10000000-0000-0000-0000-000000000018</guid>
+ <password>user18</password>
+ <full-name>User 18</full-name>
+ <email>user18@example.com</email>
+</record>
+<record type="user">
+ <short-name>user19</short-name>
+ <uid>10000000-0000-0000-0000-000000000019</uid>
+ <guid>10000000-0000-0000-0000-000000000019</guid>
+ <password>user19</password>
+ <full-name>User 19</full-name>
+ <email>user19@example.com</email>
+</record>
+<record type="user">
+ <short-name>user20</short-name>
+ <uid>10000000-0000-0000-0000-000000000020</uid>
+ <guid>10000000-0000-0000-0000-000000000020</guid>
+ <password>user20</password>
+ <full-name>User 20</full-name>
+ <email>user20@example.com</email>
+</record>
+<record type="user">
+ <short-name>user21</short-name>
+ <uid>10000000-0000-0000-0000-000000000021</uid>
+ <guid>10000000-0000-0000-0000-000000000021</guid>
+ <password>user21</password>
+ <full-name>User 21</full-name>
+ <email>user21@example.com</email>
+</record>
+<record type="user">
+ <short-name>user22</short-name>
+ <uid>10000000-0000-0000-0000-000000000022</uid>
+ <guid>10000000-0000-0000-0000-000000000022</guid>
+ <password>user22</password>
+ <full-name>User 22</full-name>
+ <email>user22@example.com</email>
+</record>
+<record type="user">
+ <short-name>user23</short-name>
+ <uid>10000000-0000-0000-0000-000000000023</uid>
+ <guid>10000000-0000-0000-0000-000000000023</guid>
+ <password>user23</password>
+ <full-name>User 23</full-name>
+ <email>user23@example.com</email>
+</record>
+<record type="user">
+ <short-name>user24</short-name>
+ <uid>10000000-0000-0000-0000-000000000024</uid>
+ <guid>10000000-0000-0000-0000-000000000024</guid>
+ <password>user24</password>
+ <full-name>User 24</full-name>
+ <email>user24@example.com</email>
+</record>
+<record type="user">
+ <short-name>user25</short-name>
+ <uid>10000000-0000-0000-0000-000000000025</uid>
+ <guid>10000000-0000-0000-0000-000000000025</guid>
+ <password>user25</password>
+ <full-name>User 25</full-name>
+ <email>user25@example.com</email>
+</record>
+<record type="user">
+ <short-name>user26</short-name>
+ <uid>10000000-0000-0000-0000-000000000026</uid>
+ <guid>10000000-0000-0000-0000-000000000026</guid>
+ <password>user26</password>
+ <full-name>User 26</full-name>
+ <email>user26@example.com</email>
+</record>
+<record type="user">
+ <short-name>user27</short-name>
+ <uid>10000000-0000-0000-0000-000000000027</uid>
+ <guid>10000000-0000-0000-0000-000000000027</guid>
+ <password>user27</password>
+ <full-name>User 27</full-name>
+ <email>user27@example.com</email>
+</record>
+<record type="user">
+ <short-name>user28</short-name>
+ <uid>10000000-0000-0000-0000-000000000028</uid>
+ <guid>10000000-0000-0000-0000-000000000028</guid>
+ <password>user28</password>
+ <full-name>User 28</full-name>
+ <email>user28@example.com</email>
+</record>
+<record type="user">
+ <short-name>user29</short-name>
+ <uid>10000000-0000-0000-0000-000000000029</uid>
+ <guid>10000000-0000-0000-0000-000000000029</guid>
+ <password>user29</password>
+ <full-name>User 29</full-name>
+ <email>user29@example.com</email>
+</record>
+<record type="user">
+ <short-name>user30</short-name>
+ <uid>10000000-0000-0000-0000-000000000030</uid>
+ <guid>10000000-0000-0000-0000-000000000030</guid>
+ <password>user30</password>
+ <full-name>User 30</full-name>
+ <email>user30@example.com</email>
+</record>
+<record type="user">
+ <short-name>user31</short-name>
+ <uid>10000000-0000-0000-0000-000000000031</uid>
+ <guid>10000000-0000-0000-0000-000000000031</guid>
+ <password>user31</password>
+ <full-name>User 31</full-name>
+ <email>user31@example.com</email>
+</record>
+<record type="user">
+ <short-name>user32</short-name>
+ <uid>10000000-0000-0000-0000-000000000032</uid>
+ <guid>10000000-0000-0000-0000-000000000032</guid>
+ <password>user32</password>
+ <full-name>User 32</full-name>
+ <email>user32@example.com</email>
+</record>
+<record type="user">
+ <short-name>user33</short-name>
+ <uid>10000000-0000-0000-0000-000000000033</uid>
+ <guid>10000000-0000-0000-0000-000000000033</guid>
+ <password>user33</password>
+ <full-name>User 33</full-name>
+ <email>user33@example.com</email>
+</record>
+<record type="user">
+ <short-name>user34</short-name>
+ <uid>10000000-0000-0000-0000-000000000034</uid>
+ <guid>10000000-0000-0000-0000-000000000034</guid>
+ <password>user34</password>
+ <full-name>User 34</full-name>
+ <email>user34@example.com</email>
+</record>
+<record type="user">
+ <short-name>user35</short-name>
+ <uid>10000000-0000-0000-0000-000000000035</uid>
+ <guid>10000000-0000-0000-0000-000000000035</guid>
+ <password>user35</password>
+ <full-name>User 35</full-name>
+ <email>user35@example.com</email>
+</record>
+<record type="user">
+ <short-name>user36</short-name>
+ <uid>10000000-0000-0000-0000-000000000036</uid>
+ <guid>10000000-0000-0000-0000-000000000036</guid>
+ <password>user36</password>
+ <full-name>User 36</full-name>
+ <email>user36@example.com</email>
+</record>
+<record type="user">
+ <short-name>user37</short-name>
+ <uid>10000000-0000-0000-0000-000000000037</uid>
+ <guid>10000000-0000-0000-0000-000000000037</guid>
+ <password>user37</password>
+ <full-name>User 37</full-name>
+ <email>user37@example.com</email>
+</record>
+<record type="user">
+ <short-name>user38</short-name>
+ <uid>10000000-0000-0000-0000-000000000038</uid>
+ <guid>10000000-0000-0000-0000-000000000038</guid>
+ <password>user38</password>
+ <full-name>User 38</full-name>
+ <email>user38@example.com</email>
+</record>
+<record type="user">
+ <short-name>user39</short-name>
+ <uid>10000000-0000-0000-0000-000000000039</uid>
+ <guid>10000000-0000-0000-0000-000000000039</guid>
+ <password>user39</password>
+ <full-name>User 39</full-name>
+ <email>user39@example.com</email>
+</record>
+<record type="user">
+ <short-name>user40</short-name>
+ <uid>10000000-0000-0000-0000-000000000040</uid>
+ <guid>10000000-0000-0000-0000-000000000040</guid>
+ <password>user40</password>
+ <full-name>User 40</full-name>
+ <email>user40@example.com</email>
+</record>
+<record type="user">
+ <short-name>user41</short-name>
+ <uid>10000000-0000-0000-0000-000000000041</uid>
+ <guid>10000000-0000-0000-0000-000000000041</guid>
+ <password>user41</password>
+ <full-name>User 41</full-name>
+ <email>user41@example.com</email>
+</record>
+<record type="user">
+ <short-name>user42</short-name>
+ <uid>10000000-0000-0000-0000-000000000042</uid>
+ <guid>10000000-0000-0000-0000-000000000042</guid>
+ <password>user42</password>
+ <full-name>User 42</full-name>
+ <email>user42@example.com</email>
+</record>
+<record type="user">
+ <short-name>user43</short-name>
+ <uid>10000000-0000-0000-0000-000000000043</uid>
+ <guid>10000000-0000-0000-0000-000000000043</guid>
+ <password>user43</password>
+ <full-name>User 43</full-name>
+ <email>user43@example.com</email>
+</record>
+<record type="user">
+ <short-name>user44</short-name>
+ <uid>10000000-0000-0000-0000-000000000044</uid>
+ <guid>10000000-0000-0000-0000-000000000044</guid>
+ <password>user44</password>
+ <full-name>User 44</full-name>
+ <email>user44@example.com</email>
+</record>
+<record type="user">
+ <short-name>user45</short-name>
+ <uid>10000000-0000-0000-0000-000000000045</uid>
+ <guid>10000000-0000-0000-0000-000000000045</guid>
+ <password>user45</password>
+ <full-name>User 45</full-name>
+ <email>user45@example.com</email>
+</record>
+<record type="user">
+ <short-name>user46</short-name>
+ <uid>10000000-0000-0000-0000-000000000046</uid>
+ <guid>10000000-0000-0000-0000-000000000046</guid>
+ <password>user46</password>
+ <full-name>User 46</full-name>
+ <email>user46@example.com</email>
+</record>
+<record type="user">
+ <short-name>user47</short-name>
+ <uid>10000000-0000-0000-0000-000000000047</uid>
+ <guid>10000000-0000-0000-0000-000000000047</guid>
+ <password>user47</password>
+ <full-name>User 47</full-name>
+ <email>user47@example.com</email>
+</record>
+<record type="user">
+ <short-name>user48</short-name>
+ <uid>10000000-0000-0000-0000-000000000048</uid>
+ <guid>10000000-0000-0000-0000-000000000048</guid>
+ <password>user48</password>
+ <full-name>User 48</full-name>
+ <email>user48@example.com</email>
+</record>
+<record type="user">
+ <short-name>user49</short-name>
+ <uid>10000000-0000-0000-0000-000000000049</uid>
+ <guid>10000000-0000-0000-0000-000000000049</guid>
+ <password>user49</password>
+ <full-name>User 49</full-name>
+ <email>user49@example.com</email>
+</record>
+<record type="user">
+ <short-name>user50</short-name>
+ <uid>10000000-0000-0000-0000-000000000050</uid>
+ <guid>10000000-0000-0000-0000-000000000050</guid>
+ <password>user50</password>
+ <full-name>User 50</full-name>
+ <email>user50@example.com</email>
+</record>
+<record type="user">
+ <short-name>user51</short-name>
+ <uid>10000000-0000-0000-0000-000000000051</uid>
+ <guid>10000000-0000-0000-0000-000000000051</guid>
+ <password>user51</password>
+ <full-name>User 51</full-name>
+ <email>user51@example.com</email>
+</record>
+<record type="user">
+ <short-name>user52</short-name>
+ <uid>10000000-0000-0000-0000-000000000052</uid>
+ <guid>10000000-0000-0000-0000-000000000052</guid>
+ <password>user52</password>
+ <full-name>User 52</full-name>
+ <email>user52@example.com</email>
+</record>
+<record type="user">
+ <short-name>user53</short-name>
+ <uid>10000000-0000-0000-0000-000000000053</uid>
+ <guid>10000000-0000-0000-0000-000000000053</guid>
+ <password>user53</password>
+ <full-name>User 53</full-name>
+ <email>user53@example.com</email>
+</record>
+<record type="user">
+ <short-name>user54</short-name>
+ <uid>10000000-0000-0000-0000-000000000054</uid>
+ <guid>10000000-0000-0000-0000-000000000054</guid>
+ <password>user54</password>
+ <full-name>User 54</full-name>
+ <email>user54@example.com</email>
+</record>
+<record type="user">
+ <short-name>user55</short-name>
+ <uid>10000000-0000-0000-0000-000000000055</uid>
+ <guid>10000000-0000-0000-0000-000000000055</guid>
+ <password>user55</password>
+ <full-name>User 55</full-name>
+ <email>user55@example.com</email>
+</record>
+<record type="user">
+ <short-name>user56</short-name>
+ <uid>10000000-0000-0000-0000-000000000056</uid>
+ <guid>10000000-0000-0000-0000-000000000056</guid>
+ <password>user56</password>
+ <full-name>User 56</full-name>
+ <email>user56@example.com</email>
+</record>
+<record type="user">
+ <short-name>user57</short-name>
+ <uid>10000000-0000-0000-0000-000000000057</uid>
+ <guid>10000000-0000-0000-0000-000000000057</guid>
+ <password>user57</password>
+ <full-name>User 57</full-name>
+ <email>user57@example.com</email>
+</record>
+<record type="user">
+ <short-name>user58</short-name>
+ <uid>10000000-0000-0000-0000-000000000058</uid>
+ <guid>10000000-0000-0000-0000-000000000058</guid>
+ <password>user58</password>
+ <full-name>User 58</full-name>
+ <email>user58@example.com</email>
+</record>
+<record type="user">
+ <short-name>user59</short-name>
+ <uid>10000000-0000-0000-0000-000000000059</uid>
+ <guid>10000000-0000-0000-0000-000000000059</guid>
+ <password>user59</password>
+ <full-name>User 59</full-name>
+ <email>user59@example.com</email>
+</record>
+<record type="user">
+ <short-name>user60</short-name>
+ <uid>10000000-0000-0000-0000-000000000060</uid>
+ <guid>10000000-0000-0000-0000-000000000060</guid>
+ <password>user60</password>
+ <full-name>User 60</full-name>
+ <email>user60@example.com</email>
+</record>
+<record type="user">
+ <short-name>user61</short-name>
+ <uid>10000000-0000-0000-0000-000000000061</uid>
+ <guid>10000000-0000-0000-0000-000000000061</guid>
+ <password>user61</password>
+ <full-name>User 61</full-name>
+ <email>user61@example.com</email>
+</record>
+<record type="user">
+ <short-name>user62</short-name>
+ <uid>10000000-0000-0000-0000-000000000062</uid>
+ <guid>10000000-0000-0000-0000-000000000062</guid>
+ <password>user62</password>
+ <full-name>User 62</full-name>
+ <email>user62@example.com</email>
+</record>
+<record type="user">
+ <short-name>user63</short-name>
+ <uid>10000000-0000-0000-0000-000000000063</uid>
+ <guid>10000000-0000-0000-0000-000000000063</guid>
+ <password>user63</password>
+ <full-name>User 63</full-name>
+ <email>user63@example.com</email>
+</record>
+<record type="user">
+ <short-name>user64</short-name>
+ <uid>10000000-0000-0000-0000-000000000064</uid>
+ <guid>10000000-0000-0000-0000-000000000064</guid>
+ <password>user64</password>
+ <full-name>User 64</full-name>
+ <email>user64@example.com</email>
+</record>
+<record type="user">
+ <short-name>user65</short-name>
+ <uid>10000000-0000-0000-0000-000000000065</uid>
+ <guid>10000000-0000-0000-0000-000000000065</guid>
+ <password>user65</password>
+ <full-name>User 65</full-name>
+ <email>user65@example.com</email>
+</record>
+<record type="user">
+ <short-name>user66</short-name>
+ <uid>10000000-0000-0000-0000-000000000066</uid>
+ <guid>10000000-0000-0000-0000-000000000066</guid>
+ <password>user66</password>
+ <full-name>User 66</full-name>
+ <email>user66@example.com</email>
+</record>
+<record type="user">
+ <short-name>user67</short-name>
+ <uid>10000000-0000-0000-0000-000000000067</uid>
+ <guid>10000000-0000-0000-0000-000000000067</guid>
+ <password>user67</password>
+ <full-name>User 67</full-name>
+ <email>user67@example.com</email>
+</record>
+<record type="user">
+ <short-name>user68</short-name>
+ <uid>10000000-0000-0000-0000-000000000068</uid>
+ <guid>10000000-0000-0000-0000-000000000068</guid>
+ <password>user68</password>
+ <full-name>User 68</full-name>
+ <email>user68@example.com</email>
+</record>
+<record type="user">
+ <short-name>user69</short-name>
+ <uid>10000000-0000-0000-0000-000000000069</uid>
+ <guid>10000000-0000-0000-0000-000000000069</guid>
+ <password>user69</password>
+ <full-name>User 69</full-name>
+ <email>user69@example.com</email>
+</record>
+<record type="user">
+ <short-name>user70</short-name>
+ <uid>10000000-0000-0000-0000-000000000070</uid>
+ <guid>10000000-0000-0000-0000-000000000070</guid>
+ <password>user70</password>
+ <full-name>User 70</full-name>
+ <email>user70@example.com</email>
+</record>
+<record type="user">
+ <short-name>user71</short-name>
+ <uid>10000000-0000-0000-0000-000000000071</uid>
+ <guid>10000000-0000-0000-0000-000000000071</guid>
+ <password>user71</password>
+ <full-name>User 71</full-name>
+ <email>user71@example.com</email>
+</record>
+<record type="user">
+ <short-name>user72</short-name>
+ <uid>10000000-0000-0000-0000-000000000072</uid>
+ <guid>10000000-0000-0000-0000-000000000072</guid>
+ <password>user72</password>
+ <full-name>User 72</full-name>
+ <email>user72@example.com</email>
+</record>
+<record type="user">
+ <short-name>user73</short-name>
+ <uid>10000000-0000-0000-0000-000000000073</uid>
+ <guid>10000000-0000-0000-0000-000000000073</guid>
+ <password>user73</password>
+ <full-name>User 73</full-name>
+ <email>user73@example.com</email>
+</record>
+<record type="user">
+ <short-name>user74</short-name>
+ <uid>10000000-0000-0000-0000-000000000074</uid>
+ <guid>10000000-0000-0000-0000-000000000074</guid>
+ <password>user74</password>
+ <full-name>User 74</full-name>
+ <email>user74@example.com</email>
+</record>
+<record type="user">
+ <short-name>user75</short-name>
+ <uid>10000000-0000-0000-0000-000000000075</uid>
+ <guid>10000000-0000-0000-0000-000000000075</guid>
+ <password>user75</password>
+ <full-name>User 75</full-name>
+ <email>user75@example.com</email>
+</record>
+<record type="user">
+ <short-name>user76</short-name>
+ <uid>10000000-0000-0000-0000-000000000076</uid>
+ <guid>10000000-0000-0000-0000-000000000076</guid>
+ <password>user76</password>
+ <full-name>User 76</full-name>
+ <email>user76@example.com</email>
+</record>
+<record type="user">
+ <short-name>user77</short-name>
+ <uid>10000000-0000-0000-0000-000000000077</uid>
+ <guid>10000000-0000-0000-0000-000000000077</guid>
+ <password>user77</password>
+ <full-name>User 77</full-name>
+ <email>user77@example.com</email>
+</record>
+<record type="user">
+ <short-name>user78</short-name>
+ <uid>10000000-0000-0000-0000-000000000078</uid>
+ <guid>10000000-0000-0000-0000-000000000078</guid>
+ <password>user78</password>
+ <full-name>User 78</full-name>
+ <email>user78@example.com</email>
+</record>
+<record type="user">
+ <short-name>user79</short-name>
+ <uid>10000000-0000-0000-0000-000000000079</uid>
+ <guid>10000000-0000-0000-0000-000000000079</guid>
+ <password>user79</password>
+ <full-name>User 79</full-name>
+ <email>user79@example.com</email>
+</record>
+<record type="user">
+ <short-name>user80</short-name>
+ <uid>10000000-0000-0000-0000-000000000080</uid>
+ <guid>10000000-0000-0000-0000-000000000080</guid>
+ <password>user80</password>
+ <full-name>User 80</full-name>
+ <email>user80@example.com</email>
+</record>
+<record type="user">
+ <short-name>user81</short-name>
+ <uid>10000000-0000-0000-0000-000000000081</uid>
+ <guid>10000000-0000-0000-0000-000000000081</guid>
+ <password>user81</password>
+ <full-name>User 81</full-name>
+ <email>user81@example.com</email>
+</record>
+<record type="user">
+ <short-name>user82</short-name>
+ <uid>10000000-0000-0000-0000-000000000082</uid>
+ <guid>10000000-0000-0000-0000-000000000082</guid>
+ <password>user82</password>
+ <full-name>User 82</full-name>
+ <email>user82@example.com</email>
+</record>
+<record type="user">
+ <short-name>user83</short-name>
+ <uid>10000000-0000-0000-0000-000000000083</uid>
+ <guid>10000000-0000-0000-0000-000000000083</guid>
+ <password>user83</password>
+ <full-name>User 83</full-name>
+ <email>user83@example.com</email>
+</record>
+<record type="user">
+ <short-name>user84</short-name>
+ <uid>10000000-0000-0000-0000-000000000084</uid>
+ <guid>10000000-0000-0000-0000-000000000084</guid>
+ <password>user84</password>
+ <full-name>User 84</full-name>
+ <email>user84@example.com</email>
+</record>
+<record type="user">
+ <short-name>user85</short-name>
+ <uid>10000000-0000-0000-0000-000000000085</uid>
+ <guid>10000000-0000-0000-0000-000000000085</guid>
+ <password>user85</password>
+ <full-name>User 85</full-name>
+ <email>user85@example.com</email>
+</record>
+<record type="user">
+ <short-name>user86</short-name>
+ <uid>10000000-0000-0000-0000-000000000086</uid>
+ <guid>10000000-0000-0000-0000-000000000086</guid>
+ <password>user86</password>
+ <full-name>User 86</full-name>
+ <email>user86@example.com</email>
+</record>
+<record type="user">
+ <short-name>user87</short-name>
+ <uid>10000000-0000-0000-0000-000000000087</uid>
+ <guid>10000000-0000-0000-0000-000000000087</guid>
+ <password>user87</password>
+ <full-name>User 87</full-name>
+ <email>user87@example.com</email>
+</record>
+<record type="user">
+ <short-name>user88</short-name>
+ <uid>10000000-0000-0000-0000-000000000088</uid>
+ <guid>10000000-0000-0000-0000-000000000088</guid>
+ <password>user88</password>
+ <full-name>User 88</full-name>
+ <email>user88@example.com</email>
+</record>
+<record type="user">
+ <short-name>user89</short-name>
+ <uid>10000000-0000-0000-0000-000000000089</uid>
+ <guid>10000000-0000-0000-0000-000000000089</guid>
+ <password>user89</password>
+ <full-name>User 89</full-name>
+ <email>user89@example.com</email>
+</record>
+<record type="user">
+ <short-name>user90</short-name>
+ <uid>10000000-0000-0000-0000-000000000090</uid>
+ <guid>10000000-0000-0000-0000-000000000090</guid>
+ <password>user90</password>
+ <full-name>User 90</full-name>
+ <email>user90@example.com</email>
+</record>
+<record type="user">
+ <short-name>user91</short-name>
+ <uid>10000000-0000-0000-0000-000000000091</uid>
+ <guid>10000000-0000-0000-0000-000000000091</guid>
+ <password>user91</password>
+ <full-name>User 91</full-name>
+ <email>user91@example.com</email>
+</record>
+<record type="user">
+ <short-name>user92</short-name>
+ <uid>10000000-0000-0000-0000-000000000092</uid>
+ <guid>10000000-0000-0000-0000-000000000092</guid>
+ <password>user92</password>
+ <full-name>User 92</full-name>
+ <email>user92@example.com</email>
+</record>
+<record type="user">
+ <short-name>user93</short-name>
+ <uid>10000000-0000-0000-0000-000000000093</uid>
+ <guid>10000000-0000-0000-0000-000000000093</guid>
+ <password>user93</password>
+ <full-name>User 93</full-name>
+ <email>user93@example.com</email>
+</record>
+<record type="user">
+ <short-name>user94</short-name>
+ <uid>10000000-0000-0000-0000-000000000094</uid>
+ <guid>10000000-0000-0000-0000-000000000094</guid>
+ <password>user94</password>
+ <full-name>User 94</full-name>
+ <email>user94@example.com</email>
+</record>
+<record type="user">
+ <short-name>user95</short-name>
+ <uid>10000000-0000-0000-0000-000000000095</uid>
+ <guid>10000000-0000-0000-0000-000000000095</guid>
+ <password>user95</password>
+ <full-name>User 95</full-name>
+ <email>user95@example.com</email>
+</record>
+<record type="user">
+ <short-name>user96</short-name>
+ <uid>10000000-0000-0000-0000-000000000096</uid>
+ <guid>10000000-0000-0000-0000-000000000096</guid>
+ <password>user96</password>
+ <full-name>User 96</full-name>
+ <email>user96@example.com</email>
+</record>
+<record type="user">
+ <short-name>user97</short-name>
+ <uid>10000000-0000-0000-0000-000000000097</uid>
+ <guid>10000000-0000-0000-0000-000000000097</guid>
+ <password>user97</password>
+ <full-name>User 97</full-name>
+ <email>user97@example.com</email>
+</record>
+<record type="user">
+ <short-name>user98</short-name>
+ <uid>10000000-0000-0000-0000-000000000098</uid>
+ <guid>10000000-0000-0000-0000-000000000098</guid>
+ <password>user98</password>
+ <full-name>User 98</full-name>
+ <email>user98@example.com</email>
+</record>
+<record type="user">
+ <short-name>user99</short-name>
+ <uid>10000000-0000-0000-0000-000000000099</uid>
+ <guid>10000000-0000-0000-0000-000000000099</guid>
+ <password>user99</password>
+ <full-name>User 99</full-name>
+ <email>user99@example.com</email>
+</record>
+<record type="user">
+ <short-name>user100</short-name>
+ <uid>10000000-0000-0000-0000-000000000100</uid>
+ <guid>10000000-0000-0000-0000-000000000100</guid>
+ <password>user100</password>
+ <full-name>User 100</full-name>
+ <email>user100@example.com</email>
+</record>
+<record type="user">
+ <short-name>public01</short-name>
+ <uid>50000000-0000-0000-0000-000000000001</uid>
+ <guid>50000000-0000-0000-0000-000000000001</guid>
+ <password>public01</password>
+ <full-name>Public 01</full-name>
+ <email>public01@example.com</email>
+</record>
+<record type="user">
+ <short-name>public02</short-name>
+ <uid>50000000-0000-0000-0000-000000000002</uid>
+ <guid>50000000-0000-0000-0000-000000000002</guid>
+ <password>public02</password>
+ <full-name>Public 02</full-name>
+ <email>public02@example.com</email>
+</record>
+<record type="user">
+ <short-name>public03</short-name>
+ <uid>50000000-0000-0000-0000-000000000003</uid>
+ <guid>50000000-0000-0000-0000-000000000003</guid>
+ <password>public03</password>
+ <full-name>Public 03</full-name>
+ <email>public03@example.com</email>
+</record>
+<record type="user">
+ <short-name>public04</short-name>
+ <uid>50000000-0000-0000-0000-000000000004</uid>
+ <guid>50000000-0000-0000-0000-000000000004</guid>
+ <password>public04</password>
+ <full-name>Public 04</full-name>
+ <email>public04@example.com</email>
+</record>
+<record type="user">
+ <short-name>public05</short-name>
+ <uid>50000000-0000-0000-0000-000000000005</uid>
+ <guid>50000000-0000-0000-0000-000000000005</guid>
+ <password>public05</password>
+ <full-name>Public 05</full-name>
+ <email>public05@example.com</email>
+</record>
+<record type="user">
+ <short-name>public06</short-name>
+ <uid>50000000-0000-0000-0000-000000000006</uid>
+ <guid>50000000-0000-0000-0000-000000000006</guid>
+ <password>public06</password>
+ <full-name>Public 06</full-name>
+ <email>public06@example.com</email>
+</record>
+<record type="user">
+ <short-name>public07</short-name>
+ <uid>50000000-0000-0000-0000-000000000007</uid>
+ <guid>50000000-0000-0000-0000-000000000007</guid>
+ <password>public07</password>
+ <full-name>Public 07</full-name>
+ <email>public07@example.com</email>
+</record>
+<record type="user">
+ <short-name>public08</short-name>
+ <uid>50000000-0000-0000-0000-000000000008</uid>
+ <guid>50000000-0000-0000-0000-000000000008</guid>
+ <password>public08</password>
+ <full-name>Public 08</full-name>
+ <email>public08@example.com</email>
+</record>
+<record type="user">
+ <short-name>public09</short-name>
+ <uid>50000000-0000-0000-0000-000000000009</uid>
+ <guid>50000000-0000-0000-0000-000000000009</guid>
+ <password>public09</password>
+ <full-name>Public 09</full-name>
+ <email>public09@example.com</email>
+</record>
+<record type="user">
+ <short-name>public10</short-name>
+ <uid>50000000-0000-0000-0000-000000000010</uid>
+ <guid>50000000-0000-0000-0000-000000000010</guid>
+ <password>public10</password>
+ <full-name>Public 10</full-name>
+ <email>public10@example.com</email>
+</record>
+<record type="group">
+ <short-name>group01</short-name>
+ <uid>20000000-0000-0000-0000-000000000001</uid>
+ <guid>20000000-0000-0000-0000-000000000001</guid>
+ <full-name>Group 01</full-name>
+ <email>group01@example.com</email>
+ <member-uid>10000000-0000-0000-0000-000000000001</member-uid>
+</record>
+<record type="group">
+ <short-name>group02</short-name>
+ <uid>20000000-0000-0000-0000-000000000002</uid>
+ <guid>20000000-0000-0000-0000-000000000002</guid>
+ <full-name>Group 02</full-name>
+ <email>group02@example.com</email>
+ <member-uid>10000000-0000-0000-0000-000000000006</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000007</member-uid>
+</record>
+<record type="group">
+ <short-name>group03</short-name>
+ <uid>20000000-0000-0000-0000-000000000003</uid>
+ <guid>20000000-0000-0000-0000-000000000003</guid>
+ <full-name>Group 03</full-name>
+ <email>group03@example.com</email>
+ <member-uid>10000000-0000-0000-0000-000000000008</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000009</member-uid>
+</record>
+<record type="group">
+ <short-name>group04</short-name>
+ <uid>20000000-0000-0000-0000-000000000004</uid>
+ <guid>20000000-0000-0000-0000-000000000004</guid>
+ <full-name>Group 04</full-name>
+ <email>group04@example.com</email>
+ <member-uid>20000000-0000-0000-0000-000000000002</member-uid>
+ <member-uid>20000000-0000-0000-0000-000000000003</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000010</member-uid>
+</record>
+<record type="group">
+ <short-name>group05</short-name>
+ <uid>20000000-0000-0000-0000-000000000005</uid>
+ <guid>20000000-0000-0000-0000-000000000005</guid>
+ <full-name>Group 05</full-name>
+ <email>group05@example.com</email>
+ <member-uid>20000000-0000-0000-0000-000000000006</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000020</member-uid>
+</record>
+<record type="group">
+ <short-name>group06</short-name>
+ <uid>20000000-0000-0000-0000-000000000006</uid>
+ <guid>20000000-0000-0000-0000-000000000006</guid>
+ <full-name>Group 06</full-name>
+ <email>group06@example.com</email>
+ <member-uid>10000000-0000-0000-0000-000000000021</member-uid>
+</record>
+<record type="group">
+ <short-name>group07</short-name>
+ <uid>20000000-0000-0000-0000-000000000007</uid>
+ <guid>20000000-0000-0000-0000-000000000007</guid>
+ <full-name>Group 07</full-name>
+ <email>group07@example.com</email>
+ <member-uid>10000000-0000-0000-0000-000000000022</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000023</member-uid>
+ <member-uid>10000000-0000-0000-0000-000000000024</member-uid>
+</record>
+<record type="group">
+ <short-name>group08</short-name>
+ <uid>20000000-0000-0000-0000-000000000008</uid>
+ <guid>20000000-0000-0000-0000-000000000008</guid>
+ <full-name>Group 08</full-name>
+ <email>group08@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group09</short-name>
+ <uid>20000000-0000-0000-0000-000000000009</uid>
+ <guid>20000000-0000-0000-0000-000000000009</guid>
+ <full-name>Group 09</full-name>
+ <email>group09@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group10</short-name>
+ <uid>20000000-0000-0000-0000-000000000010</uid>
+ <guid>20000000-0000-0000-0000-000000000010</guid>
+ <full-name>Group 10</full-name>
+ <email>group10@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group11</short-name>
+ <uid>20000000-0000-0000-0000-000000000011</uid>
+ <guid>20000000-0000-0000-0000-000000000011</guid>
+ <full-name>Group 11</full-name>
+ <email>group11@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group12</short-name>
+ <uid>20000000-0000-0000-0000-000000000012</uid>
+ <guid>20000000-0000-0000-0000-000000000012</guid>
+ <full-name>Group 12</full-name>
+ <email>group12@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group13</short-name>
+ <uid>20000000-0000-0000-0000-000000000013</uid>
+ <guid>20000000-0000-0000-0000-000000000013</guid>
+ <full-name>Group 13</full-name>
+ <email>group13@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group14</short-name>
+ <uid>20000000-0000-0000-0000-000000000014</uid>
+ <guid>20000000-0000-0000-0000-000000000014</guid>
+ <full-name>Group 14</full-name>
+ <email>group14@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group15</short-name>
+ <uid>20000000-0000-0000-0000-000000000015</uid>
+ <guid>20000000-0000-0000-0000-000000000015</guid>
+ <full-name>Group 15</full-name>
+ <email>group15@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group16</short-name>
+ <uid>20000000-0000-0000-0000-000000000016</uid>
+ <guid>20000000-0000-0000-0000-000000000016</guid>
+ <full-name>Group 16</full-name>
+ <email>group16@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group17</short-name>
+ <uid>20000000-0000-0000-0000-000000000017</uid>
+ <guid>20000000-0000-0000-0000-000000000017</guid>
+ <full-name>Group 17</full-name>
+ <email>group17@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group18</short-name>
+ <uid>20000000-0000-0000-0000-000000000018</uid>
+ <guid>20000000-0000-0000-0000-000000000018</guid>
+ <full-name>Group 18</full-name>
+ <email>group18@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group19</short-name>
+ <uid>20000000-0000-0000-0000-000000000019</uid>
+ <guid>20000000-0000-0000-0000-000000000019</guid>
+ <full-name>Group 19</full-name>
+ <email>group19@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group20</short-name>
+ <uid>20000000-0000-0000-0000-000000000020</uid>
+ <guid>20000000-0000-0000-0000-000000000020</guid>
+ <full-name>Group 20</full-name>
+ <email>group20@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group21</short-name>
+ <uid>20000000-0000-0000-0000-000000000021</uid>
+ <guid>20000000-0000-0000-0000-000000000021</guid>
+ <full-name>Group 21</full-name>
+ <email>group21@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group22</short-name>
+ <uid>20000000-0000-0000-0000-000000000022</uid>
+ <guid>20000000-0000-0000-0000-000000000022</guid>
+ <full-name>Group 22</full-name>
+ <email>group22@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group23</short-name>
+ <uid>20000000-0000-0000-0000-000000000023</uid>
+ <guid>20000000-0000-0000-0000-000000000023</guid>
+ <full-name>Group 23</full-name>
+ <email>group23@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group24</short-name>
+ <uid>20000000-0000-0000-0000-000000000024</uid>
+ <guid>20000000-0000-0000-0000-000000000024</guid>
+ <full-name>Group 24</full-name>
+ <email>group24@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group25</short-name>
+ <uid>20000000-0000-0000-0000-000000000025</uid>
+ <guid>20000000-0000-0000-0000-000000000025</guid>
+ <full-name>Group 25</full-name>
+ <email>group25@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group26</short-name>
+ <uid>20000000-0000-0000-0000-000000000026</uid>
+ <guid>20000000-0000-0000-0000-000000000026</guid>
+ <full-name>Group 26</full-name>
+ <email>group26@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group27</short-name>
+ <uid>20000000-0000-0000-0000-000000000027</uid>
+ <guid>20000000-0000-0000-0000-000000000027</guid>
+ <full-name>Group 27</full-name>
+ <email>group27@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group28</short-name>
+ <uid>20000000-0000-0000-0000-000000000028</uid>
+ <guid>20000000-0000-0000-0000-000000000028</guid>
+ <full-name>Group 28</full-name>
+ <email>group28@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group29</short-name>
+ <uid>20000000-0000-0000-0000-000000000029</uid>
+ <guid>20000000-0000-0000-0000-000000000029</guid>
+ <full-name>Group 29</full-name>
+ <email>group29@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group30</short-name>
+ <uid>20000000-0000-0000-0000-000000000030</uid>
+ <guid>20000000-0000-0000-0000-000000000030</guid>
+ <full-name>Group 30</full-name>
+ <email>group30@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group31</short-name>
+ <uid>20000000-0000-0000-0000-000000000031</uid>
+ <guid>20000000-0000-0000-0000-000000000031</guid>
+ <full-name>Group 31</full-name>
+ <email>group31@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group32</short-name>
+ <uid>20000000-0000-0000-0000-000000000032</uid>
+ <guid>20000000-0000-0000-0000-000000000032</guid>
+ <full-name>Group 32</full-name>
+ <email>group32@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group33</short-name>
+ <uid>20000000-0000-0000-0000-000000000033</uid>
+ <guid>20000000-0000-0000-0000-000000000033</guid>
+ <full-name>Group 33</full-name>
+ <email>group33@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group34</short-name>
+ <uid>20000000-0000-0000-0000-000000000034</uid>
+ <guid>20000000-0000-0000-0000-000000000034</guid>
+ <full-name>Group 34</full-name>
+ <email>group34@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group35</short-name>
+ <uid>20000000-0000-0000-0000-000000000035</uid>
+ <guid>20000000-0000-0000-0000-000000000035</guid>
+ <full-name>Group 35</full-name>
+ <email>group35@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group36</short-name>
+ <uid>20000000-0000-0000-0000-000000000036</uid>
+ <guid>20000000-0000-0000-0000-000000000036</guid>
+ <full-name>Group 36</full-name>
+ <email>group36@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group37</short-name>
+ <uid>20000000-0000-0000-0000-000000000037</uid>
+ <guid>20000000-0000-0000-0000-000000000037</guid>
+ <full-name>Group 37</full-name>
+ <email>group37@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group38</short-name>
+ <uid>20000000-0000-0000-0000-000000000038</uid>
+ <guid>20000000-0000-0000-0000-000000000038</guid>
+ <full-name>Group 38</full-name>
+ <email>group38@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group39</short-name>
+ <uid>20000000-0000-0000-0000-000000000039</uid>
+ <guid>20000000-0000-0000-0000-000000000039</guid>
+ <full-name>Group 39</full-name>
+ <email>group39@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group40</short-name>
+ <uid>20000000-0000-0000-0000-000000000040</uid>
+ <guid>20000000-0000-0000-0000-000000000040</guid>
+ <full-name>Group 40</full-name>
+ <email>group40@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group41</short-name>
+ <uid>20000000-0000-0000-0000-000000000041</uid>
+ <guid>20000000-0000-0000-0000-000000000041</guid>
+ <full-name>Group 41</full-name>
+ <email>group41@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group42</short-name>
+ <uid>20000000-0000-0000-0000-000000000042</uid>
+ <guid>20000000-0000-0000-0000-000000000042</guid>
+ <full-name>Group 42</full-name>
+ <email>group42@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group43</short-name>
+ <uid>20000000-0000-0000-0000-000000000043</uid>
+ <guid>20000000-0000-0000-0000-000000000043</guid>
+ <full-name>Group 43</full-name>
+ <email>group43@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group44</short-name>
+ <uid>20000000-0000-0000-0000-000000000044</uid>
+ <guid>20000000-0000-0000-0000-000000000044</guid>
+ <full-name>Group 44</full-name>
+ <email>group44@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group45</short-name>
+ <uid>20000000-0000-0000-0000-000000000045</uid>
+ <guid>20000000-0000-0000-0000-000000000045</guid>
+ <full-name>Group 45</full-name>
+ <email>group45@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group46</short-name>
+ <uid>20000000-0000-0000-0000-000000000046</uid>
+ <guid>20000000-0000-0000-0000-000000000046</guid>
+ <full-name>Group 46</full-name>
+ <email>group46@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group47</short-name>
+ <uid>20000000-0000-0000-0000-000000000047</uid>
+ <guid>20000000-0000-0000-0000-000000000047</guid>
+ <full-name>Group 47</full-name>
+ <email>group47@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group48</short-name>
+ <uid>20000000-0000-0000-0000-000000000048</uid>
+ <guid>20000000-0000-0000-0000-000000000048</guid>
+ <full-name>Group 48</full-name>
+ <email>group48@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group49</short-name>
+ <uid>20000000-0000-0000-0000-000000000049</uid>
+ <guid>20000000-0000-0000-0000-000000000049</guid>
+ <full-name>Group 49</full-name>
+ <email>group49@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group50</short-name>
+ <uid>20000000-0000-0000-0000-000000000050</uid>
+ <guid>20000000-0000-0000-0000-000000000050</guid>
+ <full-name>Group 50</full-name>
+ <email>group50@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group51</short-name>
+ <uid>20000000-0000-0000-0000-000000000051</uid>
+ <guid>20000000-0000-0000-0000-000000000051</guid>
+ <full-name>Group 51</full-name>
+ <email>group51@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group52</short-name>
+ <uid>20000000-0000-0000-0000-000000000052</uid>
+ <guid>20000000-0000-0000-0000-000000000052</guid>
+ <full-name>Group 52</full-name>
+ <email>group52@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group53</short-name>
+ <uid>20000000-0000-0000-0000-000000000053</uid>
+ <guid>20000000-0000-0000-0000-000000000053</guid>
+ <full-name>Group 53</full-name>
+ <email>group53@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group54</short-name>
+ <uid>20000000-0000-0000-0000-000000000054</uid>
+ <guid>20000000-0000-0000-0000-000000000054</guid>
+ <full-name>Group 54</full-name>
+ <email>group54@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group55</short-name>
+ <uid>20000000-0000-0000-0000-000000000055</uid>
+ <guid>20000000-0000-0000-0000-000000000055</guid>
+ <full-name>Group 55</full-name>
+ <email>group55@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group56</short-name>
+ <uid>20000000-0000-0000-0000-000000000056</uid>
+ <guid>20000000-0000-0000-0000-000000000056</guid>
+ <full-name>Group 56</full-name>
+ <email>group56@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group57</short-name>
+ <uid>20000000-0000-0000-0000-000000000057</uid>
+ <guid>20000000-0000-0000-0000-000000000057</guid>
+ <full-name>Group 57</full-name>
+ <email>group57@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group58</short-name>
+ <uid>20000000-0000-0000-0000-000000000058</uid>
+ <guid>20000000-0000-0000-0000-000000000058</guid>
+ <full-name>Group 58</full-name>
+ <email>group58@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group59</short-name>
+ <uid>20000000-0000-0000-0000-000000000059</uid>
+ <guid>20000000-0000-0000-0000-000000000059</guid>
+ <full-name>Group 59</full-name>
+ <email>group59@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group60</short-name>
+ <uid>20000000-0000-0000-0000-000000000060</uid>
+ <guid>20000000-0000-0000-0000-000000000060</guid>
+ <full-name>Group 60</full-name>
+ <email>group60@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group61</short-name>
+ <uid>20000000-0000-0000-0000-000000000061</uid>
+ <guid>20000000-0000-0000-0000-000000000061</guid>
+ <full-name>Group 61</full-name>
+ <email>group61@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group62</short-name>
+ <uid>20000000-0000-0000-0000-000000000062</uid>
+ <guid>20000000-0000-0000-0000-000000000062</guid>
+ <full-name>Group 62</full-name>
+ <email>group62@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group63</short-name>
+ <uid>20000000-0000-0000-0000-000000000063</uid>
+ <guid>20000000-0000-0000-0000-000000000063</guid>
+ <full-name>Group 63</full-name>
+ <email>group63@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group64</short-name>
+ <uid>20000000-0000-0000-0000-000000000064</uid>
+ <guid>20000000-0000-0000-0000-000000000064</guid>
+ <full-name>Group 64</full-name>
+ <email>group64@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group65</short-name>
+ <uid>20000000-0000-0000-0000-000000000065</uid>
+ <guid>20000000-0000-0000-0000-000000000065</guid>
+ <full-name>Group 65</full-name>
+ <email>group65@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group66</short-name>
+ <uid>20000000-0000-0000-0000-000000000066</uid>
+ <guid>20000000-0000-0000-0000-000000000066</guid>
+ <full-name>Group 66</full-name>
+ <email>group66@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group67</short-name>
+ <uid>20000000-0000-0000-0000-000000000067</uid>
+ <guid>20000000-0000-0000-0000-000000000067</guid>
+ <full-name>Group 67</full-name>
+ <email>group67@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group68</short-name>
+ <uid>20000000-0000-0000-0000-000000000068</uid>
+ <guid>20000000-0000-0000-0000-000000000068</guid>
+ <full-name>Group 68</full-name>
+ <email>group68@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group69</short-name>
+ <uid>20000000-0000-0000-0000-000000000069</uid>
+ <guid>20000000-0000-0000-0000-000000000069</guid>
+ <full-name>Group 69</full-name>
+ <email>group69@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group70</short-name>
+ <uid>20000000-0000-0000-0000-000000000070</uid>
+ <guid>20000000-0000-0000-0000-000000000070</guid>
+ <full-name>Group 70</full-name>
+ <email>group70@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group71</short-name>
+ <uid>20000000-0000-0000-0000-000000000071</uid>
+ <guid>20000000-0000-0000-0000-000000000071</guid>
+ <full-name>Group 71</full-name>
+ <email>group71@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group72</short-name>
+ <uid>20000000-0000-0000-0000-000000000072</uid>
+ <guid>20000000-0000-0000-0000-000000000072</guid>
+ <full-name>Group 72</full-name>
+ <email>group72@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group73</short-name>
+ <uid>20000000-0000-0000-0000-000000000073</uid>
+ <guid>20000000-0000-0000-0000-000000000073</guid>
+ <full-name>Group 73</full-name>
+ <email>group73@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group74</short-name>
+ <uid>20000000-0000-0000-0000-000000000074</uid>
+ <guid>20000000-0000-0000-0000-000000000074</guid>
+ <full-name>Group 74</full-name>
+ <email>group74@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group75</short-name>
+ <uid>20000000-0000-0000-0000-000000000075</uid>
+ <guid>20000000-0000-0000-0000-000000000075</guid>
+ <full-name>Group 75</full-name>
+ <email>group75@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group76</short-name>
+ <uid>20000000-0000-0000-0000-000000000076</uid>
+ <guid>20000000-0000-0000-0000-000000000076</guid>
+ <full-name>Group 76</full-name>
+ <email>group76@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group77</short-name>
+ <uid>20000000-0000-0000-0000-000000000077</uid>
+ <guid>20000000-0000-0000-0000-000000000077</guid>
+ <full-name>Group 77</full-name>
+ <email>group77@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group78</short-name>
+ <uid>20000000-0000-0000-0000-000000000078</uid>
+ <guid>20000000-0000-0000-0000-000000000078</guid>
+ <full-name>Group 78</full-name>
+ <email>group78@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group79</short-name>
+ <uid>20000000-0000-0000-0000-000000000079</uid>
+ <guid>20000000-0000-0000-0000-000000000079</guid>
+ <full-name>Group 79</full-name>
+ <email>group79@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group80</short-name>
+ <uid>20000000-0000-0000-0000-000000000080</uid>
+ <guid>20000000-0000-0000-0000-000000000080</guid>
+ <full-name>Group 80</full-name>
+ <email>group80@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group81</short-name>
+ <uid>20000000-0000-0000-0000-000000000081</uid>
+ <guid>20000000-0000-0000-0000-000000000081</guid>
+ <full-name>Group 81</full-name>
+ <email>group81@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group82</short-name>
+ <uid>20000000-0000-0000-0000-000000000082</uid>
+ <guid>20000000-0000-0000-0000-000000000082</guid>
+ <full-name>Group 82</full-name>
+ <email>group82@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group83</short-name>
+ <uid>20000000-0000-0000-0000-000000000083</uid>
+ <guid>20000000-0000-0000-0000-000000000083</guid>
+ <full-name>Group 83</full-name>
+ <email>group83@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group84</short-name>
+ <uid>20000000-0000-0000-0000-000000000084</uid>
+ <guid>20000000-0000-0000-0000-000000000084</guid>
+ <full-name>Group 84</full-name>
+ <email>group84@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group85</short-name>
+ <uid>20000000-0000-0000-0000-000000000085</uid>
+ <guid>20000000-0000-0000-0000-000000000085</guid>
+ <full-name>Group 85</full-name>
+ <email>group85@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group86</short-name>
+ <uid>20000000-0000-0000-0000-000000000086</uid>
+ <guid>20000000-0000-0000-0000-000000000086</guid>
+ <full-name>Group 86</full-name>
+ <email>group86@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group87</short-name>
+ <uid>20000000-0000-0000-0000-000000000087</uid>
+ <guid>20000000-0000-0000-0000-000000000087</guid>
+ <full-name>Group 87</full-name>
+ <email>group87@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group88</short-name>
+ <uid>20000000-0000-0000-0000-000000000088</uid>
+ <guid>20000000-0000-0000-0000-000000000088</guid>
+ <full-name>Group 88</full-name>
+ <email>group88@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group89</short-name>
+ <uid>20000000-0000-0000-0000-000000000089</uid>
+ <guid>20000000-0000-0000-0000-000000000089</guid>
+ <full-name>Group 89</full-name>
+ <email>group89@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group90</short-name>
+ <uid>20000000-0000-0000-0000-000000000090</uid>
+ <guid>20000000-0000-0000-0000-000000000090</guid>
+ <full-name>Group 90</full-name>
+ <email>group90@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group91</short-name>
+ <uid>20000000-0000-0000-0000-000000000091</uid>
+ <guid>20000000-0000-0000-0000-000000000091</guid>
+ <full-name>Group 91</full-name>
+ <email>group91@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group92</short-name>
+ <uid>20000000-0000-0000-0000-000000000092</uid>
+ <guid>20000000-0000-0000-0000-000000000092</guid>
+ <full-name>Group 92</full-name>
+ <email>group92@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group93</short-name>
+ <uid>20000000-0000-0000-0000-000000000093</uid>
+ <guid>20000000-0000-0000-0000-000000000093</guid>
+ <full-name>Group 93</full-name>
+ <email>group93@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group94</short-name>
+ <uid>20000000-0000-0000-0000-000000000094</uid>
+ <guid>20000000-0000-0000-0000-000000000094</guid>
+ <full-name>Group 94</full-name>
+ <email>group94@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group95</short-name>
+ <uid>20000000-0000-0000-0000-000000000095</uid>
+ <guid>20000000-0000-0000-0000-000000000095</guid>
+ <full-name>Group 95</full-name>
+ <email>group95@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group96</short-name>
+ <uid>20000000-0000-0000-0000-000000000096</uid>
+ <guid>20000000-0000-0000-0000-000000000096</guid>
+ <full-name>Group 96</full-name>
+ <email>group96@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group97</short-name>
+ <uid>20000000-0000-0000-0000-000000000097</uid>
+ <guid>20000000-0000-0000-0000-000000000097</guid>
+ <full-name>Group 97</full-name>
+ <email>group97@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group98</short-name>
+ <uid>20000000-0000-0000-0000-000000000098</uid>
+ <guid>20000000-0000-0000-0000-000000000098</guid>
+ <full-name>Group 98</full-name>
+ <email>group98@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group99</short-name>
+ <uid>20000000-0000-0000-0000-000000000099</uid>
+ <guid>20000000-0000-0000-0000-000000000099</guid>
+ <full-name>Group 99</full-name>
+ <email>group99@example.com</email>
+
+</record>
+<record type="group">
+ <short-name>group100</short-name>
+ <uid>20000000-0000-0000-0000-000000000100</uid>
+ <guid>20000000-0000-0000-0000-000000000100</guid>
+ <full-name>Group 100</full-name>
+ <email>group100@example.com</email>
+
+</record>
+</directory>
</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"> <?xml version="1.0" encoding="utf-8"?>
</span><del>-<!DOCTYPE augments SYSTEM "augments.dtd">
</del><span class="cx">
</span><ins>+<!--
+Copyright (c) 2006-2014 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+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 "AS IS" 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.
+ -->
+
</ins><span class="cx"> <augments>
</span><del>- <record>
</del><ins>+<record>
</ins><span class="cx"> <uid>Default</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- </record>
- <record repeat="10">
- <uid>location%02d</uid>
- <enable>true</enable>
</del><ins>+</record>
+<record>
+ <uid>Default-Location</uid>
</ins><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <auto-schedule>true</auto-schedule>
- </record>
- <record repeat="4">
- <uid>resource%02d</uid>
- <enable>true</enable>
</del><ins>+ <auto-schedule-mode>automatic</auto-schedule-mode>
+</record>
+<record>
+ <uid>Default-Resource</uid>
</ins><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <auto-schedule>true</auto-schedule>
- </record>
- <record>
- <uid>resource05</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <auto-schedule>true</auto-schedule>
</del><ins>+ <auto-schedule-mode>automatic</auto-schedule-mode>
+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000005</uid>
</ins><span class="cx"> <auto-schedule-mode>none</auto-schedule-mode>
</span><del>- </record>
- <record>
- <uid>resource06</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
</del><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>accept-always</auto-schedule-mode>
- </record>
- <record>
- <uid>resource07</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000006</uid>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
</ins><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>decline-always</auto-schedule-mode>
- </record>
- <record>
- <uid>resource08</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><del>- <enable-addressbook>true</enable-addressbook>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>accept-if-free</auto-schedule-mode>
- </record>
- <record>
- <uid>resource09</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>decline-if-busy</auto-schedule-mode>
- </record>
- <record>
- <uid>resource10</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>automatic</auto-schedule-mode>
- </record>
- <record>
- <uid>resource11</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <auto-schedule>true</auto-schedule>
</del><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000007</uid>
</ins><span class="cx"> <auto-schedule-mode>decline-always</auto-schedule-mode>
</span><del>- <auto-accept-group>group01</auto-accept-group>
- </record>
- <record repeat="10">
- <uid>group%02d</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>disabledgroup</uid>
- <enable>false</enable>
- </record>
- <record>
- <uid>delegatedroom</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>false</enable-addressbook>
- <auto-schedule>false</auto-schedule>
- </record>
- <record>
- <uid>03DFF660-8BCC-4198-8588-DD77F776F518</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
</del><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- </record>
- <record>
- <uid>80689D41-DAF8-4189-909C-DB017B271892</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000008</uid>
+ <auto-schedule-mode>accept-if-free</auto-schedule-mode>
</ins><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
- <record>
- <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000009</uid>
+ <auto-schedule-mode>decline-if-busy</auto-schedule-mode>
</ins><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- <auto-accept-group>group01</auto-accept-group>
- </record>
- <record>
- <uid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000010</uid>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
</ins><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- </record>
- <record>
- <uid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><ins>+</record>
+<record>
+ <uid>40000000-0000-0000-0000-000000000011</uid>
+ <auto-schedule-mode>decline-always</auto-schedule-mode>
</ins><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><del>- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- </record>
- <record>
- <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><del>- <enable-addressbook>true</enable-addressbook>
- <enable-login>true</enable-login>
- <auto-schedule>false</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
- <record>
- <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <enable-login>true</enable-login>
- <auto-schedule>false</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
- <record>
- <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <enable-login>true</enable-login>
- <auto-schedule>false</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
- <record>
- <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
- <record>
- <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <enable-addressbook>true</enable-addressbook>
- <enable-login>true</enable-login>
- <auto-schedule>true</auto-schedule>
- <auto-schedule-mode>default</auto-schedule-mode>
- </record>
</del><ins>+ <auto-accept-group>20000000-0000-0000-0000-000000000001</auto-accept-group>
+</record>
</ins><span class="cx"> </augments>
</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 = """<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2014 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+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 "AS IS" 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.
+ -->
+
+"""
+
+# The uids and guids for CDT test accounts are the same
+# The short name is of the form userNN
+USERGUIDS = "10000000-0000-0000-0000-000000000%03d"
+GROUPGUIDS = "20000000-0000-0000-0000-000000000%03d"
+LOCATIONGUIDS = "30000000-0000-0000-0000-000000000%03d"
+RESOURCEGUIDS = "40000000-0000-0000-0000-000000000%03d"
+PUBLICGUIDS = "50000000-0000-0000-0000-0000000000%02d"
+
+# accounts-test.xml
+
+out = file("accounts-test.xml", "w")
+out.write(prefix)
+out.write('<directory realm="Test Realm">\n')
+
+
+for uid, fullName, guid in (
+ ("admin", "Super User", "0C8BDE62-E600-4696-83D3-8B5ECABDFD2E"),
+ ("apprentice", "Apprentice Super User", "29B6C503-11DF-43EC-8CCA-40C7003149CE"),
+ ("i18nuser", u"\u307e\u3060".encode("utf-8"), "860B3EE9-6D7C-4296-9639-E6B998074A78"),
+):
+ out.write("""<record>
+ <uid>{guid}</uid>
+ <guid>{guid}</guid>
+ <short-name>{uid}</short-name>
+ <password>{uid}</password>
+ <full-name>{fullName}</full-name>
+ <email>{uid}@example.com</email>
+</record>
+""".format(uid=uid, guid=guid, fullName=fullName))
+
+# user01-100
+for i in xrange(1, 101):
+ out.write("""<record type="user">
+ <short-name>user%02d</short-name>
+ <uid>%s</uid>
+ <guid>%s</guid>
+ <password>user%02d</password>
+ <full-name>User %02d</full-name>
+ <email>user%02d@example.com</email>
+</record>
+""" % (i, USERGUIDS % i, USERGUIDS % i, i, i, i))
+
+# public01-10
+for i in xrange(1, 11):
+ out.write("""<record type="user">
+ <short-name>public%02d</short-name>
+ <uid>%s</uid>
+ <guid>%s</guid>
+ <password>public%02d</password>
+ <full-name>Public %02d</full-name>
+ <email>public%02d@example.com</email>
+</record>
+""" % (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("<member-uid>{}</member-uid>".format(uid))
+ memberString = "\n ".join(memberElements)
+ else:
+ memberString = ""
+
+ out.write("""<record type="group">
+ <short-name>group%02d</short-name>
+ <uid>%s</uid>
+ <guid>%s</guid>
+ <full-name>Group %02d</full-name>
+ <email>group%02d@example.com</email>
+ %s
+</record>
+""" % (i, GROUPGUIDS % i, GROUPGUIDS % i, i, i, memberString))
+
+out.write("</directory>\n")
+out.close()
+
+
+# resources-test.xml
+
+out = file("resources-test.xml", "w")
+out.write(prefix)
+out.write('<directory realm="Test Realm">\n')
+
+out.write("""
+ <record type="location">
+ <short-name>pretend</short-name>
+ <uid>pretend</uid>
+ <full-name>Pretend Conference Room</full-name>
+ <associated-address>il1</associated-address>
+ </record>
+ <record type="address">
+ <short-name>il1</short-name>
+ <uid>il1</uid>
+ <full-name>IL1</full-name>
+ <street-address>1 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.331741,-122.030333</geographic-location>
+ </record>
+ <record type="location">
+ <short-name>fantastic</short-name>
+ <uid>fantastic</uid>
+ <full-name>Fantastic Conference Room</full-name>
+ <associated-address>il2</associated-address>
+ </record>
+ <record type="address">
+ <short-name>il2</short-name>
+ <uid>il2</uid>
+ <full-name>IL2</full-name>
+ <street-address>2 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.332633,-122.030502</geographic-location>
+ </record>
+ <record type="location">
+ <short-name>delegatedroom</short-name>
+ <uid>delegatedroom</uid>
+ <full-name>Delegated Conference Room</full-name>
+ </record>
+
+""")
+
+for i in xrange(1, 101):
+ out.write("""<record type="location">
+ <short-name>location%02d</short-name>
+ <uid>%s</uid>
+ <guid>%s</guid>
+ <full-name>Location %02d</full-name>
+</record>
+""" % (i, LOCATIONGUIDS % i, LOCATIONGUIDS % i, i))
+
+
+for i in xrange(1, 101):
+ out.write("""<record type="resource">
+ <short-name>resource%02d</short-name>
+ <uid>%s</uid>
+ <guid>%s</guid>
+ <full-name>Resource %02d</full-name>
+</record>
+""" % (i, RESOURCEGUIDS % i, RESOURCEGUIDS % i, i))
+
+out.write("</directory>\n")
+out.close()
+
+
+# augments-test.xml
+
+out = file("augments-test.xml", "w")
+out.write(prefix)
+out.write("<augments>\n")
+
+augments = (
+ # resource05
+ (RESOURCEGUIDS % 5, {
+ "auto-schedule-mode": "none",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource06
+ (RESOURCEGUIDS % 6, {
+ "auto-schedule-mode": "accept-always",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource07
+ (RESOURCEGUIDS % 7, {
+ "auto-schedule-mode": "decline-always",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource08
+ (RESOURCEGUIDS % 8, {
+ "auto-schedule-mode": "accept-if-free",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource09
+ (RESOURCEGUIDS % 9, {
+ "auto-schedule-mode": "decline-if-busy",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource10
+ (RESOURCEGUIDS % 10, {
+ "auto-schedule-mode": "automatic",
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+ # resource11
+ (RESOURCEGUIDS % 11, {
+ "auto-schedule-mode": "decline-always",
+ "auto-accept-group": GROUPGUIDS % 1,
+ "enable-calendar": "true",
+ "enable-addressbook": "true",
+ }),
+)
+
+out.write("""<record>
+ <uid>Default</uid>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+</record>
+""")
+
+out.write("""<record>
+ <uid>Default-Location</uid>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+</record>
+""")
+
+out.write("""<record>
+ <uid>Default-Resource</uid>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+</record>
+""")
+
+for uid, settings in augments:
+ elements = []
+ for key, value in settings.iteritems():
+ elements.append("<{key}>{value}</{key}>".format(key=key, value=value))
+ elementsString = "\n ".join(elements)
+
+ out.write("""<record>
+ <uid>{uid}</uid>
+ {elements}
+</record>
+""".format(uid=uid, elements=elementsString))
+
+out.write("</augments>\n")
+out.close()
+
+
+# proxies-test.xml
+
+out = file("proxies-test.xml", "w")
+out.write(prefix)
+out.write("<proxies>\n")
+
+proxies = (
+ (RESOURCEGUIDS % 1, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 2, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 3, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 4, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 5, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 6, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 7, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 8, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 9, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ (RESOURCEGUIDS % 10, {
+ "proxies": (USERGUIDS % 1,),
+ "read-only-proxies": (USERGUIDS % 3,),
+ }),
+ ("delegatedroom", {
+ "proxies": (GROUPGUIDS % 5,),
+ "read-only-proxies": (),
+ }),
+)
+
+for uid, settings in proxies:
+ elements = []
+ for key, values in settings.iteritems():
+ elements.append("<{key}>".format(key=key))
+ for value in values:
+ elements.append("<member>{value}</member>".format(value=value))
+ elements.append("</{key}>".format(key=key))
+ elementsString = "\n ".join(elements)
+
+ out.write("""<record>
+ <guid>{uid}</guid>
+ {elements}
+</record>
+""".format(uid=uid, elements=elementsString))
+
+out.write("</proxies>\n")
+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"> <?xml version="1.0" encoding="utf-8"?>
</span><span class="cx">
</span><span class="cx"> <!--
</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 "License");
</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"> -->
</span><span class="cx">
</span><del>-<!DOCTYPE proxies SYSTEM "proxies.dtd">
-
</del><span class="cx"> <proxies>
</span><del>- <record repeat="10">
- <guid>resource%02d</guid>
</del><ins>+<record>
+ <guid>40000000-0000-0000-0000-000000000001</guid>
</ins><span class="cx"> <proxies>
</span><del>- <member>user01</member>
</del><ins>+ <member>10000000-0000-0000-0000-000000000001</member>
</ins><span class="cx"> </proxies>
</span><span class="cx"> <read-only-proxies>
</span><del>- <member>user03</member>
</del><ins>+ <member>10000000-0000-0000-0000-000000000003</member>
</ins><span class="cx"> </read-only-proxies>
</span><del>- </record>
- <record>
</del><ins>+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000002</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000003</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000004</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000005</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000006</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000007</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000008</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000009</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
+ <guid>40000000-0000-0000-0000-000000000010</guid>
+ <proxies>
+ <member>10000000-0000-0000-0000-000000000001</member>
+ </proxies>
+ <read-only-proxies>
+ <member>10000000-0000-0000-0000-000000000003</member>
+ </read-only-proxies>
+</record>
+<record>
</ins><span class="cx"> <guid>delegatedroom</guid>
</span><span class="cx"> <proxies>
</span><del>- <member>group05</member>
</del><ins>+ <member>20000000-0000-0000-0000-000000000005</member>
</ins><span class="cx"> </proxies>
</span><span class="cx"> <read-only-proxies>
</span><del>- <member>group07</member>
</del><span class="cx"> </read-only-proxies>
</span><del>- </record>
</del><ins>+</record>
</ins><span class="cx"> </proxies>
</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>-<accounts realm="Test Realm">
- <location repeat="10">
- <uid>location%02d</uid>
- <guid>location%02d</guid>
- <password>location%02d</password>
- <name>Room %02d</name>
- </location>
- <resource repeat="20">
- <uid>resource%02d</uid>
- <guid>resource%02d</guid>
- <password>resource%02d</password>
- <name>Resource %02d</name>
- </resource>
- <location>
</del><ins>+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2014 Apple Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+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 "AS IS" 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.
+ -->
+
+<directory realm="Test Realm">
+
+ <record type="location">
+ <short-name>pretend</short-name>
+ <uid>pretend</uid>
+ <full-name>Pretend Conference Room</full-name>
+ <associated-address>il1</associated-address>
+ </record>
+ <record type="address">
+ <short-name>il1</short-name>
+ <uid>il1</uid>
+ <full-name>IL1</full-name>
+ <street-address>1 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.331741,-122.030333</geographic-location>
+ </record>
+ <record type="location">
+ <short-name>fantastic</short-name>
</ins><span class="cx"> <uid>fantastic</uid>
</span><del>- <guid>4D66A20A-1437-437D-8069-2F14E8322234</guid>
- <name>Fantastic Conference Room</name>
- <extras>
- <associatedAddress>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</associatedAddress>
- </extras>
- </location>
- <location>
- <uid>jupiter</uid>
- <guid>jupiter</guid>
- <name>Jupiter Conference Room, Building 2, 1st Floor</name>
- </location>
- <location>
- <uid>uranus</uid>
- <guid>uranus</guid>
- <name>Uranus Conference Room, Building 3, 1st Floor</name>
- </location>
- <location>
- <uid>morgensroom</uid>
- <guid>03DFF660-8BCC-4198-8588-DD77F776F518</guid>
- <name>Morgen's Room</name>
- </location>
- <location>
- <uid>mercury</uid>
- <guid>mercury</guid>
- <name>Mercury Conference Room, Building 1, 2nd Floor</name>
- </location>
- <location>
</del><ins>+ <full-name>Fantastic Conference Room</full-name>
+ <associated-address>il2</associated-address>
+ </record>
+ <record type="address">
+ <short-name>il2</short-name>
+ <uid>il2</uid>
+ <full-name>IL2</full-name>
+ <street-address>2 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.332633,-122.030502</geographic-location>
+ </record>
+ <record type="location">
+ <short-name>delegatedroom</short-name>
</ins><span class="cx"> <uid>delegatedroom</uid>
</span><del>- <guid>delegatedroom</guid>
- <name>Delegated Conference Room</name>
- </location>
- <location>
- <uid>mars</uid>
- <guid>redplanet</guid>
- <name>Mars Conference Room, Building 1, 1st Floor</name>
- </location>
- <location>
- <uid>sharissroom</uid>
- <guid>80689D41-DAF8-4189-909C-DB017B271892</guid>
- <name>Shari's Room</name>
- <extras>
- <associatedAddress>6F9EE33B-78F6-481B-9289-3D0812FF0D64</associatedAddress>
- </extras>
- </location>
- <location>
- <uid>pluto</uid>
- <guid>pluto</guid>
- <name>Pluto Conference Room, Building 2, 1st Floor</name>
- </location>
- <location>
- <uid>saturn</uid>
- <guid>saturn</guid>
- <name>Saturn Conference Room, Building 2, 1st Floor</name>
- </location>
- <location>
- <uid>pretend</uid>
- <guid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</guid>
- <name>Pretend Conference Room</name>
- <extras>
- <associatedAddress>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</associatedAddress>
- </extras>
- </location>
- <location>
- <uid>neptune</uid>
- <guid>neptune</guid>
- <name>Neptune Conference Room, Building 2, 1st Floor</name>
- </location>
- <location>
- <uid>Earth</uid>
- <guid>Earth</guid>
- <name>Earth Conference Room, Building 1, 1st Floor</name>
- </location>
- <location>
- <uid>venus</uid>
- <guid>venus</guid>
- <name>Venus Conference Room, Building 1, 2nd Floor</name>
- </location>
- <resource>
- <uid>sharisotherresource</uid>
- <guid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</guid>
- <name>Shari's Other Resource</name>
- </resource>
- <resource>
- <uid>sharisresource</uid>
- <guid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</guid>
- <name>Shari's Resource</name>
- </resource>
- <resource>
- <uid>sharisotherresource1</uid>
- <guid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</guid>
- <name>Shari's Other Resource1</name>
- </resource>
- <address>
- <uid>testaddress1</uid>
- <guid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</guid>
- <name>Test Address One</name>
- <extras>
- <streetAddress>20300 Stevens Creek Blvd, Cupertino, CA 95014</streetAddress>
- <geo>37.322281,-122.028345</geo>
- </extras>
- </address>
- <address>
- <uid>il2</uid>
- <guid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</guid>
- <name>IL2</name>
- <extras>
- <streetAddress>2 Infinite Loop, Cupertino, CA 95014</streetAddress>
- <geo>37.332633,-122.030502</geo>
- </extras>
- </address>
- <address>
- <uid>il1</uid>
- <guid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</guid>
- <name>IL1</name>
- <extras>
- <streetAddress>1 Infinite Loop, Cupertino, CA 95014</streetAddress>
- <geo>37.331741,-122.030333</geo>
- </extras>
- </address>
-</accounts>
</del><ins>+ <full-name>Delegated Conference Room</full-name>
+ </record>
+
+<record type="location">
+ <short-name>location01</short-name>
+ <uid>30000000-0000-0000-0000-000000000001</uid>
+ <guid>30000000-0000-0000-0000-000000000001</guid>
+ <full-name>Location 01</full-name>
+</record>
+<record type="location">
+ <short-name>location02</short-name>
+ <uid>30000000-0000-0000-0000-000000000002</uid>
+ <guid>30000000-0000-0000-0000-000000000002</guid>
+ <full-name>Location 02</full-name>
+</record>
+<record type="location">
+ <short-name>location03</short-name>
+ <uid>30000000-0000-0000-0000-000000000003</uid>
+ <guid>30000000-0000-0000-0000-000000000003</guid>
+ <full-name>Location 03</full-name>
+</record>
+<record type="location">
+ <short-name>location04</short-name>
+ <uid>30000000-0000-0000-0000-000000000004</uid>
+ <guid>30000000-0000-0000-0000-000000000004</guid>
+ <full-name>Location 04</full-name>
+</record>
+<record type="location">
+ <short-name>location05</short-name>
+ <uid>30000000-0000-0000-0000-000000000005</uid>
+ <guid>30000000-0000-0000-0000-000000000005</guid>
+ <full-name>Location 05</full-name>
+</record>
+<record type="location">
+ <short-name>location06</short-name>
+ <uid>30000000-0000-0000-0000-000000000006</uid>
+ <guid>30000000-0000-0000-0000-000000000006</guid>
+ <full-name>Location 06</full-name>
+</record>
+<record type="location">
+ <short-name>location07</short-name>
+ <uid>30000000-0000-0000-0000-000000000007</uid>
+ <guid>30000000-0000-0000-0000-000000000007</guid>
+ <full-name>Location 07</full-name>
+</record>
+<record type="location">
+ <short-name>location08</short-name>
+ <uid>30000000-0000-0000-0000-000000000008</uid>
+ <guid>30000000-0000-0000-0000-000000000008</guid>
+ <full-name>Location 08</full-name>
+</record>
+<record type="location">
+ <short-name>location09</short-name>
+ <uid>30000000-0000-0000-0000-000000000009</uid>
+ <guid>30000000-0000-0000-0000-000000000009</guid>
+ <full-name>Location 09</full-name>
+</record>
+<record type="location">
+ <short-name>location10</short-name>
+ <uid>30000000-0000-0000-0000-000000000010</uid>
+ <guid>30000000-0000-0000-0000-000000000010</guid>
+ <full-name>Location 10</full-name>
+</record>
+<record type="location">
+ <short-name>location11</short-name>
+ <uid>30000000-0000-0000-0000-000000000011</uid>
+ <guid>30000000-0000-0000-0000-000000000011</guid>
+ <full-name>Location 11</full-name>
+</record>
+<record type="location">
+ <short-name>location12</short-name>
+ <uid>30000000-0000-0000-0000-000000000012</uid>
+ <guid>30000000-0000-0000-0000-000000000012</guid>
+ <full-name>Location 12</full-name>
+</record>
+<record type="location">
+ <short-name>location13</short-name>
+ <uid>30000000-0000-0000-0000-000000000013</uid>
+ <guid>30000000-0000-0000-0000-000000000013</guid>
+ <full-name>Location 13</full-name>
+</record>
+<record type="location">
+ <short-name>location14</short-name>
+ <uid>30000000-0000-0000-0000-000000000014</uid>
+ <guid>30000000-0000-0000-0000-000000000014</guid>
+ <full-name>Location 14</full-name>
+</record>
+<record type="location">
+ <short-name>location15</short-name>
+ <uid>30000000-0000-0000-0000-000000000015</uid>
+ <guid>30000000-0000-0000-0000-000000000015</guid>
+ <full-name>Location 15</full-name>
+</record>
+<record type="location">
+ <short-name>location16</short-name>
+ <uid>30000000-0000-0000-0000-000000000016</uid>
+ <guid>30000000-0000-0000-0000-000000000016</guid>
+ <full-name>Location 16</full-name>
+</record>
+<record type="location">
+ <short-name>location17</short-name>
+ <uid>30000000-0000-0000-0000-000000000017</uid>
+ <guid>30000000-0000-0000-0000-000000000017</guid>
+ <full-name>Location 17</full-name>
+</record>
+<record type="location">
+ <short-name>location18</short-name>
+ <uid>30000000-0000-0000-0000-000000000018</uid>
+ <guid>30000000-0000-0000-0000-000000000018</guid>
+ <full-name>Location 18</full-name>
+</record>
+<record type="location">
+ <short-name>location19</short-name>
+ <uid>30000000-0000-0000-0000-000000000019</uid>
+ <guid>30000000-0000-0000-0000-000000000019</guid>
+ <full-name>Location 19</full-name>
+</record>
+<record type="location">
+ <short-name>location20</short-name>
+ <uid>30000000-0000-0000-0000-000000000020</uid>
+ <guid>30000000-0000-0000-0000-000000000020</guid>
+ <full-name>Location 20</full-name>
+</record>
+<record type="location">
+ <short-name>location21</short-name>
+ <uid>30000000-0000-0000-0000-000000000021</uid>
+ <guid>30000000-0000-0000-0000-000000000021</guid>
+ <full-name>Location 21</full-name>
+</record>
+<record type="location">
+ <short-name>location22</short-name>
+ <uid>30000000-0000-0000-0000-000000000022</uid>
+ <guid>30000000-0000-0000-0000-000000000022</guid>
+ <full-name>Location 22</full-name>
+</record>
+<record type="location">
+ <short-name>location23</short-name>
+ <uid>30000000-0000-0000-0000-000000000023</uid>
+ <guid>30000000-0000-0000-0000-000000000023</guid>
+ <full-name>Location 23</full-name>
+</record>
+<record type="location">
+ <short-name>location24</short-name>
+ <uid>30000000-0000-0000-0000-000000000024</uid>
+ <guid>30000000-0000-0000-0000-000000000024</guid>
+ <full-name>Location 24</full-name>
+</record>
+<record type="location">
+ <short-name>location25</short-name>
+ <uid>30000000-0000-0000-0000-000000000025</uid>
+ <guid>30000000-0000-0000-0000-000000000025</guid>
+ <full-name>Location 25</full-name>
+</record>
+<record type="location">
+ <short-name>location26</short-name>
+ <uid>30000000-0000-0000-0000-000000000026</uid>
+ <guid>30000000-0000-0000-0000-000000000026</guid>
+ <full-name>Location 26</full-name>
+</record>
+<record type="location">
+ <short-name>location27</short-name>
+ <uid>30000000-0000-0000-0000-000000000027</uid>
+ <guid>30000000-0000-0000-0000-000000000027</guid>
+ <full-name>Location 27</full-name>
+</record>
+<record type="location">
+ <short-name>location28</short-name>
+ <uid>30000000-0000-0000-0000-000000000028</uid>
+ <guid>30000000-0000-0000-0000-000000000028</guid>
+ <full-name>Location 28</full-name>
+</record>
+<record type="location">
+ <short-name>location29</short-name>
+ <uid>30000000-0000-0000-0000-000000000029</uid>
+ <guid>30000000-0000-0000-0000-000000000029</guid>
+ <full-name>Location 29</full-name>
+</record>
+<record type="location">
+ <short-name>location30</short-name>
+ <uid>30000000-0000-0000-0000-000000000030</uid>
+ <guid>30000000-0000-0000-0000-000000000030</guid>
+ <full-name>Location 30</full-name>
+</record>
+<record type="location">
+ <short-name>location31</short-name>
+ <uid>30000000-0000-0000-0000-000000000031</uid>
+ <guid>30000000-0000-0000-0000-000000000031</guid>
+ <full-name>Location 31</full-name>
+</record>
+<record type="location">
+ <short-name>location32</short-name>
+ <uid>30000000-0000-0000-0000-000000000032</uid>
+ <guid>30000000-0000-0000-0000-000000000032</guid>
+ <full-name>Location 32</full-name>
+</record>
+<record type="location">
+ <short-name>location33</short-name>
+ <uid>30000000-0000-0000-0000-000000000033</uid>
+ <guid>30000000-0000-0000-0000-000000000033</guid>
+ <full-name>Location 33</full-name>
+</record>
+<record type="location">
+ <short-name>location34</short-name>
+ <uid>30000000-0000-0000-0000-000000000034</uid>
+ <guid>30000000-0000-0000-0000-000000000034</guid>
+ <full-name>Location 34</full-name>
+</record>
+<record type="location">
+ <short-name>location35</short-name>
+ <uid>30000000-0000-0000-0000-000000000035</uid>
+ <guid>30000000-0000-0000-0000-000000000035</guid>
+ <full-name>Location 35</full-name>
+</record>
+<record type="location">
+ <short-name>location36</short-name>
+ <uid>30000000-0000-0000-0000-000000000036</uid>
+ <guid>30000000-0000-0000-0000-000000000036</guid>
+ <full-name>Location 36</full-name>
+</record>
+<record type="location">
+ <short-name>location37</short-name>
+ <uid>30000000-0000-0000-0000-000000000037</uid>
+ <guid>30000000-0000-0000-0000-000000000037</guid>
+ <full-name>Location 37</full-name>
+</record>
+<record type="location">
+ <short-name>location38</short-name>
+ <uid>30000000-0000-0000-0000-000000000038</uid>
+ <guid>30000000-0000-0000-0000-000000000038</guid>
+ <full-name>Location 38</full-name>
+</record>
+<record type="location">
+ <short-name>location39</short-name>
+ <uid>30000000-0000-0000-0000-000000000039</uid>
+ <guid>30000000-0000-0000-0000-000000000039</guid>
+ <full-name>Location 39</full-name>
+</record>
+<record type="location">
+ <short-name>location40</short-name>
+ <uid>30000000-0000-0000-0000-000000000040</uid>
+ <guid>30000000-0000-0000-0000-000000000040</guid>
+ <full-name>Location 40</full-name>
+</record>
+<record type="location">
+ <short-name>location41</short-name>
+ <uid>30000000-0000-0000-0000-000000000041</uid>
+ <guid>30000000-0000-0000-0000-000000000041</guid>
+ <full-name>Location 41</full-name>
+</record>
+<record type="location">
+ <short-name>location42</short-name>
+ <uid>30000000-0000-0000-0000-000000000042</uid>
+ <guid>30000000-0000-0000-0000-000000000042</guid>
+ <full-name>Location 42</full-name>
+</record>
+<record type="location">
+ <short-name>location43</short-name>
+ <uid>30000000-0000-0000-0000-000000000043</uid>
+ <guid>30000000-0000-0000-0000-000000000043</guid>
+ <full-name>Location 43</full-name>
+</record>
+<record type="location">
+ <short-name>location44</short-name>
+ <uid>30000000-0000-0000-0000-000000000044</uid>
+ <guid>30000000-0000-0000-0000-000000000044</guid>
+ <full-name>Location 44</full-name>
+</record>
+<record type="location">
+ <short-name>location45</short-name>
+ <uid>30000000-0000-0000-0000-000000000045</uid>
+ <guid>30000000-0000-0000-0000-000000000045</guid>
+ <full-name>Location 45</full-name>
+</record>
+<record type="location">
+ <short-name>location46</short-name>
+ <uid>30000000-0000-0000-0000-000000000046</uid>
+ <guid>30000000-0000-0000-0000-000000000046</guid>
+ <full-name>Location 46</full-name>
+</record>
+<record type="location">
+ <short-name>location47</short-name>
+ <uid>30000000-0000-0000-0000-000000000047</uid>
+ <guid>30000000-0000-0000-0000-000000000047</guid>
+ <full-name>Location 47</full-name>
+</record>
+<record type="location">
+ <short-name>location48</short-name>
+ <uid>30000000-0000-0000-0000-000000000048</uid>
+ <guid>30000000-0000-0000-0000-000000000048</guid>
+ <full-name>Location 48</full-name>
+</record>
+<record type="location">
+ <short-name>location49</short-name>
+ <uid>30000000-0000-0000-0000-000000000049</uid>
+ <guid>30000000-0000-0000-0000-000000000049</guid>
+ <full-name>Location 49</full-name>
+</record>
+<record type="location">
+ <short-name>location50</short-name>
+ <uid>30000000-0000-0000-0000-000000000050</uid>
+ <guid>30000000-0000-0000-0000-000000000050</guid>
+ <full-name>Location 50</full-name>
+</record>
+<record type="location">
+ <short-name>location51</short-name>
+ <uid>30000000-0000-0000-0000-000000000051</uid>
+ <guid>30000000-0000-0000-0000-000000000051</guid>
+ <full-name>Location 51</full-name>
+</record>
+<record type="location">
+ <short-name>location52</short-name>
+ <uid>30000000-0000-0000-0000-000000000052</uid>
+ <guid>30000000-0000-0000-0000-000000000052</guid>
+ <full-name>Location 52</full-name>
+</record>
+<record type="location">
+ <short-name>location53</short-name>
+ <uid>30000000-0000-0000-0000-000000000053</uid>
+ <guid>30000000-0000-0000-0000-000000000053</guid>
+ <full-name>Location 53</full-name>
+</record>
+<record type="location">
+ <short-name>location54</short-name>
+ <uid>30000000-0000-0000-0000-000000000054</uid>
+ <guid>30000000-0000-0000-0000-000000000054</guid>
+ <full-name>Location 54</full-name>
+</record>
+<record type="location">
+ <short-name>location55</short-name>
+ <uid>30000000-0000-0000-0000-000000000055</uid>
+ <guid>30000000-0000-0000-0000-000000000055</guid>
+ <full-name>Location 55</full-name>
+</record>
+<record type="location">
+ <short-name>location56</short-name>
+ <uid>30000000-0000-0000-0000-000000000056</uid>
+ <guid>30000000-0000-0000-0000-000000000056</guid>
+ <full-name>Location 56</full-name>
+</record>
+<record type="location">
+ <short-name>location57</short-name>
+ <uid>30000000-0000-0000-0000-000000000057</uid>
+ <guid>30000000-0000-0000-0000-000000000057</guid>
+ <full-name>Location 57</full-name>
+</record>
+<record type="location">
+ <short-name>location58</short-name>
+ <uid>30000000-0000-0000-0000-000000000058</uid>
+ <guid>30000000-0000-0000-0000-000000000058</guid>
+ <full-name>Location 58</full-name>
+</record>
+<record type="location">
+ <short-name>location59</short-name>
+ <uid>30000000-0000-0000-0000-000000000059</uid>
+ <guid>30000000-0000-0000-0000-000000000059</guid>
+ <full-name>Location 59</full-name>
+</record>
+<record type="location">
+ <short-name>location60</short-name>
+ <uid>30000000-0000-0000-0000-000000000060</uid>
+ <guid>30000000-0000-0000-0000-000000000060</guid>
+ <full-name>Location 60</full-name>
+</record>
+<record type="location">
+ <short-name>location61</short-name>
+ <uid>30000000-0000-0000-0000-000000000061</uid>
+ <guid>30000000-0000-0000-0000-000000000061</guid>
+ <full-name>Location 61</full-name>
+</record>
+<record type="location">
+ <short-name>location62</short-name>
+ <uid>30000000-0000-0000-0000-000000000062</uid>
+ <guid>30000000-0000-0000-0000-000000000062</guid>
+ <full-name>Location 62</full-name>
+</record>
+<record type="location">
+ <short-name>location63</short-name>
+ <uid>30000000-0000-0000-0000-000000000063</uid>
+ <guid>30000000-0000-0000-0000-000000000063</guid>
+ <full-name>Location 63</full-name>
+</record>
+<record type="location">
+ <short-name>location64</short-name>
+ <uid>30000000-0000-0000-0000-000000000064</uid>
+ <guid>30000000-0000-0000-0000-000000000064</guid>
+ <full-name>Location 64</full-name>
+</record>
+<record type="location">
+ <short-name>location65</short-name>
+ <uid>30000000-0000-0000-0000-000000000065</uid>
+ <guid>30000000-0000-0000-0000-000000000065</guid>
+ <full-name>Location 65</full-name>
+</record>
+<record type="location">
+ <short-name>location66</short-name>
+ <uid>30000000-0000-0000-0000-000000000066</uid>
+ <guid>30000000-0000-0000-0000-000000000066</guid>
+ <full-name>Location 66</full-name>
+</record>
+<record type="location">
+ <short-name>location67</short-name>
+ <uid>30000000-0000-0000-0000-000000000067</uid>
+ <guid>30000000-0000-0000-0000-000000000067</guid>
+ <full-name>Location 67</full-name>
+</record>
+<record type="location">
+ <short-name>location68</short-name>
+ <uid>30000000-0000-0000-0000-000000000068</uid>
+ <guid>30000000-0000-0000-0000-000000000068</guid>
+ <full-name>Location 68</full-name>
+</record>
+<record type="location">
+ <short-name>location69</short-name>
+ <uid>30000000-0000-0000-0000-000000000069</uid>
+ <guid>30000000-0000-0000-0000-000000000069</guid>
+ <full-name>Location 69</full-name>
+</record>
+<record type="location">
+ <short-name>location70</short-name>
+ <uid>30000000-0000-0000-0000-000000000070</uid>
+ <guid>30000000-0000-0000-0000-000000000070</guid>
+ <full-name>Location 70</full-name>
+</record>
+<record type="location">
+ <short-name>location71</short-name>
+ <uid>30000000-0000-0000-0000-000000000071</uid>
+ <guid>30000000-0000-0000-0000-000000000071</guid>
+ <full-name>Location 71</full-name>
+</record>
+<record type="location">
+ <short-name>location72</short-name>
+ <uid>30000000-0000-0000-0000-000000000072</uid>
+ <guid>30000000-0000-0000-0000-000000000072</guid>
+ <full-name>Location 72</full-name>
+</record>
+<record type="location">
+ <short-name>location73</short-name>
+ <uid>30000000-0000-0000-0000-000000000073</uid>
+ <guid>30000000-0000-0000-0000-000000000073</guid>
+ <full-name>Location 73</full-name>
+</record>
+<record type="location">
+ <short-name>location74</short-name>
+ <uid>30000000-0000-0000-0000-000000000074</uid>
+ <guid>30000000-0000-0000-0000-000000000074</guid>
+ <full-name>Location 74</full-name>
+</record>
+<record type="location">
+ <short-name>location75</short-name>
+ <uid>30000000-0000-0000-0000-000000000075</uid>
+ <guid>30000000-0000-0000-0000-000000000075</guid>
+ <full-name>Location 75</full-name>
+</record>
+<record type="location">
+ <short-name>location76</short-name>
+ <uid>30000000-0000-0000-0000-000000000076</uid>
+ <guid>30000000-0000-0000-0000-000000000076</guid>
+ <full-name>Location 76</full-name>
+</record>
+<record type="location">
+ <short-name>location77</short-name>
+ <uid>30000000-0000-0000-0000-000000000077</uid>
+ <guid>30000000-0000-0000-0000-000000000077</guid>
+ <full-name>Location 77</full-name>
+</record>
+<record type="location">
+ <short-name>location78</short-name>
+ <uid>30000000-0000-0000-0000-000000000078</uid>
+ <guid>30000000-0000-0000-0000-000000000078</guid>
+ <full-name>Location 78</full-name>
+</record>
+<record type="location">
+ <short-name>location79</short-name>
+ <uid>30000000-0000-0000-0000-000000000079</uid>
+ <guid>30000000-0000-0000-0000-000000000079</guid>
+ <full-name>Location 79</full-name>
+</record>
+<record type="location">
+ <short-name>location80</short-name>
+ <uid>30000000-0000-0000-0000-000000000080</uid>
+ <guid>30000000-0000-0000-0000-000000000080</guid>
+ <full-name>Location 80</full-name>
+</record>
+<record type="location">
+ <short-name>location81</short-name>
+ <uid>30000000-0000-0000-0000-000000000081</uid>
+ <guid>30000000-0000-0000-0000-000000000081</guid>
+ <full-name>Location 81</full-name>
+</record>
+<record type="location">
+ <short-name>location82</short-name>
+ <uid>30000000-0000-0000-0000-000000000082</uid>
+ <guid>30000000-0000-0000-0000-000000000082</guid>
+ <full-name>Location 82</full-name>
+</record>
+<record type="location">
+ <short-name>location83</short-name>
+ <uid>30000000-0000-0000-0000-000000000083</uid>
+ <guid>30000000-0000-0000-0000-000000000083</guid>
+ <full-name>Location 83</full-name>
+</record>
+<record type="location">
+ <short-name>location84</short-name>
+ <uid>30000000-0000-0000-0000-000000000084</uid>
+ <guid>30000000-0000-0000-0000-000000000084</guid>
+ <full-name>Location 84</full-name>
+</record>
+<record type="location">
+ <short-name>location85</short-name>
+ <uid>30000000-0000-0000-0000-000000000085</uid>
+ <guid>30000000-0000-0000-0000-000000000085</guid>
+ <full-name>Location 85</full-name>
+</record>
+<record type="location">
+ <short-name>location86</short-name>
+ <uid>30000000-0000-0000-0000-000000000086</uid>
+ <guid>30000000-0000-0000-0000-000000000086</guid>
+ <full-name>Location 86</full-name>
+</record>
+<record type="location">
+ <short-name>location87</short-name>
+ <uid>30000000-0000-0000-0000-000000000087</uid>
+ <guid>30000000-0000-0000-0000-000000000087</guid>
+ <full-name>Location 87</full-name>
+</record>
+<record type="location">
+ <short-name>location88</short-name>
+ <uid>30000000-0000-0000-0000-000000000088</uid>
+ <guid>30000000-0000-0000-0000-000000000088</guid>
+ <full-name>Location 88</full-name>
+</record>
+<record type="location">
+ <short-name>location89</short-name>
+ <uid>30000000-0000-0000-0000-000000000089</uid>
+ <guid>30000000-0000-0000-0000-000000000089</guid>
+ <full-name>Location 89</full-name>
+</record>
+<record type="location">
+ <short-name>location90</short-name>
+ <uid>30000000-0000-0000-0000-000000000090</uid>
+ <guid>30000000-0000-0000-0000-000000000090</guid>
+ <full-name>Location 90</full-name>
+</record>
+<record type="location">
+ <short-name>location91</short-name>
+ <uid>30000000-0000-0000-0000-000000000091</uid>
+ <guid>30000000-0000-0000-0000-000000000091</guid>
+ <full-name>Location 91</full-name>
+</record>
+<record type="location">
+ <short-name>location92</short-name>
+ <uid>30000000-0000-0000-0000-000000000092</uid>
+ <guid>30000000-0000-0000-0000-000000000092</guid>
+ <full-name>Location 92</full-name>
+</record>
+<record type="location">
+ <short-name>location93</short-name>
+ <uid>30000000-0000-0000-0000-000000000093</uid>
+ <guid>30000000-0000-0000-0000-000000000093</guid>
+ <full-name>Location 93</full-name>
+</record>
+<record type="location">
+ <short-name>location94</short-name>
+ <uid>30000000-0000-0000-0000-000000000094</uid>
+ <guid>30000000-0000-0000-0000-000000000094</guid>
+ <full-name>Location 94</full-name>
+</record>
+<record type="location">
+ <short-name>location95</short-name>
+ <uid>30000000-0000-0000-0000-000000000095</uid>
+ <guid>30000000-0000-0000-0000-000000000095</guid>
+ <full-name>Location 95</full-name>
+</record>
+<record type="location">
+ <short-name>location96</short-name>
+ <uid>30000000-0000-0000-0000-000000000096</uid>
+ <guid>30000000-0000-0000-0000-000000000096</guid>
+ <full-name>Location 96</full-name>
+</record>
+<record type="location">
+ <short-name>location97</short-name>
+ <uid>30000000-0000-0000-0000-000000000097</uid>
+ <guid>30000000-0000-0000-0000-000000000097</guid>
+ <full-name>Location 97</full-name>
+</record>
+<record type="location">
+ <short-name>location98</short-name>
+ <uid>30000000-0000-0000-0000-000000000098</uid>
+ <guid>30000000-0000-0000-0000-000000000098</guid>
+ <full-name>Location 98</full-name>
+</record>
+<record type="location">
+ <short-name>location99</short-name>
+ <uid>30000000-0000-0000-0000-000000000099</uid>
+ <guid>30000000-0000-0000-0000-000000000099</guid>
+ <full-name>Location 99</full-name>
+</record>
+<record type="location">
+ <short-name>location100</short-name>
+ <uid>30000000-0000-0000-0000-000000000100</uid>
+ <guid>30000000-0000-0000-0000-000000000100</guid>
+ <full-name>Location 100</full-name>
+</record>
+<record type="resource">
+ <short-name>resource01</short-name>
+ <uid>40000000-0000-0000-0000-000000000001</uid>
+ <guid>40000000-0000-0000-0000-000000000001</guid>
+ <full-name>Resource 01</full-name>
+</record>
+<record type="resource">
+ <short-name>resource02</short-name>
+ <uid>40000000-0000-0000-0000-000000000002</uid>
+ <guid>40000000-0000-0000-0000-000000000002</guid>
+ <full-name>Resource 02</full-name>
+</record>
+<record type="resource">
+ <short-name>resource03</short-name>
+ <uid>40000000-0000-0000-0000-000000000003</uid>
+ <guid>40000000-0000-0000-0000-000000000003</guid>
+ <full-name>Resource 03</full-name>
+</record>
+<record type="resource">
+ <short-name>resource04</short-name>
+ <uid>40000000-0000-0000-0000-000000000004</uid>
+ <guid>40000000-0000-0000-0000-000000000004</guid>
+ <full-name>Resource 04</full-name>
+</record>
+<record type="resource">
+ <short-name>resource05</short-name>
+ <uid>40000000-0000-0000-0000-000000000005</uid>
+ <guid>40000000-0000-0000-0000-000000000005</guid>
+ <full-name>Resource 05</full-name>
+</record>
+<record type="resource">
+ <short-name>resource06</short-name>
+ <uid>40000000-0000-0000-0000-000000000006</uid>
+ <guid>40000000-0000-0000-0000-000000000006</guid>
+ <full-name>Resource 06</full-name>
+</record>
+<record type="resource">
+ <short-name>resource07</short-name>
+ <uid>40000000-0000-0000-0000-000000000007</uid>
+ <guid>40000000-0000-0000-0000-000000000007</guid>
+ <full-name>Resource 07</full-name>
+</record>
+<record type="resource">
+ <short-name>resource08</short-name>
+ <uid>40000000-0000-0000-0000-000000000008</uid>
+ <guid>40000000-0000-0000-0000-000000000008</guid>
+ <full-name>Resource 08</full-name>
+</record>
+<record type="resource">
+ <short-name>resource09</short-name>
+ <uid>40000000-0000-0000-0000-000000000009</uid>
+ <guid>40000000-0000-0000-0000-000000000009</guid>
+ <full-name>Resource 09</full-name>
+</record>
+<record type="resource">
+ <short-name>resource10</short-name>
+ <uid>40000000-0000-0000-0000-000000000010</uid>
+ <guid>40000000-0000-0000-0000-000000000010</guid>
+ <full-name>Resource 10</full-name>
+</record>
+<record type="resource">
+ <short-name>resource11</short-name>
+ <uid>40000000-0000-0000-0000-000000000011</uid>
+ <guid>40000000-0000-0000-0000-000000000011</guid>
+ <full-name>Resource 11</full-name>
+</record>
+<record type="resource">
+ <short-name>resource12</short-name>
+ <uid>40000000-0000-0000-0000-000000000012</uid>
+ <guid>40000000-0000-0000-0000-000000000012</guid>
+ <full-name>Resource 12</full-name>
+</record>
+<record type="resource">
+ <short-name>resource13</short-name>
+ <uid>40000000-0000-0000-0000-000000000013</uid>
+ <guid>40000000-0000-0000-0000-000000000013</guid>
+ <full-name>Resource 13</full-name>
+</record>
+<record type="resource">
+ <short-name>resource14</short-name>
+ <uid>40000000-0000-0000-0000-000000000014</uid>
+ <guid>40000000-0000-0000-0000-000000000014</guid>
+ <full-name>Resource 14</full-name>
+</record>
+<record type="resource">
+ <short-name>resource15</short-name>
+ <uid>40000000-0000-0000-0000-000000000015</uid>
+ <guid>40000000-0000-0000-0000-000000000015</guid>
+ <full-name>Resource 15</full-name>
+</record>
+<record type="resource">
+ <short-name>resource16</short-name>
+ <uid>40000000-0000-0000-0000-000000000016</uid>
+ <guid>40000000-0000-0000-0000-000000000016</guid>
+ <full-name>Resource 16</full-name>
+</record>
+<record type="resource">
+ <short-name>resource17</short-name>
+ <uid>40000000-0000-0000-0000-000000000017</uid>
+ <guid>40000000-0000-0000-0000-000000000017</guid>
+ <full-name>Resource 17</full-name>
+</record>
+<record type="resource">
+ <short-name>resource18</short-name>
+ <uid>40000000-0000-0000-0000-000000000018</uid>
+ <guid>40000000-0000-0000-0000-000000000018</guid>
+ <full-name>Resource 18</full-name>
+</record>
+<record type="resource">
+ <short-name>resource19</short-name>
+ <uid>40000000-0000-0000-0000-000000000019</uid>
+ <guid>40000000-0000-0000-0000-000000000019</guid>
+ <full-name>Resource 19</full-name>
+</record>
+<record type="resource">
+ <short-name>resource20</short-name>
+ <uid>40000000-0000-0000-0000-000000000020</uid>
+ <guid>40000000-0000-0000-0000-000000000020</guid>
+ <full-name>Resource 20</full-name>
+</record>
+<record type="resource">
+ <short-name>resource21</short-name>
+ <uid>40000000-0000-0000-0000-000000000021</uid>
+ <guid>40000000-0000-0000-0000-000000000021</guid>
+ <full-name>Resource 21</full-name>
+</record>
+<record type="resource">
+ <short-name>resource22</short-name>
+ <uid>40000000-0000-0000-0000-000000000022</uid>
+ <guid>40000000-0000-0000-0000-000000000022</guid>
+ <full-name>Resource 22</full-name>
+</record>
+<record type="resource">
+ <short-name>resource23</short-name>
+ <uid>40000000-0000-0000-0000-000000000023</uid>
+ <guid>40000000-0000-0000-0000-000000000023</guid>
+ <full-name>Resource 23</full-name>
+</record>
+<record type="resource">
+ <short-name>resource24</short-name>
+ <uid>40000000-0000-0000-0000-000000000024</uid>
+ <guid>40000000-0000-0000-0000-000000000024</guid>
+ <full-name>Resource 24</full-name>
+</record>
+<record type="resource">
+ <short-name>resource25</short-name>
+ <uid>40000000-0000-0000-0000-000000000025</uid>
+ <guid>40000000-0000-0000-0000-000000000025</guid>
+ <full-name>Resource 25</full-name>
+</record>
+<record type="resource">
+ <short-name>resource26</short-name>
+ <uid>40000000-0000-0000-0000-000000000026</uid>
+ <guid>40000000-0000-0000-0000-000000000026</guid>
+ <full-name>Resource 26</full-name>
+</record>
+<record type="resource">
+ <short-name>resource27</short-name>
+ <uid>40000000-0000-0000-0000-000000000027</uid>
+ <guid>40000000-0000-0000-0000-000000000027</guid>
+ <full-name>Resource 27</full-name>
+</record>
+<record type="resource">
+ <short-name>resource28</short-name>
+ <uid>40000000-0000-0000-0000-000000000028</uid>
+ <guid>40000000-0000-0000-0000-000000000028</guid>
+ <full-name>Resource 28</full-name>
+</record>
+<record type="resource">
+ <short-name>resource29</short-name>
+ <uid>40000000-0000-0000-0000-000000000029</uid>
+ <guid>40000000-0000-0000-0000-000000000029</guid>
+ <full-name>Resource 29</full-name>
+</record>
+<record type="resource">
+ <short-name>resource30</short-name>
+ <uid>40000000-0000-0000-0000-000000000030</uid>
+ <guid>40000000-0000-0000-0000-000000000030</guid>
+ <full-name>Resource 30</full-name>
+</record>
+<record type="resource">
+ <short-name>resource31</short-name>
+ <uid>40000000-0000-0000-0000-000000000031</uid>
+ <guid>40000000-0000-0000-0000-000000000031</guid>
+ <full-name>Resource 31</full-name>
+</record>
+<record type="resource">
+ <short-name>resource32</short-name>
+ <uid>40000000-0000-0000-0000-000000000032</uid>
+ <guid>40000000-0000-0000-0000-000000000032</guid>
+ <full-name>Resource 32</full-name>
+</record>
+<record type="resource">
+ <short-name>resource33</short-name>
+ <uid>40000000-0000-0000-0000-000000000033</uid>
+ <guid>40000000-0000-0000-0000-000000000033</guid>
+ <full-name>Resource 33</full-name>
+</record>
+<record type="resource">
+ <short-name>resource34</short-name>
+ <uid>40000000-0000-0000-0000-000000000034</uid>
+ <guid>40000000-0000-0000-0000-000000000034</guid>
+ <full-name>Resource 34</full-name>
+</record>
+<record type="resource">
+ <short-name>resource35</short-name>
+ <uid>40000000-0000-0000-0000-000000000035</uid>
+ <guid>40000000-0000-0000-0000-000000000035</guid>
+ <full-name>Resource 35</full-name>
+</record>
+<record type="resource">
+ <short-name>resource36</short-name>
+ <uid>40000000-0000-0000-0000-000000000036</uid>
+ <guid>40000000-0000-0000-0000-000000000036</guid>
+ <full-name>Resource 36</full-name>
+</record>
+<record type="resource">
+ <short-name>resource37</short-name>
+ <uid>40000000-0000-0000-0000-000000000037</uid>
+ <guid>40000000-0000-0000-0000-000000000037</guid>
+ <full-name>Resource 37</full-name>
+</record>
+<record type="resource">
+ <short-name>resource38</short-name>
+ <uid>40000000-0000-0000-0000-000000000038</uid>
+ <guid>40000000-0000-0000-0000-000000000038</guid>
+ <full-name>Resource 38</full-name>
+</record>
+<record type="resource">
+ <short-name>resource39</short-name>
+ <uid>40000000-0000-0000-0000-000000000039</uid>
+ <guid>40000000-0000-0000-0000-000000000039</guid>
+ <full-name>Resource 39</full-name>
+</record>
+<record type="resource">
+ <short-name>resource40</short-name>
+ <uid>40000000-0000-0000-0000-000000000040</uid>
+ <guid>40000000-0000-0000-0000-000000000040</guid>
+ <full-name>Resource 40</full-name>
+</record>
+<record type="resource">
+ <short-name>resource41</short-name>
+ <uid>40000000-0000-0000-0000-000000000041</uid>
+ <guid>40000000-0000-0000-0000-000000000041</guid>
+ <full-name>Resource 41</full-name>
+</record>
+<record type="resource">
+ <short-name>resource42</short-name>
+ <uid>40000000-0000-0000-0000-000000000042</uid>
+ <guid>40000000-0000-0000-0000-000000000042</guid>
+ <full-name>Resource 42</full-name>
+</record>
+<record type="resource">
+ <short-name>resource43</short-name>
+ <uid>40000000-0000-0000-0000-000000000043</uid>
+ <guid>40000000-0000-0000-0000-000000000043</guid>
+ <full-name>Resource 43</full-name>
+</record>
+<record type="resource">
+ <short-name>resource44</short-name>
+ <uid>40000000-0000-0000-0000-000000000044</uid>
+ <guid>40000000-0000-0000-0000-000000000044</guid>
+ <full-name>Resource 44</full-name>
+</record>
+<record type="resource">
+ <short-name>resource45</short-name>
+ <uid>40000000-0000-0000-0000-000000000045</uid>
+ <guid>40000000-0000-0000-0000-000000000045</guid>
+ <full-name>Resource 45</full-name>
+</record>
+<record type="resource">
+ <short-name>resource46</short-name>
+ <uid>40000000-0000-0000-0000-000000000046</uid>
+ <guid>40000000-0000-0000-0000-000000000046</guid>
+ <full-name>Resource 46</full-name>
+</record>
+<record type="resource">
+ <short-name>resource47</short-name>
+ <uid>40000000-0000-0000-0000-000000000047</uid>
+ <guid>40000000-0000-0000-0000-000000000047</guid>
+ <full-name>Resource 47</full-name>
+</record>
+<record type="resource">
+ <short-name>resource48</short-name>
+ <uid>40000000-0000-0000-0000-000000000048</uid>
+ <guid>40000000-0000-0000-0000-000000000048</guid>
+ <full-name>Resource 48</full-name>
+</record>
+<record type="resource">
+ <short-name>resource49</short-name>
+ <uid>40000000-0000-0000-0000-000000000049</uid>
+ <guid>40000000-0000-0000-0000-000000000049</guid>
+ <full-name>Resource 49</full-name>
+</record>
+<record type="resource">
+ <short-name>resource50</short-name>
+ <uid>40000000-0000-0000-0000-000000000050</uid>
+ <guid>40000000-0000-0000-0000-000000000050</guid>
+ <full-name>Resource 50</full-name>
+</record>
+<record type="resource">
+ <short-name>resource51</short-name>
+ <uid>40000000-0000-0000-0000-000000000051</uid>
+ <guid>40000000-0000-0000-0000-000000000051</guid>
+ <full-name>Resource 51</full-name>
+</record>
+<record type="resource">
+ <short-name>resource52</short-name>
+ <uid>40000000-0000-0000-0000-000000000052</uid>
+ <guid>40000000-0000-0000-0000-000000000052</guid>
+ <full-name>Resource 52</full-name>
+</record>
+<record type="resource">
+ <short-name>resource53</short-name>
+ <uid>40000000-0000-0000-0000-000000000053</uid>
+ <guid>40000000-0000-0000-0000-000000000053</guid>
+ <full-name>Resource 53</full-name>
+</record>
+<record type="resource">
+ <short-name>resource54</short-name>
+ <uid>40000000-0000-0000-0000-000000000054</uid>
+ <guid>40000000-0000-0000-0000-000000000054</guid>
+ <full-name>Resource 54</full-name>
+</record>
+<record type="resource">
+ <short-name>resource55</short-name>
+ <uid>40000000-0000-0000-0000-000000000055</uid>
+ <guid>40000000-0000-0000-0000-000000000055</guid>
+ <full-name>Resource 55</full-name>
+</record>
+<record type="resource">
+ <short-name>resource56</short-name>
+ <uid>40000000-0000-0000-0000-000000000056</uid>
+ <guid>40000000-0000-0000-0000-000000000056</guid>
+ <full-name>Resource 56</full-name>
+</record>
+<record type="resource">
+ <short-name>resource57</short-name>
+ <uid>40000000-0000-0000-0000-000000000057</uid>
+ <guid>40000000-0000-0000-0000-000000000057</guid>
+ <full-name>Resource 57</full-name>
+</record>
+<record type="resource">
+ <short-name>resource58</short-name>
+ <uid>40000000-0000-0000-0000-000000000058</uid>
+ <guid>40000000-0000-0000-0000-000000000058</guid>
+ <full-name>Resource 58</full-name>
+</record>
+<record type="resource">
+ <short-name>resource59</short-name>
+ <uid>40000000-0000-0000-0000-000000000059</uid>
+ <guid>40000000-0000-0000-0000-000000000059</guid>
+ <full-name>Resource 59</full-name>
+</record>
+<record type="resource">
+ <short-name>resource60</short-name>
+ <uid>40000000-0000-0000-0000-000000000060</uid>
+ <guid>40000000-0000-0000-0000-000000000060</guid>
+ <full-name>Resource 60</full-name>
+</record>
+<record type="resource">
+ <short-name>resource61</short-name>
+ <uid>40000000-0000-0000-0000-000000000061</uid>
+ <guid>40000000-0000-0000-0000-000000000061</guid>
+ <full-name>Resource 61</full-name>
+</record>
+<record type="resource">
+ <short-name>resource62</short-name>
+ <uid>40000000-0000-0000-0000-000000000062</uid>
+ <guid>40000000-0000-0000-0000-000000000062</guid>
+ <full-name>Resource 62</full-name>
+</record>
+<record type="resource">
+ <short-name>resource63</short-name>
+ <uid>40000000-0000-0000-0000-000000000063</uid>
+ <guid>40000000-0000-0000-0000-000000000063</guid>
+ <full-name>Resource 63</full-name>
+</record>
+<record type="resource">
+ <short-name>resource64</short-name>
+ <uid>40000000-0000-0000-0000-000000000064</uid>
+ <guid>40000000-0000-0000-0000-000000000064</guid>
+ <full-name>Resource 64</full-name>
+</record>
+<record type="resource">
+ <short-name>resource65</short-name>
+ <uid>40000000-0000-0000-0000-000000000065</uid>
+ <guid>40000000-0000-0000-0000-000000000065</guid>
+ <full-name>Resource 65</full-name>
+</record>
+<record type="resource">
+ <short-name>resource66</short-name>
+ <uid>40000000-0000-0000-0000-000000000066</uid>
+ <guid>40000000-0000-0000-0000-000000000066</guid>
+ <full-name>Resource 66</full-name>
+</record>
+<record type="resource">
+ <short-name>resource67</short-name>
+ <uid>40000000-0000-0000-0000-000000000067</uid>
+ <guid>40000000-0000-0000-0000-000000000067</guid>
+ <full-name>Resource 67</full-name>
+</record>
+<record type="resource">
+ <short-name>resource68</short-name>
+ <uid>40000000-0000-0000-0000-000000000068</uid>
+ <guid>40000000-0000-0000-0000-000000000068</guid>
+ <full-name>Resource 68</full-name>
+</record>
+<record type="resource">
+ <short-name>resource69</short-name>
+ <uid>40000000-0000-0000-0000-000000000069</uid>
+ <guid>40000000-0000-0000-0000-000000000069</guid>
+ <full-name>Resource 69</full-name>
+</record>
+<record type="resource">
+ <short-name>resource70</short-name>
+ <uid>40000000-0000-0000-0000-000000000070</uid>
+ <guid>40000000-0000-0000-0000-000000000070</guid>
+ <full-name>Resource 70</full-name>
+</record>
+<record type="resource">
+ <short-name>resource71</short-name>
+ <uid>40000000-0000-0000-0000-000000000071</uid>
+ <guid>40000000-0000-0000-0000-000000000071</guid>
+ <full-name>Resource 71</full-name>
+</record>
+<record type="resource">
+ <short-name>resource72</short-name>
+ <uid>40000000-0000-0000-0000-000000000072</uid>
+ <guid>40000000-0000-0000-0000-000000000072</guid>
+ <full-name>Resource 72</full-name>
+</record>
+<record type="resource">
+ <short-name>resource73</short-name>
+ <uid>40000000-0000-0000-0000-000000000073</uid>
+ <guid>40000000-0000-0000-0000-000000000073</guid>
+ <full-name>Resource 73</full-name>
+</record>
+<record type="resource">
+ <short-name>resource74</short-name>
+ <uid>40000000-0000-0000-0000-000000000074</uid>
+ <guid>40000000-0000-0000-0000-000000000074</guid>
+ <full-name>Resource 74</full-name>
+</record>
+<record type="resource">
+ <short-name>resource75</short-name>
+ <uid>40000000-0000-0000-0000-000000000075</uid>
+ <guid>40000000-0000-0000-0000-000000000075</guid>
+ <full-name>Resource 75</full-name>
+</record>
+<record type="resource">
+ <short-name>resource76</short-name>
+ <uid>40000000-0000-0000-0000-000000000076</uid>
+ <guid>40000000-0000-0000-0000-000000000076</guid>
+ <full-name>Resource 76</full-name>
+</record>
+<record type="resource">
+ <short-name>resource77</short-name>
+ <uid>40000000-0000-0000-0000-000000000077</uid>
+ <guid>40000000-0000-0000-0000-000000000077</guid>
+ <full-name>Resource 77</full-name>
+</record>
+<record type="resource">
+ <short-name>resource78</short-name>
+ <uid>40000000-0000-0000-0000-000000000078</uid>
+ <guid>40000000-0000-0000-0000-000000000078</guid>
+ <full-name>Resource 78</full-name>
+</record>
+<record type="resource">
+ <short-name>resource79</short-name>
+ <uid>40000000-0000-0000-0000-000000000079</uid>
+ <guid>40000000-0000-0000-0000-000000000079</guid>
+ <full-name>Resource 79</full-name>
+</record>
+<record type="resource">
+ <short-name>resource80</short-name>
+ <uid>40000000-0000-0000-0000-000000000080</uid>
+ <guid>40000000-0000-0000-0000-000000000080</guid>
+ <full-name>Resource 80</full-name>
+</record>
+<record type="resource">
+ <short-name>resource81</short-name>
+ <uid>40000000-0000-0000-0000-000000000081</uid>
+ <guid>40000000-0000-0000-0000-000000000081</guid>
+ <full-name>Resource 81</full-name>
+</record>
+<record type="resource">
+ <short-name>resource82</short-name>
+ <uid>40000000-0000-0000-0000-000000000082</uid>
+ <guid>40000000-0000-0000-0000-000000000082</guid>
+ <full-name>Resource 82</full-name>
+</record>
+<record type="resource">
+ <short-name>resource83</short-name>
+ <uid>40000000-0000-0000-0000-000000000083</uid>
+ <guid>40000000-0000-0000-0000-000000000083</guid>
+ <full-name>Resource 83</full-name>
+</record>
+<record type="resource">
+ <short-name>resource84</short-name>
+ <uid>40000000-0000-0000-0000-000000000084</uid>
+ <guid>40000000-0000-0000-0000-000000000084</guid>
+ <full-name>Resource 84</full-name>
+</record>
+<record type="resource">
+ <short-name>resource85</short-name>
+ <uid>40000000-0000-0000-0000-000000000085</uid>
+ <guid>40000000-0000-0000-0000-000000000085</guid>
+ <full-name>Resource 85</full-name>
+</record>
+<record type="resource">
+ <short-name>resource86</short-name>
+ <uid>40000000-0000-0000-0000-000000000086</uid>
+ <guid>40000000-0000-0000-0000-000000000086</guid>
+ <full-name>Resource 86</full-name>
+</record>
+<record type="resource">
+ <short-name>resource87</short-name>
+ <uid>40000000-0000-0000-0000-000000000087</uid>
+ <guid>40000000-0000-0000-0000-000000000087</guid>
+ <full-name>Resource 87</full-name>
+</record>
+<record type="resource">
+ <short-name>resource88</short-name>
+ <uid>40000000-0000-0000-0000-000000000088</uid>
+ <guid>40000000-0000-0000-0000-000000000088</guid>
+ <full-name>Resource 88</full-name>
+</record>
+<record type="resource">
+ <short-name>resource89</short-name>
+ <uid>40000000-0000-0000-0000-000000000089</uid>
+ <guid>40000000-0000-0000-0000-000000000089</guid>
+ <full-name>Resource 89</full-name>
+</record>
+<record type="resource">
+ <short-name>resource90</short-name>
+ <uid>40000000-0000-0000-0000-000000000090</uid>
+ <guid>40000000-0000-0000-0000-000000000090</guid>
+ <full-name>Resource 90</full-name>
+</record>
+<record type="resource">
+ <short-name>resource91</short-name>
+ <uid>40000000-0000-0000-0000-000000000091</uid>
+ <guid>40000000-0000-0000-0000-000000000091</guid>
+ <full-name>Resource 91</full-name>
+</record>
+<record type="resource">
+ <short-name>resource92</short-name>
+ <uid>40000000-0000-0000-0000-000000000092</uid>
+ <guid>40000000-0000-0000-0000-000000000092</guid>
+ <full-name>Resource 92</full-name>
+</record>
+<record type="resource">
+ <short-name>resource93</short-name>
+ <uid>40000000-0000-0000-0000-000000000093</uid>
+ <guid>40000000-0000-0000-0000-000000000093</guid>
+ <full-name>Resource 93</full-name>
+</record>
+<record type="resource">
+ <short-name>resource94</short-name>
+ <uid>40000000-0000-0000-0000-000000000094</uid>
+ <guid>40000000-0000-0000-0000-000000000094</guid>
+ <full-name>Resource 94</full-name>
+</record>
+<record type="resource">
+ <short-name>resource95</short-name>
+ <uid>40000000-0000-0000-0000-000000000095</uid>
+ <guid>40000000-0000-0000-0000-000000000095</guid>
+ <full-name>Resource 95</full-name>
+</record>
+<record type="resource">
+ <short-name>resource96</short-name>
+ <uid>40000000-0000-0000-0000-000000000096</uid>
+ <guid>40000000-0000-0000-0000-000000000096</guid>
+ <full-name>Resource 96</full-name>
+</record>
+<record type="resource">
+ <short-name>resource97</short-name>
+ <uid>40000000-0000-0000-0000-000000000097</uid>
+ <guid>40000000-0000-0000-0000-000000000097</guid>
+ <full-name>Resource 97</full-name>
+</record>
+<record type="resource">
+ <short-name>resource98</short-name>
+ <uid>40000000-0000-0000-0000-000000000098</uid>
+ <guid>40000000-0000-0000-0000-000000000098</guid>
+ <full-name>Resource 98</full-name>
+</record>
+<record type="resource">
+ <short-name>resource99</short-name>
+ <uid>40000000-0000-0000-0000-000000000099</uid>
+ <guid>40000000-0000-0000-0000-000000000099</guid>
+ <full-name>Resource 99</full-name>
+</record>
+<record type="resource">
+ <short-name>resource100</short-name>
+ <uid>40000000-0000-0000-0000-000000000100</uid>
+ <guid>40000000-0000-0000-0000-000000000100</guid>
+ <full-name>Resource 100</full-name>
+</record>
+</directory>
</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"> <!-- Principals with "DAV:all" access (relative URLs) -->
</span><span class="cx"> <key>AdminPrincipals</key>
</span><span class="cx"> <array>
</span><del>- <string>/principals/__uids__/admin/</string>
</del><ins>+ <string>/principals/__uids__/0C8BDE62-E600-4696-83D3-8B5ECABDFD2E/</string>
</ins><span class="cx"> </array>
</span><span class="cx">
</span><span class="cx"> <!-- Principals with "DAV:read" access (relative URLs) -->
</span><span class="lines">@@ -577,7 +577,7 @@
</span><span class="cx">
</span><span class="cx"> <!-- Log levels -->
</span><span class="cx"> <key>DefaultLogLevel</key>
</span><del>- <string>info</string> <!-- debug, info, warn, error -->
</del><ins>+ <string>debug</string> <!-- debug, info, warn, error -->
</ins><span class="cx">
</span><span class="cx"> <!-- Log level overrides for specific functionality -->
</span><span class="cx"> <key>LogLevels</key>
</span><span class="lines">@@ -1017,6 +1017,8 @@
</span><span class="cx"> <string>en</string>
</span><span class="cx"> </dict>
</span><span class="cx">
</span><del>-
</del><ins>+ <!-- Directory Address Book -->
+ <key>EnableSearchAddressBook</key>
+ <true/>
</ins><span class="cx"> </dict>
</span><span class="cx"> </plist>
</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 + "@example.com"
+ 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["accounts"] = {
</span><span class="cx"> "loader": "contrib.performance.loadtest.sim.recordsFromCSVFile",
</span><span class="cx"> "params": {
</span><del>- "path": accounts.path},
- }
</del><ins>+ "path": 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["accounts"] = {
</span><span class="cx"> "loader": "contrib.performance.loadtest.sim.recordsFromCSVFile",
</span><span class="cx"> "params": {
</span><del>- "path": ""},
- }
</del><ins>+ "path": ""
+ },
+ }
</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"> """
</span><span class="cx"> config = FilePath(self.mktemp())
</span><del>- config.setContent(writePlistToString({
- "server": "https://127.0.0.3:8432/"}))
</del><ins>+ config.setContent(
+ writePlistToString({"server": "https://127.0.0.3:8432/"})
+ )
</ins><span class="cx"> sim = LoadSimulator.fromCommandLine(['--config', config.path])
</span><span class="cx"> self.assertEquals(sim.server, "https://127.0.0.3:8432/")
</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"> """
</span><span class="cx"> config = FilePath(self.mktemp())
</span><del>- config.setContent(writePlistToString({
- "arrival": {
- "factory": "contrib.performance.loadtest.population.SmoothRampUp",
- "params": {
- "groups": 10,
- "groupSize": 1,
- "interval": 3,
- },
- },
- }))
</del><ins>+ config.setContent(
+ writePlistToString({
+ "arrival": {
+ "factory": "contrib.performance.loadtest.population.SmoothRampUp",
+ "params": {
+ "groups": 10,
+ "groupSize": 1,
+ "interval": 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"> """
</span><span class="cx"> config = FilePath(self.mktemp())
</span><del>- config.setContent(writePlistToString({
- "clients": [{
</del><ins>+ config.setContent(
+ writePlistToString(
+ {
+ "clients": [
+ {
</ins><span class="cx"> "software": "contrib.performance.loadtest.ical.OS_X_10_6",
</span><del>- "params": {"foo": "bar"},
- "profiles": [{
</del><ins>+ "params": {
+ "foo": "bar"
+ },
+ "profiles": [
+ {
</ins><span class="cx"> "params": {
</span><span class="cx"> "interval": 25,
</span><span class="cx"> "eventStartDistribution": {
</span><span class="lines">@@ -473,19 +488,38 @@
</span><span class="cx"> "params": {
</span><span class="cx"> "mu": 123,
</span><span class="cx"> "sigma": 456,
</span><del>- }}},
- "class": "contrib.performance.loadtest.profiles.Eventer"}],
</del><ins>+ }
+ }
+ },
+ "class": "contrib.performance.loadtest.profiles.Eventer"
+ }
+ ],
</ins><span class="cx"> "weight": 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, {"foo": "bar"}, [ProfileType(Eventer, {
</del><ins>+ 3,
+ ClientType(
+ OS_X_10_6,
+ {"foo": "bar"},
+ [
+ ProfileType(
+ Eventer, {
</ins><span class="cx"> "interval": 25,
</span><del>- "eventStartDistribution": NormalDistribution(123, 456)})]))
</del><ins>+ "eventStartDistribution": 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"> """
</span><span class="cx"> config = FilePath(self.mktemp())
</span><del>- config.setContent(writePlistToString({
- "observers": [{"type":"contrib.performance.loadtest.population.SimpleStatistics", "params":{}, }, ]
- }))
</del><ins>+ config.setContent(
+ writePlistToString(
+ {
+ "observers": [
+ {
+ "type": "contrib.performance.loadtest.population.SimpleStatistics",
+ "params": {},
+ },
+ ]
+ }
+ )
+ )
</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>=0.11
</span><ins>+mockldap>=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"> """
</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"> """
</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("/")
</span><span class="cx"> if len(uribits) > 1 and uribits[1] in ("principals", "calendars", "addressbooks"):
</span><span class="cx"> if uribits[2] == "__uids__":
</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] = "__uids__"
</span><del>- uribits[3] = record.uid
- return succeed("/".join(uribits))
</del><ins>+ uribits[3] = record.uid.encode("utf-8")
+ returnValue("/".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"> """
</span><span class="cx"> Get the current token for a particular URI.
</span><span class="cx"> """
</span><del>-
</del><ins>+ if isinstance(uri, unicode):
+ uri = uri.encode("utf-8")
</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("Could not locate URI: {e!r}", 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"> """
</span><span class="cx"> Resource which provisions address book home collections as needed.
</span><span class="cx"> """
</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"> """
</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"> """
</span><del>- def __init__(self, parent, recordType):
</del><ins>+ def __init__(self, parent, name, recordType):
</ins><span class="cx"> """
</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"> """
</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, "hasContacts", 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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-Directory service implementation which aggregates multiple directory
-services.
-"""
-
-__all__ = [
- "AggregateDirectoryService",
- "DuplicateRecordTypeError",
-]
-
-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):
- """
- 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.
- """
- baseGUID = "06FB225F-39E7-4D34-B1D1-29925F5E619B"
-
- 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, (
- "Aggregated directory services must have the same realm name: %r != %r\nServices: %r"
- % (service.realmName, realmName, services)
- )
- realmName = service.realmName
-
- if not hasattr(service, "recordTypePrefix"):
- service.recordTypePrefix = ""
- prefix = service.recordTypePrefix
-
- for recordType in (prefix + r for r in service.recordTypes()):
- if recordType in recordTypes:
- raise DuplicateRecordTypeError(
- "%r is in multiple services: %s, %s"
- % (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 = {
- "uids" : { },
- "shortNames" : { },
- }
-
-
- def __repr__(self):
- return "<%s (%s): %r>" % (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):
- """
- Add another service to this aggregate.
-
- @param service: the service to add
- @type service: L{IDirectoryService}
- """
- service = IDirectoryService(service)
-
- if service.realmName != self.realmName:
- assert self.realmName is None, (
- "Aggregated directory services must have the same realm name: %r != %r\nServices: %r"
- % (service.realmName, self.realmName, service)
- )
-
- if not hasattr(service, "recordTypePrefix"):
- service.recordTypePrefix = ""
- prefix = service.recordTypePrefix
-
- for recordType in (prefix + r for r in service.recordTypes()):
- if recordType in self._recordTypes:
- raise DuplicateRecordTypeError(
- "%r is in multiple services: %s, %s"
- % (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("listRecords", 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["shortNames"].get(shortName, None)
- if record:
- return record
-
- return self._query("recordWithShortName", 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["uids"].get(uid, None)
- if record:
- return record
-
- return self._queryAll("recordWithUID", uid)
-
- recordWithGUID = recordWithUID
-
- def recordWithAuthID(self, authID):
- return self._queryAll("recordWithAuthID", authID)
-
-
- def recordWithCalendarUserAddress(self, address):
- return self._queryAll("recordWithCalendarUserAddress", address)
-
-
- def recordWithCachedGroupsAlias(self, recordType, alias):
- """
- @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.
- """
- service = self.serviceForRecordType(recordType)
- return service.recordWithCachedGroupsAlias(recordType, alias)
-
-
- @inlineCallbacks
- def recordsMatchingFields(self, fields, operand="or", 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):
- """
- 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
- "location", only locations are considered. If context is "attendee",
- 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;
- "attendee", "location", or None
- @type context: C{str}
-
- @return: a deferred sequence of L{IDirectoryRecord}s which match the
- given tokens and optional context.
- """
-
- 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):
- """
- 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.
- """
- 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, "_initCaches"):
- service._initCaches()
-
- userRecordTypes = [DirectoryService.recordType_users]
-
- def requestAvatarId(self, credentials):
-
- if credentials.authnPrincipal:
- return credentials.authnPrincipal.record.service.requestAvatarId(credentials)
-
- raise UnauthorizedLogin("No such user: %s" % (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):
- """
- Set a new realm name for this and nested services
- """
- self.realmName = realmName
- for service in self._recordTypes.values():
- service.setRealm(realmName)
-
-
- def setPrincipalCollection(self, principalCollection):
- """
- Set the principal service that the directory relies on for doing proxy tests.
-
- @param principalService: the principal service.
- @type principalService: L{DirectoryProvisioningResource}
- """
- self.principalCollection = principalCollection
- for service in self._recordTypes.values():
- service.setPrincipalCollection(principalCollection)
-
-
-
-class DuplicateRecordTypeError(DirectoryError):
- """
- Duplicate record type.
- """
</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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-Apple OpenDirectory directory service implementation.
-"""
-
-__all__ = [
- "OpenDirectoryService",
- "OpenDirectoryInitError",
-]
-
-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):
- """
- OpenDirectory implementation of L{IDirectoryService}.
- """
- log = Logger()
-
- baseGUID = "891F8321-ED02-424C-BA72-89C32F215C1E"
-
- def __repr__(self):
- return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, self.node)
-
-
- def __init__(self, params, odModule=None):
- """
- @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
- """
- 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("OpenDirectory (node=%s) Initialization error: %s" % (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 == "/Search":
- result = self.odModule.getNodeAttributes(self.directory, "/Search",
- (dsattributes.kDS1AttrSearchPath,))
- if "/Local/Default" in result[dsattributes.kDS1AttrSearchPath]:
- try:
- self.localNode = self.odModule.odInit("/Local/Default")
- except self.odModule.ODError, e:
- self.log.error("Failed to open /Local/Default): %s" % (e,))
- except AttributeError:
- pass
-
-
- @property
- def restrictedGUIDs(self):
- """
- 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.
- """
- if self.restrictEnabledRecords:
- if time.time() - self.restrictedTimestamp > self.cacheTimeout:
- attributeToMatch = dsattributes.kDS1AttrGeneratedUID if self.restrictToGUID else dsattributes.kDSNAttrRecordName
- valueToMatch = self.restrictToGroup
- self.log.debug("Doing restricted group membership check")
- self.log.debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- 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("Got %d restricted group members" % (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 ("directory", "node"):
- 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 ("node",):
- h = (h + hash(getattr(self, attr))) & 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("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- 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("Couldn't find group %s when trying to expand nested groups."
- % (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):
- """
- Retrieve all the records of recordType from the directory, but for
- expediency don't index them or cache them locally, nor in memcached.
- """
-
- 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("Querying OD for all %s records" % (recordType,))
- results = self.odModule.listAllRecordsWithAttributes_list(
- self.directory, ODRecordType, attrs)
- self.log.debug("Retrieved %d %s records" % (len(results), recordType,))
-
- for key, value in results:
- recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
- if not recordGUID:
- self.log.warn("Ignoring record missing GUID: %s %s" %
- (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="",
- shortNames=recordShortNames,
- authIDs=(),
- fullName=recordFullName,
- firstName="",
- lastName="",
- emailAddresses="",
- 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("ListRecords returning %d %s records" % (len(records),
- recordType))
-
- return records
-
-
- def groupsForGUID(self, guid):
-
- attrs = [
- dsattributes.kDS1AttrGeneratedUID,
- ]
-
- recordType = dsattributes.kDSStdRecordTypeGroups
-
- guids = set()
-
- self.log.debug("Looking up which groups %s is a member of" % (guid,))
- try:
- self.log.debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- 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("OpenDirectory (node=%s) error: %s" % (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("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- 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("OpenDirectory (node=%s) error: %s" % (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("%s is a member of %d groups" % (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):
- """
- @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; "attendee", "location", 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 "location", only locations are considered. If
- context is "attendee", only users, groups, and resources
- are considered.
- """
-
- if lookupMethod is None:
- lookupMethod = self.odModule.queryRecordsWithAttributes_list
-
- def collectResults(results):
- self.log.debug("Got back %d records from OD" % (len(results),))
- for _ignore_key, value in results:
- # self.log.debug("OD result: {key} {value}", 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("Attendee OD query: Types %s, Query %s, %.2f sec, %d results" %
- (recordTypes, compound, totalTime, len(queryResults)))
- sets.append(newSet)
-
- except self.odModule.ODError, e:
- self.log.error("Ignoring OD Error: %d %s" %
- (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) > 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="or", 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("Got back %d records from OD" % (len(results),))
- for _ignore_key, value in results:
- # self.log.debug("OD result: {key} {value}", 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 == "starts-with":
- comparison = dsattributes.eDSStartsWith
- elif matchType == "contains":
- comparison = dsattributes.eDSContains
- else:
- comparison = dsattributes.eDSExact
-
- self.log.debug("Calling OD: Types %s, Field %s, Value %s, Match %s, Caseless %s" %
- (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("Ignoring OD Error: %d %s" %
- (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 == "or"
- 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, "Invalid type for record faulting query"
- # 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("Unknown OpenDirectory record type: %s" % (recordType))
-
- # Because we're getting transient OD error -14987, try 3 times:
- for _ignore in xrange(3):
- try:
- self.log.debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
- 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("OpenDirectory (node=%s) error: %s" % (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("OpenDirectory (node=%s) error: %s" % (self.realmName, str(ex)))
- raise
- else:
- # Success, so break the retry loop
- break
-
- self.log.debug("opendirectory.queryRecordsWithAttribute_list matched records: %s" % (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("Record (unknown)%s in node %s has no recordType; ignoring."
- % (recordShortName, recordNodeName))
- continue
-
- recordType = self._fromODRecordTypes[recordType]
-
- if not recordGUID:
- self.log.debug("Record (%s)%s in node %s has no GUID; ignoring."
- % (recordType, recordShortName, recordNodeName))
- continue
-
- if recordGUID.lower().startswith("ffffeeee-dddd-cccc-bbbb-aaaa"):
- self.log.debug("Ignoring system record (%s)%s in node %s."
- % (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("%s is not enabled because it's not a member of group: %s" % (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) > 1:
- self.log.error("Duplicate records found for GUID %s:" % (indexKey,))
- for duplicateRecord in enabledRecords:
- self.log.error("Duplicate: %s" % (", ".join(duplicateRecord.shortNames)))
-
- if record:
- if isinstance(origIndexKey, unicode):
- origIndexKey = origIndexKey.encode("utf-8")
- self.log.debug("Storing (%s %s) %s in internal cache" % (indexType, origIndexKey, record))
-
- self.recordCacheForType(recordType).addRecord(record, indexType, origIndexKey)
-
-
- def getResourceInfo(self):
- """
- 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.
- """
- attrs = [
- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDSNAttrResourceInfo,
- ]
-
- for recordType in (dsattributes.kDSStdRecordTypePlaces, dsattributes.kDSStdRecordTypeResources):
- try:
- self.log.debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)" % (
- self.directory,
- recordType,
- attrs,
- ))
- results = self.odModule.listAllRecordsWithAttributes_list(
- self.directory,
- recordType,
- attrs,
- )
- except self.odModule.ODError, ex:
- self.log.error("OpenDirectory (node=%s) error: %s" % (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):
- """
- Returns True if all configured directory nodes are accessible, False otherwise
- """
-
- if self.node == "/Search":
- result = self.odModule.getNodeAttributes(self.directory, "/Search",
- (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("OpenDirectory Node %s not available" % (node,))
- return False
-
- return True
-
-
- @inlineCallbacks
- def getGroups(self, guids):
- """
- 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.
- """
-
- recordsByGUID = {}
- valuesToFetch = guids
-
- loop = 1
- while valuesToFetch:
- self.log.debug("getGroups loop %d" % (loop,))
-
- results = []
-
- for batch in splitIntoBatches(valuesToFetch, self.batchSize):
- fields = []
- for value in batch:
- fields.append(["guid", value, False, "equals"])
- self.log.debug("getGroups fetching batch of %d" %
- (len(fields),))
- result = list((yield self.recordsMatchingFields(fields,
- recordType=self.recordType_groups)))
- results.extend(result)
- self.log.debug("getGroups got back batch of %d for subtotal of %d" %
- (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("getGroups group %s contains group %s" %
- (record.guid, memberGUID))
- valuesToFetch.add(memberGUID)
-
- loop += 1
-
- returnValue(recordsByGUID.values())
-
-
-
-def buildQueries(recordTypes, fields, mapping):
- """
- 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).
- """
-
- 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):
- """
- 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}
- """
-
- if len(tokens) == 0:
- return None
-
- fields = [
- ("fullName", dsattributes.eDSContains),
- ("emailAddresses", 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):
- """
- 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
- """
-
- if len(tokens) == 0:
- return None
-
- fields = [
- ("fullName", dsattributes.eDSContains),
- ("emailAddresses", dsattributes.eDSStartsWith),
- ("recordName", 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):
- """
- OpenDirectory implementation of L{IDirectoryRecord}.
- """
- 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 = "%s->%s" % (self.service.realmName, self.nodeName)
-
- return "<%s[%s@%s(%s)] %s(%s) %r>" % (
- self.__class__.__name__,
- self.recordType,
- self.service.guid,
- location,
- self.guid,
- ",".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("OpenDirectory (node=%s) error while performing basic authentication for user %s: %s"
- % (self.service.realmName, self.shortNames[0], e))
-
- return False
-
- elif isinstance(credentials, DigestedCredentials):
- #
- # We need a special format for the "challenge" and "response" strings passed into OpenDirectory, as it is
- # picky about exactly what it receives.
- #
- try:
- if "algorithm" not in credentials.fields:
- credentials.fields["algorithm"] = "md5"
- challenge = 'Digest realm="%(realm)s", nonce="%(nonce)s", algorithm=%(algorithm)s' % credentials.fields
- response = (
- 'Digest username="%(username)s", '
- 'realm="%(realm)s", '
- 'nonce="%(nonce)s", '
- 'uri="%(uri)s", '
- 'response="%(response)s",'
- 'algorithm=%(algorithm)s'
- ) % credentials.fields
- except KeyError, e:
- self.log.error(
- "OpenDirectory (node=%s) error while performing digest authentication for user %s: "
- "missing digest response field: %s in: %s"
- % (self.service.realmName, self.shortNames[0], e, credentials.fields)
- )
- return False
-
- try:
- if self.digestcache[credentials.fields["uri"]] == 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["uri"]] = response
-
- return True
- else:
- self.log.debug(
-"""OpenDirectory digest authentication failed with:
- Nodename: %s
- Username: %s
- Challenge: %s
- Response: %s
- Method: %s
-""" % (self.nodeName, self.shortNames[0], challenge, response,
- credentials.method))
-
- except self.service.odModule.ODError, e:
- self.log.error(
- "OpenDirectory (node=%s) error while performing digest authentication for user %s: %s"
- % (self.service.realmName, self.shortNames[0], e)
- )
- return False
-
- return False
-
- return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
-
-
-
-class OpenDirectoryInitError(DirectoryError):
- """
- OpenDirectory initialization error.
- """
</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"> "automatic",
</span><span class="cx"> ))
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> class AugmentRecord(object):
</span><span class="cx"> """
</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="default",
</span><del>- autoAcceptGroup="",
</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>- "users" : "User",
- "groups" : "Group",
- "locations" : "Location",
- "resources" : "Resource",
- "addresses" : "Address",
</del><ins>+ "users": "User",
+ "groups": "Group",
+ "locations": "Location",
+ "resources": "Resource",
+ "addresses": "Address",
+ "wikis": "Wiki",
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> class AugmentDB(object):
</span><span class="cx"> """
</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"> """
</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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-Caching directory service implementation.
-"""
-
-__all__ = [
- "CachingDirectoryService",
- "CachingDirectoryRecord",
- "DictRecordTypeCache",
-]
-
-
-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):
- """
- Abstract class for a record type cache. We will likely have dict and memcache implementations of this.
- """
-
- 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):
- """
- Cache implementation using a dict, and uses memcached to share records
- with other instances.
- """
- 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("Memcache: storing %s" % (key,))
- try:
- self.directoryService.memcacheSet(key, record)
- except DirectoryMemcacheError:
- self.log.error("Memcache: failed to store %s" % (key,))
- pass
-
-
- def removeRecord(self, record):
- if record in self.records:
- self.records.remove(record)
- self.log.debug("Removed record %s" % (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):
- """
- Scan the cached records and remove any that have expired.
- Does nothing if we've scanned within the past cacheTimeout seconds.
- """
- if time.time() - self.lastPurgedTime > self.directoryService.cacheTimeout:
- for record in list(self.records):
- if record.isExpired():
- self.removeRecord(record)
- self.lastPurgedTime = time.time()
-
-
-
-class CachingDirectoryService(DirectoryService):
- """
- Caching Directory implementation of L{IDirectoryService}.
-
- This is class must be overridden to provide a concrete implementation.
- """
- log = Logger()
-
- INDEX_TYPE_GUID = "guid"
- INDEX_TYPE_SHORTNAME = "shortname"
- INDEX_TYPE_CUA = "cua"
- INDEX_TYPE_AUTHID = "authid"
-
- indexTypeToRecordAttribute = {
- "guid" : "guid",
- "shortname": "shortNames",
- "cua" : "calendarUserAddresses",
- "authid" : "authIDs",
- }
-
- def __init__(
- self,
- cacheTimeout=1,
- negativeCaching=False,
- cacheClass=DictRecordTypeCache,
- ):
- """
- @param cacheTimeout: C{int} number of minutes before cache is invalidated.
- """
-
- 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, "memcacheClient"):
- 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("Could not write to memcache, retrying")
- if not self._getMemcacheClient(refresh=True).set(
- key, record,
- time=self.cacheTimeout
- ):
- self.log.error("Could not write to memcache again, giving up")
- del self.memcacheClient
- raise DirectoryMemcacheError("Failed to write to memcache")
- 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("Could not read from memcache, retrying")
- 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("Could not read from memcache again, giving up")
- del self.memcacheClient
- raise DirectoryMemcacheError("Failed to read from memcache")
- return record
-
-
- def generateMemcacheKey(self, indexType, indexKey, recordType):
- """
- 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}
- """
- keyVersion = 2
- if indexType == CachingDirectoryService.INDEX_TYPE_SHORTNAME:
- return "dir|v%d|%s|%s|%s|%s" % (keyVersion, self.baseGUID, recordType,
- indexType, indexKey)
- else:
- return "dir|v%d|%s|%s|%s" % (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("mailto:"):
- 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 < 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("Memcache: checking %s" % (key,))
-
- try:
- record = self.memcacheGet(key)
- except DirectoryMemcacheError:
- self.log.error("Memcache: failed to get %s" % (key,))
- record = None
-
- if record is None:
- self.log.debug("Memcache: miss %s" % (key,))
- else:
- self.log.debug("Memcache: hit %s" % (key,))
- self.recordCacheForType(record.recordType).addRecord(record, indexType, indexKey, useMemcache=False)
- return record
-
- if self.negativeCaching:
-
- # Check negative memcache
- try:
- val = self.memcacheGet("-%s" % (key,))
- except DirectoryMemcacheError:
- self.log.error("Memcache: failed to get -%s" % (key,))
- val = None
- if val == 1:
- self.log.debug("Memcache: negative %s" % (key,))
- self._disabledKeys[indexType][indexKey] = time.time()
- return None
-
- # Try query
- self.log.debug("Faulting record for attribute '%s' with value '%s'" % (indexType, indexKey,))
- self.queryDirectory(recordTypes, indexType, indexKey)
-
- # Now try again from cache
- record = lookup()
- if record:
- self.log.debug("Found record for attribute '%s' with value '%s'" % (indexType, indexKey,))
- return record
-
- if self.negativeCaching:
-
- # Add to negative cache with timestamp
- self.log.debug("Failed to fault record for attribute '%s' with value '%s'" % (indexType, indexKey,))
- self._disabledKeys[indexType][indexKey] = time.time()
-
- if config.Memcached.Pools.Default.ClientEnabled:
- self.log.debug("Memcache: storing (negative) %s" % (key,))
- try:
- self.memcacheSet("-%s" % (key,), 1)
- except DirectoryMemcacheError:
- self.log.error("Memcache: failed to set -%s" % (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):
- """
- Returns True if this record was created more than cacheTimeout
- seconds ago
- """
- if (
- self.cachedTime != 0 and
- time.time() - self.cachedTime > self.service.cacheTimeout
- ):
- return True
- else:
- return False
-
-
-
-class DirectoryMemcacheError(DirectoryError):
- """
- Error communicating with memcached.
- """
</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"> """
</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"> """
</span><del>- def __init__(self, parent, recordType):
</del><ins>+ def __init__(self, parent, name, recordType):
</ins><span class="cx"> """
</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"> """
</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 = ""
</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 == "calendar-proxy-read":
</span><del>- return davxml.ResourceType.calendarproxyread #@UndefinedVariable
</del><ins>+ return davxml.ResourceType.calendarproxyread # @UndefinedVariable
</ins><span class="cx"> elif self.proxyType == "calendar-proxy-write":
</span><del>- return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
</del><ins>+ return davxml.ResourceType.calendarproxywrite # @UndefinedVariable
+ elif self.proxyType == "calendar-proxy-read-for":
+ return davxml.ResourceType.calendarproxyreadfor # @UndefinedVariable
+ elif self.proxyType == "calendar-proxy-write-for":
+ 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 {
+ "calendar-proxy-read": DelegateRecordType.readDelegateGroup,
+ "calendar-proxy-write": DelegateRecordType.writeDelegateGroup,
+ "calendar-proxy-read-for": DelegateRecordType.readDelegatorGroup,
+ "calendar-proxy-write-for": 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("Delegate is missing from directory: %s" % (uid,))
</del><ins>+ """
+ 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.
+ """
+ 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"> """
</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"> """
</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(
- "select GROUPNAME, MEMBER from GROUPS"))
- ):
</del><ins>+ (yield self._db_all_values_for_sql(
+ "select GROUPNAME, MEMBER from GROUPS"))
+ ):
</ins><span class="cx"> grouplist = groupname.split("#")
</span><span class="cx"> grouplist[0] = normalizeUUID(grouplist[0])
</span><span class="cx"> newGroupName = "#".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 = "repeat"
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> class XMLCalendarUserProxyLoader(object):
</span><span class="cx"> """
</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 "<%s %r>" % (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("%s#%s" % (guid, "calendar-proxy-write"), 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 = "__uids__"
</span><span class="cx">
</span><ins>+
</ins><span class="cx"> class CommonUIDProvisioningResource(object):
</span><span class="cx"> """
</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("No directory record with GUID %r" % (name,))
</del><ins>+ log.debug("No directory record with UID %r" % (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("Directory record %r is not enabled for %s" % (
</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 == "":
</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 == "":
</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: <t:slot name="principalGUID"/>
</span><span class="cx"> Record type: <t:slot name="recordType"/>
</span><span class="cx"> Short names: <t:slot name="shortNames"/>
</span><del>-Security Identities: <t:slot name="securityIDs"/>
</del><span class="cx"> Full name: <t:slot name="fullName"/>
</span><del>-First name: <t:slot name="firstName"/>
-Last name: <t:slot name="lastName"/>
</del><span class="cx"> Email addresses:
</span><span class="cx"> <t:slot name="emailAddresses" />Principal UID: <t:slot name="principalUID"/>
</span><span class="cx"> Principal URL: <t:slot name="principalURL"/>
</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 "License");
-# 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 "AS IS" 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.
-##
-
-
-"""
-Generic directory service classes.
-"""
-
-__all__ = [
- "DirectoryService",
- "DirectoryRecord",
- "DirectoryError",
- "DirectoryConfigurationError",
- "UnknownRecordTypeError",
- "GroupMembershipCacheUpdater",
-]
-
-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 = "users"
- recordType_people = "people"
- recordType_groups = "groups"
- recordType_locations = "locations"
- recordType_resources = "resources"
- recordType_addresses = "addresses"
-
- searchContext_location = "location"
- searchContext_resource = "resource"
- searchContext_user = "user"
- searchContext_group = "group"
- searchContext_attendee = "attendee"
-
- aggregateService = None
-
- def _generatedGUID(self):
- if not hasattr(self, "_guid"):
- realmName = self.realmName
-
- assert self.baseGUID, "Class %s must provide a baseGUID attribute" % (self.__class__.__name__,)
-
- if realmName is None:
- self.log.error("Directory service %s has no realm name or GUID; generated service GUID will not be unique." % (self,))
- realmName = ""
- else:
- self.log.info("Directory service %s has no GUID; generating service GUID from realm name." % (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):
- """
- By default, the directory is available. This may return a boolean or a
- Deferred which fires a boolean.
-
- A return value of "False" means that the directory is currently
- unavailable due to the service starting up.
- """
- 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("No such user: %s" % (credentials.credentials.username,))
-
- # See if record is enabledForLogin
- if not credentials.authnPrincipal.record.isLoginEnabled():
- raise UnauthorizedLogin("User not allowed to log in: %s" %
- (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("Incorrect credentials for %s" % (credentials.credentials.username,))
-
-
- def recordTypes(self):
- raise NotImplementedError("Subclass must implement recordTypes()")
-
-
- def listRecords(self, recordType):
- raise NotImplementedError("Subclass must implement listRecords()")
-
-
- 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("urn:uuid:"):
- guid = address[9:]
- record = self.recordWithGUID(guid)
- elif address.startswith("mailto:"):
- for record in self.allRecords():
- if address[7:] in record.emailAddresses:
- break
- else:
- return None
- elif address.startswith("/principals/"):
- parts = map(unquote, address.split("/"))
- if len(parts) == 4:
- if parts[2] == "__uids__":
- 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):
- """
- @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.
- """
- # 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="or",
- cuType=None):
- if cuType:
- recordType = DirectoryRecord.fromCUType(cuType)
- else:
- recordType = None
-
- return self.recordsMatchingFields(fields, operand=operand,
- recordType=recordType)
-
-
- def recordTypesForSearchContext(self, context):
- """
- 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}
- """
- 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):
- """
- @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; "attendee", "location", 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 "location", only locations are considered. If
- context is "attendee", only users, groups, and resources
- are considered.
- """
-
- # 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 ["fullName", "emailAddresses"]:
- 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="or", 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 == "and":
- 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 => no match
- return False
- # we hit on every property
- return True
- else: # "or"
- 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):
- """
- This implementation returns all groups, not just the ones specified
- by guids
- """
- return succeed(self.listRecords(self.recordType_groups))
-
-
- def getResourceInfo(self):
- return ()
-
-
- def isAvailable(self):
- return True
-
-
- def getParams(self, params, defaults, ignore=None):
- """ Checks configuration parameters for unexpected/ignored keys, and
- applies default values. """
-
- 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("Ignoring obsolete directory service parameter: %s" % (key,))
- keys.remove(key)
-
- if keys:
- raise DirectoryConfigurationError("Invalid directory service parameter(s): %s" % (", ".join(list(keys)),))
- return result
-
-
- def parseResourceInfo(self, plist, guid, recordType, shortname):
- """
- 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.
- """
- try:
- plist = readPlistFromString(plist)
- wpframework = plist.get("com.apple.WhitePagesFramework", {})
- autoaccept = wpframework.get("AutoAcceptsInvitation", False)
- proxy = wpframework.get("CalendaringDelegate", None)
- read_only_proxy = wpframework.get("ReadOnlyCalendaringDelegate", None)
- autoAcceptGroup = wpframework.get("AutoAcceptGroup", "")
- except (ExpatError, AttributeError), e:
- self.log.error(
- "Failed to parse ResourceInfo attribute of record (%s)%s (guid=%s): %s\n%s" %
- (recordType, shortname, guid, e, plist,)
- )
- raise ValueError("Invalid ResourceInfo")
-
- return (autoaccept, proxy, read_only_proxy, autoAcceptGroup)
-
-
- def getExternalProxyAssignments(self):
- """
- 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.
- """
- 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(("%s#calendar-proxy-write" % (guid,),
- record.externalProxies()))
- assignments.append(("%s#calendar-proxy-read" % (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):
- """
- Create/persist a directory record based on the given values
- """
- raise NotImplementedError("Subclass must implement createRecord")
-
-
- def updateRecord(self, recordType, guid=None, shortNames=(), authIDs=set(),
- fullName=None, firstName=None, lastName=None, emailAddresses=set(),
- uid=None, password=None, **kwargs):
- """
- Update/persist a directory record based on the given values
- """
- raise NotImplementedError("Subclass must implement updateRecord")
-
-
- def destroyRecord(self, recordType, guid=None):
- """
- Remove a directory record from the directory
- """
- raise NotImplementedError("Subclass must implement destroyRecord")
-
-
- def createRecords(self, data):
- """
- Create directory records in bulk
- """
- raise NotImplementedError("Subclass must implement createRecords")
-
-
- def setPrincipalCollection(self, principalCollection):
- """
- Set the principal service that the directory relies on for doing proxy tests.
-
- @param principalService: the principal service.
- @type principalService: L{DirectoryProvisioningResource}
- """
- self.principalCollection = principalCollection
-
-
- def isProxyFor(self, test, other):
- """
- 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}
- """
- return self.principalCollection.isProxyFor(test, other)
-
-
-
-class GroupMembershipCache(Memcacher):
- """
- 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:
-
- "groups-for:<GUID>" : comma-separated list of groups that GUID is a member
- of. Note that when using LDAP, the key for this is an LDAP DN.
-
- "group-cacher-populated" : contains a datestamp indicating the most recent
- population.
- """
- 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("set groups-for %s : %s" % (guid, memberships))
- return self.set("groups-for:%s" %
- (str(guid)), memberships,
- expireTime=self.expireSeconds)
-
-
- def getGroupsFor(self, guid):
- self.log.debug("get groups-for %s" % (guid,))
- def _value(value):
- if value:
- return value
- else:
- return set()
- d = self.get("groups-for:%s" % (str(guid),))
- d.addCallback(_value)
- return d
-
-
- def deleteGroupsFor(self, guid):
- self.log.debug("delete groups-for %s" % (guid,))
- return self.delete("groups-for:%s" % (str(guid),))
-
-
- def setPopulatedMarker(self):
- self.log.debug("set group-cacher-populated")
- return self.set("group-cacher-populated", str(datetime.datetime.now()))
-
-
- @inlineCallbacks
- def isPopulated(self):
- self.log.debug("is group-cacher-populated")
- value = (yield self.get("group-cacher-populated"))
- returnValue(value is not None)
-
-
- def acquireLock(self):
- """
- Acquire a memcached lock named group-cacher-lock
-
- return: Deferred firing True if successful, False if someone already has
- the lock
- """
- self.log.debug("add group-cacher-lock")
- return self.add("group-cacher-lock", "1", expireTime=self.lockSeconds)
-
-
- def extendLock(self):
- """
- Update the expiration time of the memcached lock
- Return: Deferred firing True if successful, False otherwise
- """
- self.log.debug("extend group-cacher-lock")
- return self.set("group-cacher-lock", "1", expireTime=self.lockSeconds)
-
-
- def releaseLock(self):
- """
- Release the memcached lock
- Return: Deferred firing True if successful, False otherwise
- """
- self.log.debug("delete group-cacher-lock")
- return self.delete("group-cacher-lock")
-
-
-
-class GroupMembershipCacheUpdater(object):
- """
- 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.
- """
- 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, "namespace must be specified if GroupMembershipCache is not provided"
- cache = GroupMembershipCache(namespace, expireSeconds=expireSeconds,
- lockSeconds=lockSeconds)
- self.cache = cache
-
-
- @inlineCallbacks
- def getGroups(self, guids=None):
- """
- 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
- """
- 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):
- """
- Return the complete, flattened set of members of a group, including
- all sub-groups, based on the group hierarchy described in the
- groups dictionary.
- """
- 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):
- """
- 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.
- """
-
- # 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("Group membership cache is already populated")
- returnValue((fast, 0, 0))
-
- # We don't care what others are doing right now, we need to update
- useLock = False
-
- self.log.info("Updating group membership cache")
-
- dataRoot = FilePath(config.DataRoot)
- membershipsCacheFile = dataRoot.child("memberships_cache")
- extProxyCacheFile = dataRoot.child("external_proxy_cache")
-
- if not membershipsCacheFile.exists():
- self.log.info("Group membership snapshot file does not yet exist")
- fast = False
- previousMembers = {}
- callGroupsChanged = False
- else:
- self.log.info("Group membership snapshot file exists: %s" %
- (membershipsCacheFile.path,))
- callGroupsChanged = True
- try:
- previousMembers = pickle.loads(membershipsCacheFile.getContent())
- except:
- self.log.warn("Could not parse snapshot; will regenerate cache")
- fast = False
- previousMembers = {}
- callGroupsChanged = False
-
- if useLock:
- self.log.info("Attempting to acquire group membership cache lock")
- acquiredLock = (yield self.cache.acquireLock())
- if not acquiredLock:
- self.log.info("Group membership cache lock held by another process")
- returnValue((fast, 0, 0))
- self.log.info("Acquired lock")
-
- 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("External proxies snapshot file exists: %s" %
- (extProxyCacheFile.path,))
- try:
- previousAssignments = pickle.loads(extProxyCacheFile.getContent())
- except:
- self.log.warn("Could not parse external proxies snapshot")
- previousAssignments = []
-
- if useLock:
- yield self.cache.extendLock()
-
- self.log.info("Retrieving proxy assignments from directory")
- assignments = self.externalProxiesSource()
- self.log.info("%d proxy assignments retrieved from directory" %
- (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("Updating proxy assignments")
- assignmentCount = 0
- totalNumAssignments = len(changed)
- currentAssignmentNum = 0
- for principalUID, members in changed:
- currentAssignmentNum += 1
- if currentAssignmentNum % 1000 == 0:
- self.log.info("...proxy assignment %d of %d" % (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("Unable to update proxy assignment: principal=%s, members=%s, error=%s" % (principalUID, members, e))
- self.log.info("Updated %d assignment%s in proxy database" %
- (assignmentCount, "" if assignmentCount == 1 else "s"))
-
- if removed:
- self.log.info("Deleting proxy assignments")
- assignmentCount = 0
- totalNumAssignments = len(removed)
- currentAssignmentNum = 0
- for principalUID in removed:
- currentAssignmentNum += 1
- if currentAssignmentNum % 1000 == 0:
- self.log.info("...proxy assignment %d of %d" % (currentAssignmentNum,
- totalNumAssignments))
- try:
- assignmentCount += 1
- yield self.proxyDB.setGroupMembers(principalUID, [])
- except Exception, e:
- self.log.error("Unable to remove proxy assignment: principal=%s, members=%s, error=%s" % (principalUID, members, e))
- self.log.info("Removed %d assignment%s from proxy database" %
- (assignmentCount, "" if assignmentCount == 1 else "s"))
-
- # Store external proxy snapshot
- self.log.info("Taking snapshot of external proxies to %s" %
- (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("Loading group memberships from snapshot")
- 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("Retrieving list of all proxies")
- # This is always a set of guids:
- delegatedGUIDs = set((yield self.proxyDB.getAllMembers()))
- self.log.info("There are %d proxies" % (len(delegatedGUIDs),))
- self.log.info("Retrieving group hierarchy from directory")
-
- # "groups" 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().
- # "aliases" 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("%d groups retrieved from the directory" %
- (len(groupGUIDs),))
-
- delegatedGUIDs = delegatedGUIDs.intersection(groupGUIDs)
- self.log.info("%d groups are proxies" % (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("There are %d users delegated-to via groups" %
- (len(members),))
-
- # Store snapshot
- self.log.info("Taking snapshot of group memberships to %s" %
- (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("Storing %d group memberships in memcached" %
- (len(members),))
- changedMembers = set()
- totalNumMembers = len(members)
- currentMemberNum = 0
- for member, groups in members.iteritems():
- currentMemberNum += 1
- if currentMemberNum % 1000 == 0:
- self.log.info("...membership %d of %d" % (currentMemberNum,
- totalNumMembers))
- # self.log.debug("%s is in %s" % (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, "principalCollection"):
- 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("Group membership changed for %s (%s)" %
- (record.shortNames[0], record.guid,))
- if hasattr(principal, "groupsChanged"):
- yield principal.groupsChanged()
-
- yield self.cache.setPopulatedMarker()
-
- if useLock:
- self.log.info("Releasing lock")
- yield self.cache.releaseLock()
-
- self.log.info("Group memberships cache updated")
-
- returnValue((fast, len(members), len(changedMembers)))
-
-
-
-def diffAssignments(old, new):
- """
- 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.
- """
- 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 "<%s[%s@%s(%s)] %s(%s) %r @ %s>" % (
- self.__class__.__name__,
- self.recordType,
- self.service.guid,
- self.service.realmName,
- self.guid,
- ",".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="",
- 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 = ""
-
- self.service = service
- self.recordType = recordType
- self.guid = guid
- self.uid = uid
- self.enabled = False
- self.serverID = ""
- 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):
- """
- Dynamically construct a calendarUserAddresses attribute which describes
- this L{DirectoryRecord}.
-
- @see: L{IDirectoryRecord.calendarUserAddresses}.
- """
- if not self.enabledForCalendaring:
- return frozenset()
- cuas = set(
- ["mailto:%s" % (emailAddress,)
- for emailAddress in self.emailAddresses]
- )
- if self.guid:
- cuas.add("urn:uuid:%s" % (self.guid,))
- cuas.add(joinURL("/principals", "__uids__", self.guid) + "/")
- for shortName in self.shortNames:
- cuas.add(joinURL("/principals", self.recordType, shortName,) + "/")
-
- return frozenset(cuas)
-
- calendarUserAddresses = property(get_calendarUserAddresses)
-
- def __cmp__(self, other):
- if not isinstance(other, DirectoryRecord):
- return NotImplemented
-
- for attr in ("service", "recordType", "shortNames", "guid"):
- 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 ("service", "recordType", "shortNames", "guid",
- "enabled", "enabledForCalendaring"):
- h = (h + hash(getattr(self, attr))) & sys.maxint
-
- return h
-
-
- def cacheToken(self):
- """
- Generate a token that can be uniquely used to identify the state of this record for use
- in a cache.
- """
- 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("Group '%s(%s)' cannot be enabled for calendaring or address books" % (self.guid, self.shortNames[0],))
-
- else:
- # Groups are by default always enabled
- self.enabled = (self.recordType == self.service.recordType_groups)
- self.serverID = ""
- self.enabledForCalendaring = False
- self.enabledForAddressBooks = False
- self.enabledForLogin = False
-
-
- def applySACLs(self):
- """
- Disable calendaring and addressbooks as dictated by SACLs
- """
-
- if config.EnableSACLs and self.CheckSACL:
- username = self.shortNames[0]
- if self.CheckSACL(username, "calendar") != 0:
- self.log.debug("%s is not enabled for calendaring due to SACL"
- % (username,))
- self.enabledForCalendaring = False
- if self.CheckSACL(username, "addressbook") != 0:
- self.log.debug("%s is not enabled for addressbooks due to SACL"
- % (username,))
- self.enabledForAddressBooks = False
-
-
- def displayName(self):
- return self.fullName if self.fullName else self.shortNames[0]
-
-
- def isLoginEnabled(self):
- """
- Returns True if the user should be allowed to log in, based on the
- enabledForLogin attribute, which is currently controlled by the
- DirectoryService implementation.
- """
- return self.enabledForLogin
-
-
- def members(self):
- return ()
-
-
- def expandedMembers(self, members=None, seen=None):
- """
- Return the complete, flattened set of members of a group, including
- all sub-groups.
- """
- 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):
- """
- Return the set of groups (guids) this record is a member of, based on
- the data cached by cacheGroupMembership( )
- """
- return self.service.groupMembershipCache.getGroupsFor(self.cachedGroupsAlias())
-
-
- def cachedGroupsAlias(self):
- """
- 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.
- """
- return self.guid
-
-
- def externalProxies(self):
- """
- Return the set of proxies defined in the directory service, as opposed
- to assignments in the proxy DB itself.
- """
- return set(self.extProxies)
-
-
- def externalReadOnlyProxies(self):
- """
- Return the set of read-only proxies defined in the directory service,
- as opposed to assignments in the proxy DB itself.
- """
- return set(self.extReadOnlyProxies)
-
-
- def memberGUIDs(self):
- """
- Return the set of GUIDs that are members of this group
- """
- return set()
-
-
- def verifyCredentials(self, credentials):
- return False
-
-
- def calendarsEnabled(self):
- return config.EnableCalDAV and self.enabledForCalendaring
-
-
- def canonicalCalendarUserAddress(self):
- """
- Return a CUA for this principal, preferring in this order:
- urn:uuid: form
- mailto: form
- first in calendarUserAddresses list
- """
-
- cua = ""
- 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("urn:uuid:"):
- cua = candidate
- break
- # Prefer mailto: if no urn:uuid:
- elif candidate.startswith("mailto:"):
- 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, "UNKNOWN")
-
-
- @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() != "INDIVIDUAL" or
- config.Scheduling.Options.AutoSchedule.AllowUsers):
- return True
- return False
-
-
- def getAutoScheduleMode(self, organizer):
- autoScheduleMode = self.autoScheduleMode
- if self.autoAcceptFromOrganizer(organizer):
- autoScheduleMode = "automatic"
- 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):
- """
- URL of the server hosting this record. Return None if hosted on this server.
- """
- if config.Servers.Enabled and self.serverID:
- return Servers.getServerURIById(self.serverID)
- else:
- return None
-
-
- def server(self):
- """
- Server hosting this record. Return None if hosted on this server.
- """
- 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):
- """
- 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}
- """
- if not hasattr(self, "_cachedAutoAcceptMembers"):
- 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):
- """
- 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}
- """
- return self.service.isProxyFor(self, other)
-
-
-
-class DirectoryError(RuntimeError):
- """
- Generic directory error.
- """
-
-
-
-class DirectoryConfigurationError(DirectoryError):
- """
- Invalid directory configuration.
- """
-
-
-
-class UnknownRecordTypeError(DirectoryError):
- """
- Unknown directory record type.
- """
- def __init__(self, recordType):
- DirectoryError.__init__(self, "Invalid record type: %s" % (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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-Directory service interfaces.
-"""
-
-__all__ = [
- "IDirectoryService",
- "IDirectoryRecord",
-]
-
-from zope.interface import Attribute, Interface
-
-class IDirectoryService(Interface):
- """
- Directory Service
- """
- realmName = Attribute("The name of the authentication realm this service represents.")
- guid = Attribute("A GUID for this service.")
-
- def recordTypes(): #@NoSelf
- """
- @return: a sequence of strings denoting the record types that
- are kept in the directory. For example: C{["users",
- "groups", "resources"]}.
- """
-
- def listRecords(recordType): #@NoSelf
- """
- @param type: the type of records to retrieve.
- @return: an iterable of records of the given type.
- """
-
- def recordWithShortName(recordType, shortName): #@NoSelf
- """
- @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.
- """
-
- def recordWithUID(uid): #@NoSelf
- """
- @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.
- """
-
- def recordWithGUID(guid): #@NoSelf
- """
- @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.
- """
-
- def recordWithCalendarUserAddress(address): #@NoSelf
- """
- @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.
- """
-
- def recordWithCachedGroupsAlias(recordType, alias): #@NoSelf
- """
- @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.
- """
-
- def recordsMatchingFields(fields): #@NoSelf
- """
- @return: a deferred sequence of L{IDirectoryRecord}s which
- match the given fields.
- """
-
- def recordsMatchingTokens(tokens, context=None): #@NoSelf
- """
- @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;
- "attendee", "location", 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
- "location", only locations are considered. If context is
- "attendee", only users, groups, and resources are considered.
- """
-
- def setRealm(realmName): #@NoSelf
- """
- Set a new realm name for this (and nested services if any)
-
- @param realmName: the realm name this service should use.
- """
-
-
-
-class IDirectoryRecord(Interface):
- """
- Directory Record
- """
- service = Attribute("The L{IDirectoryService} this record exists in.")
- recordType = Attribute("The type of this record.")
- guid = Attribute("The GUID of this record.")
- uid = Attribute("The UID of this record.")
- enabled = Attribute("Determines whether this record should allow a principal to be created.")
- serverID = Attribute("Identifies the server that actually hosts data for the record.")
- shortNames = Attribute("The names for this record.")
- authIDs = Attribute("Alternative security identities for this record.")
- fullName = Attribute("The full name of this record.")
- firstName = Attribute("The first name of this record.")
- lastName = Attribute("The last name of this record.")
- emailAddresses = Attribute("The email addresses of this record.")
- enabledForCalendaring = Attribute("Determines whether this record creates a principal with a calendar home.")
- enabledForAddressBooks = Attribute("Determines whether this record creates a principal with an address book home.")
- calendarUserAddresses = Attribute(
- """
- An iterable of C{str}s representing calendar user addresses for this
- L{IDirectoryRecord}.
-
- A "calendar user address", as defined by U{RFC 2445 section
- 4.3.3<http://xml.resource.org/public/rfc/html/rfc2445.html#anchor50>},
- 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.
- """
- )
-
- def members(): #@NoSelf
- """
- @return: an iterable of L{IDirectoryRecord}s for the members of this
- (group) record.
- """
-
- def groups(): #@NoSelf
- """
- @return: an iterable of L{IDirectoryRecord}s for the groups this
- record is a member of.
- """
-
- def verifyCredentials(credentials): #@NoSelf
- """
- 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.
- """
</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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-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)
-"""
-
-__all__ = [
- "LdapDirectoryService",
-]
-
-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):
- """
- LDAP based implementation of L{IDirectoryService}.
- """
- log = Logger()
-
- baseGUID = "5A871574-0C86-44EE-B11B-B9440C3DC4DD"
-
- def __repr__(self):
- return "<%s %r: %r>" % (
- self.__class__.__name__, self.realmName, self.uri
- )
-
-
- def __init__(self, params):
- """
- @param params: a dictionary containing the following keys:
- cacheTimeout, realmName, uri, tls, tlsCACertFile, tlsCACertDir,
- tlsRequireCert, credentials, rdnSchema, groupSchema, resourceSchema
- poddingSchema
- """
-
- defaults = {
- "augmentService": None,
- "groupMembershipCache": None,
- "cacheTimeout": 1, # Minutes
- "negativeCaching": False,
- "warningThresholdSeconds": 3,
- "batchSize": 500, # for splitting up large queries
- "requestTimeoutSeconds": 10,
- "requestResultsLimit": 200,
- "optimizeMultiName": False,
- "queryLocationsImplicitly": True,
- "restrictEnabledRecords": False,
- "restrictToGroup": "",
- "recordTypes": ("users", "groups"),
- "uri": "ldap://localhost/",
- "tls": False,
- "tlsCACertFile": None,
- "tlsCACertDir": None,
- "tlsRequireCert": None, # never, allow, try, demand, hard
- "credentials": {
- "dn": None,
- "password": None,
- },
- "authMethod": "LDAP",
- "rdnSchema": {
- "base": "dc=example,dc=com",
- "guidAttr": "entryUUID",
- "users": {
- "rdn": "ou=People",
- "filter": None, # additional filter for this type
- "loginEnabledAttr": "", # attribute controlling login
- "loginEnabledValue": "yes", # "True" value of above attribute
- "calendarEnabledAttr": "", # attribute controlling enabledForCalendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": ["mail"], # multiple LDAP fields supported
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "groups": {
- "rdn": "ou=Group",
- "filter": None, # additional filter for this type
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail"], # multiple LDAP fields supported
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "locations": {
- "rdn": "ou=Places",
- "filter": None, # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling enabledForCalendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "associatedAddressAttr": "",
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail"], # multiple LDAP fields supported
- },
- },
- "resources": {
- "rdn": "ou=Resources",
- "filter": None, # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling enabledForCalendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail"], # multiple LDAP fields supported
- },
- },
- "addresses": {
- "rdn": "ou=Buildings",
- "filter": None, # additional filter for this type
- "streetAddressAttr": "",
- "geoAttr": "",
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "member", # how members are specified
- "nestedGroupsAttr": None, # how nested groups are specified
- "memberIdAttr": None, # which attribute the above refer to (None means use DN)
- },
- "resourceSchema": {
- # Either set this attribute to retrieve the plist version
- # of resource-info, as in a Leopard OD server, or...
- "resourceInfoAttr": None,
- # ...set the above to None and instead specify these
- # individually:
- "autoScheduleAttr": None,
- "autoScheduleEnabledValue": "yes",
- "proxyAttr": None, # list of GUIDs
- "readOnlyProxyAttr": None, # list of GUIDs
- "autoAcceptGroupAttr": None, # single group GUID
- },
- "poddingSchema": {
- "serverIdAttr": None, # maps to augments server-id
- },
- }
- ignored = None
- params = self.getParams(params, defaults, ignored)
-
- self._recordTypes = params["recordTypes"]
-
- super(LdapDirectoryService, self).__init__(params["cacheTimeout"],
- params["negativeCaching"])
-
- self.warningThresholdSeconds = params["warningThresholdSeconds"]
- self.batchSize = params["batchSize"]
- self.requestTimeoutSeconds = params["requestTimeoutSeconds"]
- self.requestResultsLimit = params["requestResultsLimit"]
- self.optimizeMultiName = params["optimizeMultiName"]
- if self.batchSize > self.requestResultsLimit:
- self.batchSize = self.requestResultsLimit
- self.queryLocationsImplicitly = params["queryLocationsImplicitly"]
- self.augmentService = params["augmentService"]
- self.groupMembershipCache = params["groupMembershipCache"]
- self.realmName = params["uri"]
- self.uri = params["uri"]
- self.tls = params["tls"]
- self.tlsCACertFile = params["tlsCACertFile"]
- self.tlsCACertDir = params["tlsCACertDir"]
- self.tlsRequireCert = params["tlsRequireCert"]
- self.credentials = params["credentials"]
- self.authMethod = params["authMethod"]
- self.rdnSchema = params["rdnSchema"]
- self.groupSchema = params["groupSchema"]
- self.resourceSchema = params["resourceSchema"]
- self.poddingSchema = params["poddingSchema"]
-
- self.base = ldap.dn.str2dn(self.rdnSchema["base"])
-
- # 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["guidAttr"]:
- attrSet.add(self.rdnSchema["guidAttr"])
- for recordType in self.recordTypes():
- if self.rdnSchema[recordType]["attr"]:
- attrSet.add(self.rdnSchema[recordType]["attr"])
- for n in ("calendarEnabledAttr", "associatedAddressAttr",
- "streetAddressAttr", "geoAttr"):
- if self.rdnSchema[recordType].get(n, False):
- attrSet.add(self.rdnSchema[recordType][n])
- for attrList in self.rdnSchema[recordType]["mapping"].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]["mapping"]["guid"] = self.rdnSchema["guidAttr"]
- # Also put the memberIdAttr attribute into the mappings for each type
- # so recordsMatchingFields can query on memberIdAttr
- self.rdnSchema[recordType]["mapping"]["memberIdAttr"] = self.groupSchema["memberIdAttr"]
- if self.groupSchema["membersAttr"]:
- attrSet.add(self.groupSchema["membersAttr"])
- if self.groupSchema["nestedGroupsAttr"]:
- attrSet.add(self.groupSchema["nestedGroupsAttr"])
- if self.groupSchema["memberIdAttr"]:
- attrSet.add(self.groupSchema["memberIdAttr"])
- if self.rdnSchema["users"]["loginEnabledAttr"]:
- attrSet.add(self.rdnSchema["users"]["loginEnabledAttr"])
- if self.resourceSchema["resourceInfoAttr"]:
- attrSet.add(self.resourceSchema["resourceInfoAttr"])
- if self.resourceSchema["autoScheduleAttr"]:
- attrSet.add(self.resourceSchema["autoScheduleAttr"])
- if self.resourceSchema["autoAcceptGroupAttr"]:
- attrSet.add(self.resourceSchema["autoAcceptGroupAttr"])
- if self.resourceSchema["proxyAttr"]:
- attrSet.add(self.resourceSchema["proxyAttr"])
- if self.resourceSchema["readOnlyProxyAttr"]:
- attrSet.add(self.resourceSchema["readOnlyProxyAttr"])
- if self.poddingSchema["serverIdAttr"]:
- attrSet.add(self.poddingSchema["serverIdAttr"])
- self.attrlist = list(attrSet)
-
- self.typeDNs = {}
- for recordType in self.recordTypes():
- self.typeDNs[recordType] = ldap.dn.str2dn(
- self.rdnSchema[recordType]["rdn"].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 = "(!(objectClass=organizationalUnit))"
- typeFilter = self.rdnSchema[recordType].get("filter", "")
- if typeFilter:
- filterstr = "(&%s%s)" % (filterstr, typeFilter)
-
- # Query the LDAP server
- self.log.debug(
- "Querying ldap for records matching base {base} and "
- "filter {filter} for attributes {attrs}.",
- base=ldap.dn.dn2str(base), filter=filterstr,
- attrs=self.attrlist
- )
-
- # This takes a while, so if you don't want to have a "long request"
- # 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["guidAttr"]
- for dn, attrs in results:
- dn = normalizeDNstr(dn)
-
- unrestricted = self.isAllowedByRestrictToGroup(dn, attrs)
-
- try:
- record = self._ldapResultToRecord(dn, attrs, recordType)
- # self.log.debug("Got LDAP record {record}", record=record)
- except MissingGuidException:
- numMissingGuids += 1
- continue
-
- if not unrestricted:
- self.log.debug(
- "{dn} is not enabled because it's not a member of group: "
- "{group}", dn=dn, group=self.restrictToGroup
- )
- record.enabledForCalendaring = False
- record.enabledForAddressBooks = False
-
- records.append(record)
-
- if numMissingGuids:
- self.log.info(
- "{num} {recordType} records are missing {attr}",
- num=numMissingGuids, recordType=recordType, attr=guidAttr
- )
-
- return records
-
-
- @inlineCallbacks
- def recordWithCachedGroupsAlias(self, recordType, alias):
- """
- @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.
- """
- memberIdAttr = self.groupSchema["memberIdAttr"]
- attributeToSearch = "memberIdAttr" if memberIdAttr else "dn"
-
- fields = [[attributeToSearch, alias, False, "equals"]]
- results = yield self.recordsMatchingFields(
- fields, recordType=recordType
- )
- if results:
- returnValue(results[0])
- else:
- returnValue(None)
-
-
- def getExternalProxyAssignments(self):
- """
- Retrieve proxy assignments for locations and resources from the
- directory and return a list of (principalUID, ([memberUIDs)) tuples,
- suitable for passing to proxyDB.setGroupMembers( )
- """
- assignments = []
-
- guidAttr = self.rdnSchema["guidAttr"]
- readAttr = self.resourceSchema["readOnlyProxyAttr"]
- writeAttr = self.resourceSchema["proxyAttr"]
- if not (guidAttr and readAttr and writeAttr):
- self.log.error(
- "LDAP configuration requires guidAttr, proxyAttr, and "
- "readOnlyProxyAttr in order to use external proxy assignments "
- "efficiently; falling back to slower method"
- )
- # Fall back to the less-specialized version
- return super(
- LdapDirectoryService, self
- ).getExternalProxyAssignments()
-
- # Build filter
- filterstr = "(|(%s=*)(%s=*))" % (readAttr, writeAttr)
- # ...taking into account only calendar-enabled records
- enabledAttr = self.rdnSchema["locations"]["calendarEnabledAttr"]
- enabledValue = self.rdnSchema["locations"]["calendarEnabledValue"]
- if enabledAttr and enabledValue:
- filterstr = "(&(%s=%s)%s)" % (enabledAttr, enabledValue, filterstr)
-
- attrlist = [guidAttr, readAttr, writeAttr]
-
- # Query the LDAP server
- self.log.debug(
- "Querying ldap for records matching base {base} and filter "
- "{filter} for attributes {attrs}.",
- 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(
- ("%s#calendar-proxy-read" % (guid,), [readDelegate])
- )
- writeDelegate = self._getUniqueLdapAttribute(attrs, writeAttr)
- if writeDelegate:
- writeDelegate = normalizeUUID(writeDelegate)
- assignments.append(
- ("%s#calendar-proxy-write" % (guid,), [writeDelegate])
- )
-
- return assignments
-
-
- def getLDAPConnection(self):
- if self.ldap is None:
- self.log.info("Connecting to LDAP {uri}", uri=repr(self.uri))
- self.ldap = self.createLDAPConnection()
- self.log.info(
- "Connection established to LDAP {uri}", uri=repr(self.uri)
- )
- if self.credentials.get("dn", ""):
- try:
- self.log.info(
- "Binding to LDAP {dn}",
- dn=repr(self.credentials.get("dn"))
- )
- self.ldap.simple_bind_s(
- self.credentials.get("dn"),
- self.credentials.get("password"),
- )
- self.log.info(
- "Successfully authenticated with LDAP as {dn}",
- dn=repr(self.credentials.get("dn"))
- )
- except ldap.INVALID_CREDENTIALS:
- self.log.error(
- "Can't bind to LDAP {uri}: check credentials",
- uri=self.uri
- )
- raise DirectoryConfigurationError()
-
- return self.ldap
-
-
- def createLDAPConnection(self):
- """
- Create and configure LDAP connection
- """
- 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 == "never":
- cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_NEVER)
- elif self.tlsRequireCert == "allow":
- cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_ALLOW)
- elif self.tlsRequireCert == "try":
- cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_TRY)
- elif self.tlsRequireCert == "demand":
- cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
- elif self.tlsRequireCert == "hard":
- 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):
- """
- Perform simple bind auth, raising ldap.INVALID_CREDENTIALS if
- bad password
- """
- TRIES = 3
-
- for _ignore_i in xrange(TRIES):
- self.log.debug("Authenticating {dn}", dn=dn)
-
- if self.authLDAP is None:
- self.log.debug("Creating authentication connection to LDAP")
- 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(
- "LDAP Authentication error for {dn}: NO_SUCH_OBJECT",
- dn=dn
- )
- # fall through to try again; could be transient
-
- except ldap.INVALID_CREDENTIALS:
- raise
-
- except ldap.SERVER_DOWN:
- self.log.error("Lost connection to LDAP server.")
- self.authLDAP = None
- # Fall through and retry if TRIES has been reached
-
- except Exception, e:
- self.log.error(
- "LDAP authentication failed with {e}.", e=e
- )
- raise
-
- finally:
- totalTime = time.time() - startTime
- if totalTime > self.warningThresholdSeconds:
- self.log.error(
- "LDAP auth exceeded threshold: {time:.2f} seconds for "
- "{dn}", time=totalTime, dn=dn
- )
-
- else:
- self.log.error(
- "Giving up on LDAP authentication after {count:d} tries. "
- "Responding with 503.", count=TRIES
- )
- raise HTTPError(StatusResponse(
- responsecode.SERVICE_UNAVAILABLE, "LDAP server unavailable"
- ))
-
- self.log.debug("Authentication succeeded for {dn}", dn=dn)
-
-
- def timedSearch(
- self, base, scope, filterstr="(objectClass=*)", attrlist=None,
- timeoutSeconds=-1, resultLimit=0
- ):
- """
- 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.
- """
- 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(
- "LDAP filter error: {e} {filter}", e=e, filter=filterstr
- )
- return []
- except ldap.SIZELIMIT_EXCEEDED, e:
- self.log.debug(
- "LDAP result limit exceeded: {limit:d}", limit=resultLimit
- )
- except ldap.TIMELIMIT_EXCEEDED, e:
- self.log.warn(
- "LDAP timeout exceeded: {t:d} seconds", t=timeoutSeconds
- )
- except ldap.SERVER_DOWN:
- self.ldap = None
- self.log.error(
- "LDAP server unavailable (tried {count:d} times)",
- count=(i + 1)
- )
- continue
-
- # change format, ignoring resultsType
- result = [
- resultItem for _ignore_resultType, resultItem in s.allResults
- ]
-
- totalTime = time.time() - startTime
- if totalTime > self.warningThresholdSeconds:
- if filterstr and len(filterstr) > 100:
- filterstr = "%s..." % (filterstr[:100],)
- self.log.error(
- "LDAP query exceeded threshold: {time:.2f} seconds for "
- "{base} {filter} {attrs} (#results={count:d})",
- time=totalTime, base=base, filter=filterstr,
- attrs=attrlist, count=len(result),
- )
- return result
-
- raise HTTPError(StatusResponse(
- responsecode.SERVICE_UNAVAILABLE, "LDAP server unavailable"
- ))
-
-
- def isAllowedByRestrictToGroup(self, dn, attrs):
- """
- 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}
- """
- if not self.restrictEnabledRecords:
- return True
- if self.groupSchema["memberIdAttr"]:
- value = self._getUniqueLdapAttribute(
- attrs, self.groupSchema["memberIdAttr"]
- )
- else: # No memberIdAttr implies DN
- value = dn
- return value in self.restrictedPrincipals
-
-
- @property
- def restrictedPrincipals(self):
- """
- 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.
- """
- if self.restrictEnabledRecords:
-
- if time.time() - self.restrictedTimestamp > 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 = "(cn=%s)" % (self.restrictToGroup,)
- self.log.debug(
- "Retrieving ldap record with base {base} and filter "
- "{filter}.",
- 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["membersAttr"]:
- members = self._getMultipleLdapAttributes(
- attrs,
- self.groupSchema["membersAttr"]
- )
- if not self.groupSchema["memberIdAttr"]: # DNs
- members = [normalizeDNstr(m) for m in members]
- members = set(members)
-
- if self.groupSchema["nestedGroupsAttr"]:
- nestedGroups = self._getMultipleLdapAttributes(
- attrs,
- self.groupSchema["nestedGroupsAttr"]
- )
- if not self.groupSchema["memberIdAttr"]: # 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(
- "Got {count} restricted group members",
- count=len(self._cachedRestrictedPrincipals)
- )
- self.restrictedTimestamp = time.time()
- return self._cachedRestrictedPrincipals
- else:
- # No restrictions
- return None
-
-
- def _expandGroupMembership(self, members, nestedGroups, processedItems=None):
- """
- 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}
- """
-
- 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["memberIdAttr"]:
- scope = ldap.SCOPE_SUBTREE
- base = self.typeDNs[recordType]
- filterstr = "(%s=%s)" % (self.groupSchema["memberIdAttr"], group)
- else: # Use DN
- scope = ldap.SCOPE_BASE
- base = ldap.dn.str2dn(group)
- filterstr = "(objectClass=*)"
-
- self.log.debug(
- "Retrieving ldap record with base {base} and filter {filter}.",
- 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["membersAttr"]:
- subMembers = self._getMultipleLdapAttributes(
- attrs,
- self.groupSchema["membersAttr"]
- )
- if not self.groupSchema["memberIdAttr"]: # these are DNs
- subMembers = [normalizeDNstr(m) for m in subMembers]
- subMembers = set(subMembers)
-
- if self.groupSchema["nestedGroupsAttr"]:
- subNestedGroups = self._getMultipleLdapAttributes(
- attrs,
- self.groupSchema["nestedGroupsAttr"]
- )
- if not self.groupSchema["memberIdAttr"]: # 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):
- """
- Get the first value for one or several attributes
- Useful when attributes have aliases (e.g. sn vs. surname)
- """
- for key in keys:
- values = attrs.get(key)
- if values is not None:
- return values[0]
- return None
-
-
- def _getMultipleLdapAttributes(self, attrs, *keys):
- """
- Get all values for one or several attributes
- """
- 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):
- """
- 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
- """
-
- guid = None
- authIDs = set()
- fullName = None
- firstName = ""
- lastName = ""
- emailAddresses = set()
- enabledForCalendaring = None
- enabledForAddressBooks = None
- uid = None
- enabledForLogin = True
- extras = {}
-
- shortNames = tuple(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"]["recordName"]))
- if not shortNames:
- raise MissingRecordNameException()
-
- # First check for and add guid
- guidAttr = self.rdnSchema["guidAttr"]
- if guidAttr:
- guid = self._getUniqueLdapAttribute(attrs, guidAttr)
- if not guid:
- self.log.debug(
- "LDAP data for {shortNames} is missing guid attribute "
- "{attr}",
- 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]["mapping"].get("emailAddresses", "")
- # Supporting either string or list for emailAddresses:
- if isinstance(emailAddressesMappedTo, str):
- emailAddresses = set(self._getMultipleLdapAttributes(attrs, self.rdnSchema[recordType]["mapping"].get("emailAddresses", "")))
- else:
- emailAddresses = set(self._getMultipleLdapAttributes(attrs, *self.rdnSchema[recordType]["mapping"]["emailAddresses"]))
- emailSuffix = self.rdnSchema[recordType].get("emailSuffix", None)
-
- if len(emailAddresses) == 0 and emailSuffix:
- emailPrefix = self._getUniqueLdapAttribute(
- attrs,
- self.rdnSchema[recordType].get("attr", "cn")
- )
- emailAddresses.add(emailPrefix + emailSuffix)
-
- proxyGUIDs = ()
- readOnlyProxyGUIDs = ()
- autoSchedule = False
- autoAcceptGroup = ""
- memberGUIDs = []
-
- # LDAP attribute -> principal matchings
- if recordType == self.recordType_users:
- fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType]["mapping"]["fullName"])
- firstName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType]["mapping"]["firstName"])
- lastName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType]["mapping"]["lastName"])
- enabledForCalendaring = True
- enabledForAddressBooks = True
-
- elif recordType == self.recordType_groups:
- fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType]["mapping"]["fullName"])
- enabledForCalendaring = False
- enabledForAddressBooks = False
- enabledForLogin = False
-
- if self.groupSchema["membersAttr"]:
- members = self._getMultipleLdapAttributes(attrs, self.groupSchema["membersAttr"])
- memberGUIDs.extend(members)
- if self.groupSchema["nestedGroupsAttr"]:
- members = self._getMultipleLdapAttributes(attrs, self.groupSchema["nestedGroupsAttr"])
- memberGUIDs.extend(members)
-
- # Normalize members if they're in DN form
- if not self.groupSchema["memberIdAttr"]: # 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("Bad LDAP DN: {dn!r}", dn=dnStr)
-
- elif recordType in (self.recordType_resources,
- self.recordType_locations):
- fullName = self._getUniqueLdapAttribute(attrs, self.rdnSchema[recordType]["mapping"]["fullName"])
- enabledForCalendaring = True
- enabledForAddressBooks = False
- enabledForLogin = False
- if self.resourceSchema["resourceInfoAttr"]:
- resourceInfo = self._getUniqueLdapAttribute(
- attrs,
- self.resourceSchema["resourceInfoAttr"]
- )
- 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(
- "Unable to parse resource info: {e}", e=e
- )
- else: # the individual resource attributes might be specified
- if self.resourceSchema["autoScheduleAttr"]:
- autoScheduleValue = self._getUniqueLdapAttribute(
- attrs,
- self.resourceSchema["autoScheduleAttr"]
- )
- autoSchedule = (
- autoScheduleValue == self.resourceSchema["autoScheduleEnabledValue"]
- )
- if self.resourceSchema["proxyAttr"]:
- proxyGUIDs = set(
- self._getMultipleLdapAttributes(
- attrs,
- self.resourceSchema["proxyAttr"]
- )
- )
- if self.resourceSchema["readOnlyProxyAttr"]:
- readOnlyProxyGUIDs = set(
- self._getMultipleLdapAttributes(
- attrs,
- self.resourceSchema["readOnlyProxyAttr"]
- )
- )
- if self.resourceSchema["autoAcceptGroupAttr"]:
- autoAcceptGroup = self._getUniqueLdapAttribute(
- attrs,
- self.resourceSchema["autoAcceptGroupAttr"]
- )
-
- if recordType == self.recordType_locations:
- if self.rdnSchema[recordType].get("associatedAddressAttr", ""):
- associatedAddress = self._getUniqueLdapAttribute(
- attrs,
- self.rdnSchema[recordType]["associatedAddressAttr"]
- )
- if associatedAddress:
- extras["associatedAddress"] = associatedAddress
-
- elif recordType == self.recordType_addresses:
- if self.rdnSchema[recordType].get("geoAttr", ""):
- geo = self._getUniqueLdapAttribute(
- attrs,
- self.rdnSchema[recordType]["geoAttr"]
- )
- if geo:
- extras["geo"] = geo
- if self.rdnSchema[recordType].get("streetAddressAttr", ""):
- street = self._getUniqueLdapAttribute(
- attrs,
- self.rdnSchema[recordType]["streetAddressAttr"]
- )
- if street:
- extras["streetAddress"] = street
-
- serverID = None
- if self.poddingSchema["serverIdAttr"]:
- serverID = self._getUniqueLdapAttribute(
- attrs,
- self.poddingSchema["serverIdAttr"]
- )
-
- 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]["loginEnabledAttr"]
- if loginEnabledAttr:
- loginEnabledValue = self.rdnSchema[recordType]["loginEnabledValue"]
- record.enabledForLogin = self._getUniqueLdapAttribute(
- attrs, loginEnabledAttr
- ) == loginEnabledValue
-
- # Override with LDAP calendar-enabled control if attribute specified
- calendarEnabledAttr = self.rdnSchema[recordType].get("calendarEnabledAttr", "")
- if calendarEnabledAttr:
- calendarEnabledValue = self.rdnSchema[recordType]["calendarEnabledValue"]
- record.enabledForCalendaring = self._getUniqueLdapAttribute(
- attrs,
- calendarEnabledAttr
- ) == calendarEnabledValue
-
- return record
-
-
- def queryDirectory(
- self, recordTypes, indexType, indexKey, queryMethod=None
- ):
- """
- 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.
- """
-
- if queryMethod is None:
- queryMethod = self.timedSearch
-
- self.log.debug(
- "LDAP query for types {types}, indexType {indexType} and "
- "indexKey {indexKey}",
- types=recordTypes, indexType=indexType, indexKey=indexKey
- )
-
- guidAttr = self.rdnSchema["guidAttr"]
- for recordType in recordTypes:
- # Build base for this record Type
- base = self.typeDNs[recordType]
-
- # Build filter
- filterstr = "(!(objectClass=organizationalUnit))"
- typeFilter = self.rdnSchema[recordType].get("filter", "")
- if typeFilter:
- filterstr = "(&%s%s)" % (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 = "(&%s(%s=%s))" % (filterstr, guidAttr, indexKey)
-
- elif indexType == self.INDEX_TYPE_SHORTNAME:
- filterstr = "(&%s(%s=%s))" % (
- filterstr,
- self.rdnSchema[recordType]["mapping"]["recordName"],
- ldapEsc(indexKey)
- )
-
- elif indexType == self.INDEX_TYPE_CUA:
- # indexKey is of the form "mailto:test@example.net"
- email = indexKey[7:] # strip "mailto:"
- emailSuffix = self.rdnSchema[recordType].get(
- "emailSuffix", None
- )
- if (
- emailSuffix is not None and
- email.partition("@")[2] == emailSuffix
- ):
- filterstr = "(&%s(|(&(!(mail=*))(%s=%s))(mail=%s)))" % (
- filterstr,
- self.rdnSchema[recordType].get("attr", "cn"),
- email.partition("@")[0],
- ldapEsc(email)
- )
- else:
- # emailAddresses can map to multiple LDAP fields
- ldapFields = self.rdnSchema[recordType]["mapping"].get(
- "emailAddresses", ""
- )
- if isinstance(ldapFields, str):
- if ldapFields:
- subfilter = (
- "(%s=%s)" % (ldapFields, ldapEsc(email))
- )
- else:
- # No LDAP attribute assigned for emailAddresses
- continue
-
- else:
- subfilter = []
- for ldapField in ldapFields:
- if ldapField:
- subfilter.append(
- "(%s=%s)" % (ldapField, ldapEsc(email))
- )
- if not subfilter:
- # No LDAP attribute assigned for emailAddresses
- continue
-
- subfilter = "(|%s)" % ("".join(subfilter))
- filterstr = "(&%s%s)" % (filterstr, subfilter)
-
- elif indexType == self.INDEX_TYPE_AUTHID:
- return
-
- # Query the LDAP server
- self.log.debug(
- "Retrieving ldap record with base %s and filter %s.",
- 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("Got LDAP record {rec}", rec=record)
-
- if not unrestricted:
- self.log.debug(
- "{dn} is not enabled because it's not a member of "
- "group {group!r}",
- 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(
- "Ignoring record missing record name "
- "attribute: recordType {recordType}, indexType "
- "{indexType} and indexKey {indexKey}",
- recordTypes=recordTypes, indexType=indexType,
- indexKey=indexKey,
- )
-
- except MissingGuidException:
- self.log.warn(
- "Ignoring record missing guid attribute: "
- "recordType {recordType}, indexType {indexType} and "
- "indexKey {indexKey}",
- recordTypes=recordTypes, indexType=indexType,
- indexKey=indexKey
- )
-
-
- def recordsMatchingTokens(self, tokens, context=None, limitResults=50, timeoutSeconds=10):
- """
- # 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; "attendee", "location", 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 "location", only locations are considered. If
- context is "attendee", only users, groups, and resources
- are considered.
- """
- self.log.debug(
- "Peforming calendar user search for {tokens} ({context})",
- 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("LDAP search aggregate limit reached")
- break
- typeCounts[recordType] = 0
- base = self.typeDNs[recordType]
- scope = ldap.SCOPE_SUBTREE
- extraFilter = self.rdnSchema[recordType].get("filter", "")
- filterstr = buildFilterFromTokens(
- recordType,
- self.rdnSchema[recordType]["mapping"],
- tokens,
- extra=extraFilter
- )
-
- if filterstr is not None:
- # Query the LDAP server
- self.log.debug(
- "LDAP search {base} {filter} (limit={limit:d})",
- 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(
- "LDAP search returned {resultCount:d} results, "
- "{typeCount:d} usable",
- resultCount=len(results), typeCount=typeCounts[recordType]
- )
-
- typeCountsStr = ", ".join(
- ["%s:%d" % (rt, ct) for (rt, ct) in typeCounts.iteritems()]
- )
- totalTime = time.time() - startTime
- self.log.info(
- "Calendar user search for {tokens} matched {recordCount:d} "
- "records ({typeCount}) in {time!.2f} seconds",
- tokens=tokens, recordCount=len(records),
- typeCount=typeCountsStr, time=totalTime,
- )
- return succeed(records)
-
-
- @inlineCallbacks
- def recordsMatchingFields(self, fields, operand="or", recordType=None):
- """
- Carries out the work of a principal-property-search against LDAP
- Returns a deferred list of directory records.
- """
- records = []
-
- self.log.debug(
- "Performing principal property search for {fields}", 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["guidAttr"]
- for recordType in recordTypes:
-
- base = self.typeDNs[recordType]
-
- if fields[0][0] == "dn":
- # DN's are not an attribute that can be searched on by filter
- scope = ldap.SCOPE_BASE
- filterstr = "(objectClass=*)"
- base = ldap.dn.str2dn(fields[0][1])
-
- else:
- scope = ldap.SCOPE_SUBTREE
- filterstr = buildFilter(
- recordType,
- self.rdnSchema[recordType]["mapping"],
- fields,
- operand=operand,
- optimizeMultiName=self.optimizeMultiName
- )
-
- if filterstr is not None:
- # Query the LDAP server
- self.log.debug(
- "LDAP search {base} {scope} {filter}",
- 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(
- "LDAP search returned {count} results", 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(
- "{count:d} {type} records are missing {attr}",
- count=numMissingGuids, type=recordType, attr=guidAttr
- )
-
- if numMissingRecordNames:
- self.log.warn(
- "{count:d} {type} records are missing record name",
- count=numMissingRecordNames, type=recordType,
- )
-
- self.log.debug(
- "Principal property search matched {count} records",
- count=len(records)
- )
- returnValue(records)
-
-
- @inlineCallbacks
- def getGroups(self, guids):
- """
- 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.
- """
-
- recordsByAlias = {}
-
- groupsDN = self.typeDNs[self.recordType_groups]
- memberIdAttr = self.groupSchema["memberIdAttr"]
-
- # 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 = "guid"
- valuesToFetch = guids
-
- while valuesToFetch:
- results = []
-
- if attributeToSearch == "dn":
- # Since DN can't be searched on in a filter we have to call
- # recordsMatchingFields for *each* DN.
- for value in valuesToFetch:
- fields = [["dn", value, False, "equals"]]
- 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, "equals"])
- 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["memberIdAttr"]
- 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 = "memberIdAttr" if memberIdAttr else "dn"
-
- returnValue(recordsByAlias.values())
-
-
- def recordTypeForDN(self, dnStr):
- """
- 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
- """
- 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):
- """
- Return True if child dn is contained within parent dn, otherwise False.
- """
- return child[-len(parent):] == parent
-
-
-
-def normalizeDNstr(dnStr):
- """
- Convert to lowercase and remove extra whitespace
- @param dnStr: dn
- @type dnStr: C{str}
- @return: normalized dn C{str}
- """
- return ' '.join(ldap.dn.dn2str(ldap.dn.str2dn(dnStr.lower())).split())
-
-
-
-def _convertValue(value, matchType):
- if matchType == "starts-with":
- value = "%s*" % (ldapEsc(value),)
- elif matchType == "contains":
- value = "*%s*" % (ldapEsc(value),)
- # otherwise it's an exact match
- else:
- value = ldapEsc(value)
- return value
-
-
-
-def buildFilter(recordType, mapping, fields, operand="or", optimizeMultiName=False):
- """
- 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 "starts-with", "contains", "exact"
- """
-
- 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("(%s=%s)" % (ldapField, value))
- else:
- subConverted = []
- for lf in ldapField:
- subConverted.append("(%s=%s)" % (lf, value))
- converted.append("(|%s)" % "".join(subConverted))
-
- if len(converted) == 0:
- return None
-
- if optimizeMultiName and recordType in ("users", "groups"):
- for field in [key for key in combined.keys() if key != "guid"]:
- if len(combined.get(field, [])) > 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 == "users":
- converted = []
- for firstName, _ignore_firstCaseless, firstMatchType in combined["firstName"]:
- for lastName, _ignore_lastCaseless, lastMatchType in combined["lastName"]:
- if firstName != lastName:
- firstValue = _convertValue(firstName, firstMatchType)
- lastValue = _convertValue(lastName, lastMatchType)
- converted.append(
- "(&(%s=%s)(%s=%s))" %
- (mapping["firstName"], firstValue,
- mapping["lastName"], lastValue)
- )
- else:
- return None
-
- if len(converted) == 1:
- filterstr = converted[0]
- else:
- operand = ("|" if operand == "or" else "&")
- filterstr = "(%s%s)" % (operand, "".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 ("recordName", "guid"):
- if key in mapping:
- additional.append("(%s=*)" % (mapping.get(key),))
- if additional:
- filterstr = "(&%s%s)" % ("".join(additional), filterstr)
-
- return filterstr
-
-
-
-def buildFilterFromTokens(recordType, mapping, tokens, extra=None):
- """
- Create an LDAP filter string from a list of query tokens. Each token is
- searched for in each LDAP attribute corresponding to "fullName" and
- "emailAddresses" (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 "and" into the final filter
- @type extra: C{str} or None
- @return: An LDAP filterstr
- @rtype: C{str}
- """
-
- 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 = [
- ("fullName", "(%s=*%s*)"),
- ("emailAddresses", "(%s=%s*)"),
- ]
-
- 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 = "(|%s)" % ("".join(fragments),)
- tokenFragments.append(tokenFragment)
-
- if len(tokenFragments) == 1:
- filterStr = tokenFragments[0]
- else:
- filterStr = "(&%s)" % ("".join(tokenFragments),)
-
- return filterStr
-
-
-
-class LdapDirectoryRecord(CachingDirectoryRecord):
- """
- LDAP implementation of L{IDirectoryRecord}.
- """
- 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["memberIdAttr"]
- if memberIdAttr:
- self._memberId = self.service._getUniqueLdapAttribute(
- attrs,
- memberIdAttr
- )
- else:
- self._memberId = normalizeDNstr(self.dn)
-
-
- def members(self):
- """ Return the records representing members of this group """
-
- try:
- return self._members_storage
- except AttributeError:
- self._members_storage = self._members()
- return self._members_storage
-
-
- def _members(self):
- """ Fault in records for the members of this group """
-
- memberIdAttr = self.service.groupSchema["memberIdAttr"]
- results = []
-
- for memberId in self._memberGUIDs:
-
- if memberIdAttr:
-
- base = self.service.base
- filterstr = "(%s=%s)" % (memberIdAttr, ldapEsc(memberId))
- self.log.debug(
- "Retrieving subtree of {base} with filter {filter}",
- base=ldap.dn.dn2str(base), filter=filterstr,
- system="LdapDirectoryService"
- )
- result = self.service.timedSearch(
- ldap.dn.dn2str(base),
- ldap.SCOPE_SUBTREE,
- filterstr=filterstr,
- attrlist=self.service.attrlist
- )
-
- else: # using DN
-
- self.log.debug(
- "Retrieving {id}.",
- id=memberId, system="LdapDirectoryService"
- )
- result = self.service.timedSearch(
- memberId,
- ldap.SCOPE_BASE, attrlist=self.service.attrlist
- )
-
- if result:
-
- dn, attrs = result.pop()
- dn = normalizeDNstr(dn)
- self.log.debug("Retrieved: {dn} {attrs}", dn=dn, attrs=attrs)
- recordType = self.service.recordTypeForDN(dn)
- if recordType is None:
- self.log.error(
- "Unable to map {dn} to a record type", dn=dn
- )
- continue
-
- shortName = self.service._getUniqueLdapAttribute(
- attrs,
- self.service.rdnSchema[recordType]["mapping"]["recordName"]
- )
-
- if shortName:
- record = self.service.recordWithShortName(
- recordType,
- shortName
- )
- if record:
- results.append(record)
-
- return results
-
-
- def groups(self):
- """ Return the records representing groups this record is a member of """
- try:
- return self._groups_storage
- except AttributeError:
- self._groups_storage = self._groups()
- return self._groups_storage
-
-
- def _groups(self):
- """ Fault in the groups of which this record is a member """
-
- recordType = self.service.recordType_groups
- base = self.service.typeDNs[recordType]
-
- membersAttrs = []
- if self.service.groupSchema["membersAttr"]:
- membersAttrs.append(self.service.groupSchema["membersAttr"])
- if self.service.groupSchema["nestedGroupsAttr"]:
- membersAttrs.append(self.service.groupSchema["nestedGroupsAttr"])
-
- if len(membersAttrs) == 1:
- filterstr = "(%s=%s)" % (membersAttrs[0], self._memberId)
- else:
- filterstr = "(|%s)" % (
- "".join(
- ["(%s=%s)" % (a, self._memberId) for a in membersAttrs]
- ),
- )
- self.log.debug("Finding groups containing {id}", 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, "cn")
- self.log.debug(
- "{id} is a member of {shortName}",
- 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("{e}", e=e)
-
- return groups
-
-
- def cachedGroupsAlias(self):
- """
- 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.
- """
- return self._memberId
-
-
- def memberGUIDs(self):
- return set(self._memberGUIDs)
-
-
- def verifyCredentials(self, credentials):
- """ Supports PAM or simple LDAP bind for username+password """
-
- 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() == "PAM":
- # Authenticate against PAM (UNTESTED)
-
- if not pamAvailable:
- self.log.error("PAM module is not installed")
- raise DirectoryConfigurationError()
-
- def pam_conv(auth, query_list, userData):
- return [(credentials.password, 0)]
-
- auth = PAM.pam()
- auth.start("caldav")
- 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() == "LDAP":
-
- # 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(
- "Invalid credentials for {dn}",
- dn=repr(self.dn), system="LdapDirectoryService"
- )
- return False
-
- else:
- self.log.error(
- "Unknown Authentication Method {method!r}",
- method=self.service.authMethod.upper()
- )
- raise DirectoryConfigurationError()
-
- return super(LdapDirectoryRecord, self).verifyCredentials(credentials)
-
-
-
-class MissingRecordNameException(Exception):
- """ Raised when LDAP record is missing recordName """
- pass
-
-
-
-class MissingGuidException(Exception):
- """ Raised when LDAP record is missing guidAttr and it's required """
- 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 "License");
-# 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 "AS IS" 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.
-##
-
-
-"""
-Apple Open Directory directory service implementation for backing up directory-backed address books
-"""
-
-__all__ = [
- "OpenDirectoryBackingService", "VCardRecord",
-]
-
-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):
- """
- Open Directory implementation of L{IDirectoryService}.
- """
-
- baseGUID = "BF07A1A2-5BB5-4A4D-A59A-67260EA7E143"
-
- def __repr__(self):
- return "<%s %r>" % (self.__class__.__name__, self.realmName,)
-
-
- def __init__(self, params):
- self._actuallyConfigure(**params)
-
-
- def _actuallyConfigure(
- self, queryPeopleRecords=True,
- peopleNode="/Search/Contacts",
- queryUserRecords=True,
- userNode="/Search",
- 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 "X-" attributes
- standardizeSyntheticUIDs=False, # use simple synthetic UIDs --- good for testing
- appleInternalServer=False,
-
- additionalAttributes=[],
- allowedAttributes=[],
- directoryBackedAddressBook=None
- ):
- """
- @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
-
- """
- 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("Open Directory (node=%s) Initialization error: %s" % (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("Open Directory (node=%s) Initialization error: %s" % (userNode, e))
- raise
- if self.realmName:
- self.realmName += "+" + 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 > 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("X-INTERNAL-REQUIRED")
- )))
- if (self.allowedDSQueryAttributes != VCardRecord.allDSQueryAttributes):
- self.log.info("Allowed DS query attributes = %r" % (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 = "/LDAPv3/" + hostname
- else:
- self.defaultNodeName = None
-
- #cleanup
- self._cleanupTime = time.time()
-
- # file system locks
- self._initLockPath = join(config.DocumentRoot, ".directory_address_book_create_lock")
- self._createdLockPath = join(config.DocumentRoot, ".directory_address_book_created_lock")
- self._updateLockPath = join(config.DocumentRoot, ".directory_address_book_update_lock")
- self._tmpDirAddressBookLockPath = join(config.DocumentRoot, ".directory_address_book_tmpFolder_lock")
-
- self._updateLock = MemcacheLock("OpenDirectoryBacker", self._updateLockPath)
- self._tmpDirAddressBookLock = MemcacheLock("OpenDirectoryBacker", 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 ("directory", "node"):
- 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 ("node",):
- h = (h + hash(getattr(self, attr))) & sys.maxint
- return h
-
-
- @inlineCallbacks
- def available(self):
- if not self._triedCreateLock:
- returnValue(False)
- elif not self._created:
- createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
- self.log.debug("blocking on lock of: \"%s\")" % self._createdLockPath)
- self._created = (yield createdLock.locked())
-
- returnValue(self._created)
-
-
- def updateLock(self):
- return self._updateLock
-
-
- @inlineCallbacks
- def createCache(self):
- """
- If caching, create the cache for the first time.
- """
-
- if not self.liveQuery:
- self.log.info("loading directory address book")
-
- # get init lock
- initLock = MemcacheLock("OpenDirectoryBacker", self._initLockPath, timeout=0)
- self.log.debug("Attempt lock of: \"%s\")" % self._initLockPath)
- gotCreateLock = False
- try:
- yield initLock.acquire()
- gotCreateLock = True
- except MemcacheLockTimeoutError:
- pass
-
- self._triedCreateLock = True
-
- if gotCreateLock:
- self.log.debug("Got lock!")
- yield self._refreshCache(flushCache=False, creating=True)
- else:
- self.log.debug("Could not get lock - directory address book will be filled by peer")
-
-
- @inlineCallbacks
- def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0):
- """
- refresh the cache.
- """
-
- #print("_refreshCache:, flushCache=%s, creating=%s, reschedule=%s, query = %s" % (flushCache, creating, reschedule, "None" 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("Refresh directory address book in %d minutes %d seconds" % 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) < 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("Remove temporary directory address books in %d minutes %d seconds" % 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("/Volumes/"):
- tmpDir = absDocPath
- prefix = ".directoryAddressBook-"
- else:
- tmpDir = gettempdir()
- prefix = "directoryAddressBook-"
-
- return (tmpDir, prefix, ".tmp")
-
- 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("Checking for temporary directory address books")
- tmpDir, prefix, suffix = getTmpDirAndTmpFilePrefixSuffix()
-
- tmpDirLock = self._tmpDirAddressBookLock
- self.log.debug("blocking on lock of: \"%s\")" % 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("Deleting temporary directory address book at: %s" % path)
- FilePath(path).remove()
- self.log.debug("Done deleting")
- except:
- self.log.info("Deletion failed")
- finally:
- self.log.debug("unlocking: \"%s\")" % 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 + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-
- # get the old hash
- oldAddressBookCTag = ""
- updateLock = self.updateLock()
- self.log.debug("blocking on lock of: \"%s\")" % self._updateLockPath)
- yield updateLock.acquire()
-
- if not flushCache:
- # get update lock
- try:
- oldAddressBookCTag = self.directoryBackedAddressBook.readDeadProperty((calendarserver_namespace, "getctag"))
- except:
- oldAddressBookCTag = ""
-
- self.log.debug("Comparing {http://calendarserver.org/ns/}getctag: new = %s, old = %s" % (newAddressBookCTag, oldAddressBookCTag))
- if str(newAddressBookCTag) != str(oldAddressBookCTag):
-
- self.log.debug("unlocking: \"%s\")" % self._updateLockPath)
- yield updateLock.release()
- updateLock = None
-
- if not keepLock:
- self.log.debug("unlocking: \"%s\")" % self._updateLockPath)
- yield updateLock.release()
- updateLock = None
-
- except:
- cleanupLater()
- if reschedule:
- refreshLater()
- raise
-
- if creating:
- createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
- self.log.debug("blocking on lock of: \"%s\")" % 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("/Local/Default")
- self.log.debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)" % (
- "/DSLocal",
- recordTypes,
- self.returnedAttributes,
- ))
- results = list(opendirectory.listAllRecordsWithAttributes_list(
- localNodeDirectory,
- recordTypes,
- self.returnedAttributes,
- ))
- except opendirectory.ODError, ex:
- self.log.error("Open Directory (node=%s) error: %s" % ("/Local/Default", str(ex)))
- raise
-
- self._dsLocalRecords = []
- for (recordShortName, value) in results: #@UnusedVariable
-
- record = VCardRecord(self, value, "/Local/Default")
-
- if self.ignoreSystemRecords:
- # remove system users and people
- if record.guid.startswith("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
- self.log.info("Ignoring vcard for system record %s" % (record,))
- continue
-
- if record.guid in records:
- self.log.info("Record skipped due to conflict (duplicate uuid): %s" % (record,))
- else:
- try:
- vCardText = record.vCardText()
- except:
- traceback.print_exc()
- self.log.info("Could not get vcard for record %s" % (record,))
- else:
- self.log.debug("VCard text =\n%s" % (vCardText,))
- records[record.guid] = record
-
- return records
-
- if not self.liveQuery or not self.queryDSLocal:
- return {}
-
- if time.time() > 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):
- """
- Get a list of filtered VCardRecord for the given query with the given attributes.
- query == None gets all records. attribute == None gets VCardRecord.allDSQueryAttributes
- """
- limited = False
- queryResults = (yield self._queryDirectory(query, attributes, maxRecords))
- if maxRecords and len(queryResults) >= maxRecords:
- limited = True
- self.log.debug("Directory address book record limit (= %d) reached." % (maxRecords,))
-
- self.log.debug("Query done. Inspecting %s results" % len(queryResults))
-
- records = self._getDSLocalRecords().copy()
- self.log.debug("Adding %s DSLocal results" % 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("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
- self.log.info("Ignoring vcard for system record %s" % (record,))
- continue
-
- if record.guid in records:
- self.log.info("Ignoring vcard for record due to conflict (duplicate uuid): %s" % (record,))
- else:
- records[record.guid] = record
-
- self.log.debug("After filtering, %s records (limited=%s)." % (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 "":
- self.log.debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r,%r)" % (
- 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("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r)" % (
- node,
- query.generate(),
- False,
- recordType,
- attributes,
- maxRecords,
- ))
- results = list(
- opendirectory.queryRecordsWithAttributes_list(
- directory,
- query.generate(),
- False,
- recordType,
- attributes,
- maxRecords,
- ))
- else:
- self.log.debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r,%r)" % (
- node,
- recordType,
- attributes,
- maxRecords,
- ))
- results = list(
- opendirectory.listAllRecordsWithAttributes_list(
- directory,
- recordType,
- attributes,
- maxRecords,
- ))
- except opendirectory.ODError, ex:
- self.log.error("Open Directory (node=%s) error: %s" % (self.realmName, str(ex)))
- raise
-
- allResults.extend(results)
-
- if maxRecords:
- maxRecords -= len(results)
- if maxRecords <= 0:
- break
-
- elaspedTime = time.time() - startTime
- self.log.info("Timing: Directory query: %.1f ms (%d records, %.2f records/sec)" % (elaspedTime * 1000, len(allResults), len(allResults) / elaspedTime))
- return succeed(allResults)
-
-
- def _getDSFilter(self, addressBookFilter):
- """
- 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
- """
- def propFilterListQuery(filterAllOf, propFilters):
-
- def propFilterExpression(filterAllOf, propFilter):
- #print("propFilterExpression")
- """
- Create an expression for a single prop-filter element.
-
- @param propFilter: the L{PropertyFilter} element.
- @return: (needsAllRecords, espressionAttributes, expressions) tuple
- """
-
- def definedExpression(defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
- if constant or filterName in ("N" , "FN", "UID",):
- return (defined, [], []) # all records have this property so no records do not have it
- else:
- matchList = list(set([dsquery.match(attrName, "", dsattributes.eDSStartsWith) for attrName in allAttrStrings]))
- if defined:
- return andOrExpression(allOf, queryAttributes, matchList)
- else:
- if len(matchList) > 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("andOrExpression(propFilterAllOf=%r, queryAttributes%r, matchList%r)" % (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 == "PHOTO":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["JPEG", ], }):
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "ADR":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }):
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "LABEL":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["POSTAL", "PARCEL", ]}):
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "TEL":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "EMAIL":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "URL":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {}):
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif filterName == "KEY":
- if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE", ]}):
- return not propFilterAllOf
- oneSupported |= propFilterAllOf
- elif not filterName.startswith("X-"): #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 ("REV" , "BDAY",):
- rawString = matchString
- matchString = ""
- for c in rawString:
- if not c in "TZ-:":
- matchString += c
- elif propFilter.filter_name == "GEO":
- matchString = ",".join(matchString.split(";"))
-
- if propFilter.filter_name in ("N" , "ADR", "ORG",):
- # for structured properties, change into multiple strings for ds query
- if propFilter.filter_name == "ADR":
- #split by newline and comma
- rawStrings = ",".join(matchString.split("\n")).split(",")
- else:
- #split by space
- rawStrings = matchString.split(" ")
-
- # 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 == "UID":
- matchString = matchStrings[0]
- seperatorIndex = matchString.find(VCardRecord.peopleUIDSeparator)
- if seperatorIndex > 1:
- recordNameStart = seperatorIndex + len(VCardRecord.peopleUIDSeparator)
- else:
- seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)
- if seperatorIndex > 1:
- recordNameStart = seperatorIndex + len(VCardRecord.userUIDSeparator)
- else:
- recordNameStart = sys.maxint
-
- if recordNameStart < len(matchString) - 1:
- try:
- recordNameQualifier = matchString[recordNameStart:].decode("base64").decode("utf8")
- except Exception, e:
- self.log.debug("Could not decode UID string %r in %r: %r" % (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 ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL",):
- if textMatchElement.match_type == "equals":
- matchType = dsattributes.eDSExact
- elif textMatchElement.match_type == "starts-with":
- matchType = dsattributes.eDSStartsWith
- elif textMatchElement.match_type == "ends-with":
- 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) > 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 == "allof"
-
- # handle parameter filter elements
- if len(paramFilterElements) > 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 &= textMatchNeedsAllRecords
- else:
- propFilterNeedsAllRecords |= textMatchNeedsAllRecords
- propFilterAttributes += textMatchExpressionAttributes
- propFilterExpressionList += textMatchExpression
-
- if (len(propFilterExpressionList) > 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("propFilterListQuery: filterAllOf=%r, propFilters=%r" % (filterAllOf, propFilters,))
- """
- Create an expression for a list of prop-filter elements.
-
- @param filterAllOf: the C{True} if parent filter test is "allof"
- @param propFilters: the C{list} of L{ComponentFilter} elements.
- @return: (needsAllRecords, espressionAttributes, expression) tuple
- """
- needsAllRecords = filterAllOf
- attributes = []
- expressions = []
- for propFilter in propFilters:
-
- propNeedsAllRecords, propExpressionAttributes, propExpression = propFilterExpression(filterAllOf, propFilter)
- if filterAllOf:
- needsAllRecords &= propNeedsAllRecords
- else:
- needsAllRecords |= propNeedsAllRecords
- attributes += propExpressionAttributes
- expressions += propExpression
-
- if len(expressions) > 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("_getDSFilter")
- # 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 == "allof"
- if len(addressBookFilter.children) > 0:
- return propFilterListQuery(filterAllOf, addressBookFilter.children)
- else:
- return (filterAllOf, [], [])
- else:
- return (False, [], [])
-
-
- def _attributesForAddressBookQuery(self, addressBookQuery):
-
- propertyNames = []
- #print( "addressBookQuery.qname=%r" % addressBookQuery.qname)
- if addressBookQuery.qname() == ("DAV:", "prop"):
-
- for property in addressBookQuery.children:
- #print("property = %r" % property )
- if isinstance(property, carddavxml.AddressData):
- for addressProperty in property.children:
- #print("addressProperty = %r" % addressProperty )
- if isinstance(addressProperty, carddavxml.Property):
- #print("Adding property %r", addressProperty.attributes["name"])
- propertyNames.append(addressProperty.attributes["name"])
-
- elif not self.fakeETag and property.qname() == ("DAV:", "getetag"):
- # for a real etag == md5(vCard), we need all attributes
- propertyNames = None
- break
-
- if not len(propertyNames):
- #print("using all attributes")
- return self.returnedAttributes
-
- else:
- propertyNames.append("X-INTERNAL-MINIMUM-VCARD-PROPERTIES") # these properties are required to make a vCard
- queryAttributes = []
- for prop in propertyNames:
- if prop in VCardRecord.dsqueryAttributesForProperty:
- #print("adding attributes %r" % VCardRecord.dsqueryAttributesForProperty.get(prop))
- queryAttributes += VCardRecord.dsqueryAttributesForProperty.get(prop)
-
- return list(set(queryAttributes).intersection(set(self.returnedAttributes)))
-
-
- @inlineCallbacks
- def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
- """
- Cache the vCards for a given addressBookFilder and addressBookQuery
- """
- startTime = time.time()
- #print("Timing: cacheVCardsForAddressBookQuery.starttime=%f" % startTime)
-
- allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
- #print("allRecords = %s, query = %s" % (allRecords, "None" 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 > 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("Timing: Cache fill: %.1f ms" % (elaspedTime * 1000,))
-
- returnValue((updateLock, limited))
-
-
- @inlineCallbacks
- def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
- """
- Get vCards for a given addressBookFilder and addressBookQuery
- """
-
- allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
- #print("allRecords = %s, query = %s" % (allRecords, "None" 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 > 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("Could not get vcard for record %s" % (record,))
- else:
- if not record.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation).startswith("/Local"):
- self.log.debug("VCard text =\n%s" % (vCardText,))
- queryRecords.append(record)
-
- returnValue((queryRecords, limited,))
-
-
-
-class VCardRecord(DirectoryRecord, DAVPropertyMixIn):
- """
- Open Directory implementation of L{IDirectoryRecord}.
- """
-
- # od attributes that may contribute to vcard properties
- # will be used to translate vCard queries to od queries
-
- dsqueryAttributesForProperty = {
-
- "FN" : [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
- dsattributes.kDS1AttrMiddleName,
- dsattributes.kDSNAttrNamePrefix,
- dsattributes.kDSNAttrNameSuffix,
- dsattributes.kDS1AttrDistinguishedName,
- dsattributes.kDSNAttrRecordName,
- ],
- "N" : [
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
- dsattributes.kDS1AttrMiddleName,
- dsattributes.kDSNAttrNamePrefix,
- dsattributes.kDSNAttrNameSuffix,
- dsattributes.kDS1AttrDistinguishedName,
- dsattributes.kDSNAttrRecordName,
- ],
- "NICKNAME" : [
- dsattributes.kDSNAttrNickName,
- ],
- # no binary searching
- "PHOTO" : [
- (dsattributes.kDSNAttrJPEGPhoto, "base64"),
- ],
- "BDAY" : [
- dsattributes.kDS1AttrBirthday,
- ],
- "ADR" : [
- dsattributes.kDSNAttrBuilding,
- dsattributes.kDSNAttrStreet,
- dsattributes.kDSNAttrCity,
- dsattributes.kDSNAttrState,
- dsattributes.kDSNAttrPostalCode,
- dsattributes.kDSNAttrCountry,
- ],
- "LABEL" : [
- dsattributes.kDSNAttrPostalAddress,
- dsattributes.kDSNAttrPostalAddressContacts,
- dsattributes.kDSNAttrAddressLine1,
- dsattributes.kDSNAttrAddressLine2,
- dsattributes.kDSNAttrAddressLine3,
- ],
- "TEL" : [
- dsattributes.kDSNAttrPhoneNumber,
- dsattributes.kDSNAttrMobileNumber,
- dsattributes.kDSNAttrPagerNumber,
- dsattributes.kDSNAttrHomePhoneNumber,
- dsattributes.kDSNAttrPhoneContacts,
- dsattributes.kDSNAttrFaxNumber,
- #dsattributes.kDSNAttrAreaCode,
- ],
- "EMAIL" : [
- dsattributes.kDSNAttrEMailAddress,
- dsattributes.kDSNAttrEMailContacts,
- ],
- "GEO" : [
- dsattributes.kDSNAttrMapCoordinates,
- ],
- "TITLE" : [
- dsattributes.kDSNAttrJobTitle,
- ],
- "ORG" : [
- dsattributes.kDSNAttrCompany,
- dsattributes.kDSNAttrOrganizationName,
- dsattributes.kDSNAttrDepartment,
- ],
- "NOTE" : [
- dsattributes.kDS1AttrComment,
- dsattributes.kDS1AttrNote,
- ],
- "REV" : [
- dsattributes.kDS1AttrModificationTimestamp,
- ],
- "UID" : [
- dsattributes.kDS1AttrGeneratedUID,
- # special cased
- #dsattributes.kDSNAttrMetaNodeLocation,
- #dsattributes.kDSNAttrRecordName,
- #dsattributes.kDS1AttrDistinguishedName,
- ],
- "URL" : [
- dsattributes.kDS1AttrWeblogURI,
- dsattributes.kDSNAttrURL,
- ],
- "KEY" : [
- # check on format, are these all binary?
- (dsattributes.kDSNAttrPGPPublicKey, "base64"),
- (dsattributes.kDS1AttrUserCertificate, "base64"),
- (dsattributes.kDS1AttrUserPKCS12Data, "base64"),
- (dsattributes.kDS1AttrUserSMIMECertificate, "base64"),
- ],
- # too bad this is not one X-Attribute with params. Would make searching easier
- "X-AIM" : [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-JABBER" : [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-MSN" : [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-YAHOO" : [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-ICQ" : [
- dsattributes.kDSNAttrIMHandle,
- ],
- "X-ABRELATEDNAMES" : [
- dsattributes.kDSNAttrRelationships,
- ],
- "X-INTERNAL-MINIMUM-VCARD-PROPERTIES" : [
- dsattributes.kDS1AttrGeneratedUID,
- dsattributes.kDSNAttrMetaNodeLocation,
- dsattributes.kDS1AttrFirstName,
- dsattributes.kDS1AttrLastName,
- dsattributes.kDS1AttrMiddleName,
- dsattributes.kDSNAttrNamePrefix,
- dsattributes.kDSNAttrNameSuffix,
- dsattributes.kDS1AttrDistinguishedName,
- dsattributes.kDSNAttrRecordName,
- dsattributes.kDSNAttrRecordType,
- dsattributes.kDS1AttrModificationTimestamp,
- dsattributes.kDS1AttrCreationTimestamp,
- ],
- "X-INTERNAL-REQUIRED" : [
- 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 = "-" + OpenDirectoryBackingService.baseGUID + "-"
- userUIDSeparator = "-bf07a1a2-"
- peopleUIDSeparator = "-cf07a1a2-"
-
- constantProperties = {
- # 3.6.3 PRODID Type Definition
- "PRODID": vCardProductID,
- # 3.6.9 VERSION Type Definition
- "VERSION": "3.0",
- }
-
-
- def __init__(self, service, recordAttributes, defaultNodeName=None):
-
- self.log.debug("service=%s, attributes=%s" % (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("utf8") for val in values]
- else:
- self.attributes[key] = removeControlChars(values).decode("utf8")
- 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 == "/LDAPv3/127.0.0.1":
- node = defaultNodeName if defaultNodeName else service.realmName
- self.attributes[dsattributes.kDSNAttrMetaNodeLocation] = node
-
- guid = self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)
- if not guid:
- if service.standardizeSyntheticUIDs:
- nodeUUIDStr = "00000000"
- else:
- nodeUUIDStr = "%x" % abs(hash(node))
- nameUUIDStr = "".join(self.firstValueForAttribute(dsattributes.kDSNAttrRecordName).encode("utf8").encode("base64").split("\n"))
- 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 = "/".join(guid.split(":")).upper()
- self.attributes[dsattributes.kDS1AttrGeneratedUID] = guid
-
- if self.firstValueForAttribute(dsattributes.kDS1AttrLastName) == "99":
- 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 "<%s[%s(%s)] %s(%s) %r>" % (
- self.__class__.__name__,
- self.firstValueForAttribute(dsattributes.kDSNAttrRecordType),
- self.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation),
- self.guid,
- self.shortNames,
- self.fullName
- )
-
-
- def __hash__(self):
- s = "".join([
- "%s:%s" % (attribute, self.valuesForAttribute(attribute),)
- for attribute in self.attributes
- ])
- return hash(s)
-
- """
- def nextFileName(self):
- self.renameCounter += 1
- self.fileName = self.baseFileName + "-" + str(self.renameCounter)
- self.fileNameLower = self.fileName.lower()
- """
-
- 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("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0]
-
- if len(nonEmptyValues) > 0:
- return nonEmptyValues
- else:
- return default_values
-
-
- def firstValueForAttribute(self, attributeName, default_value=""):
- values = self.attributes.get(attributeName)
- if values is None:
- return default_value
- elif isinstance(values, list):
- return values[0].encode("utf_8") if isinstance(values[0], unicode) else values[0]
- else:
- return values.encode("utf_8") if isinstance(values, unicode) else values
-
-
- def joinedValuesForAttribute(self, attributeName, separator=",", default_string=""):
- values = self.valuesForAttribute(attributeName, None)
- if not values:
- return default_string
- else:
- return separator.join(values)
-
-
- def isoDateStringForDateAttribute(self, attributeName, default_string=""):
- modDate = self.firstValueForAttribute(attributeName, default_string)
- revDate = None
- if modDate:
- if len(modDate) >= len("YYYYMMDD") and modDate[:8].isdigit():
- revDate = "%s-%s-%s" % (modDate[:4], modDate[4:6], modDate[6:8],)
- if len(modDate) >= len("YYYYMMDDHHMMSS") and modDate[8:14].isdigit():
- revDate += "T%s:%s:%sZ" % (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("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty,))
-
- def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters=None):
- groupCount[0] += 1
- groupPrefix = "item%d" % groupCount[0]
- vcard.addProperty(Property(propertyName, propertyValue, params=parameters, group=groupPrefix))
- vcard.addProperty(Property("X-ABLabel", 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("|")
- if len(splitValue) > 1:
- attrValue = splitValue[0]
-
- colonIndex = attrValue.find(":")
- if (colonIndex > len(attrValue) - 2):
- raise ValueError("Nothing after colon.")
-
- propertyValue = attrValue[colonIndex + 1:]
- labelString = attrValue[:colonIndex] if colonIndex > 0 else defaultLabel
- paramTypeString = labelString.upper()
-
- # add PREF to first prop's parameters
- paramTypeStrings = [paramTypeString, ]
- if preferred and "PREF" != paramTypeString:
- paramTypeStrings += ["PREF", ]
- parameters = {"TYPE": 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("addPropertiesAndLabelsForPrefixedAttribute(): groupCount=%r, propertyPrefix=%r, propertyName=%r, nolabelParamTypes=%r, labelMap=%r, attrType=%r" % (groupCount[0], propertyPrefix, propertyName, nolabelParamTypes, labelMap, attrType,))
- self.log.error("addPropertiesAndLabelsForPrefixedAttribute(): Trouble parsing attribute %s, with value \"%s\". Error = %s" % (attrType, attrValue, e,))
-
- #print("VCardRecord.vCard")
- # create vCard
- vcard = Component("VCARD")
- 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("FN", 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, "")
-
- nameObject = N(
- first=self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
- last=familyName,
- middle=self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
- prefix=self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
- suffix=self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
- )
- vcard.addProperty(Property("N", 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("FN", 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("NICKNAME", 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("PHOTO", photo, params={"ENCODING": ["b", ], "TYPE": ["JPEG", ], }), 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("BDAY", 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, "")
- street = self.valuesForAttribute(dsattributes.kDSNAttrStreet, "")
- city = self.valuesForAttribute(dsattributes.kDSNAttrCity, "")
- region = self.valuesForAttribute(dsattributes.kDSNAttrState, "")
- code = self.valuesForAttribute(dsattributes.kDSNAttrPostalCode, "")
- country = self.valuesForAttribute(dsattributes.kDSNAttrCountry, "")
-
- if len(extended) > 0 or len(street) > 0 or len(city) > 0 or len(region) > 0 or len(code) > 0 or len(country) > 0:
- vcard.addProperty(Property("ADR",
- Adr(
- #pobox = box,
- extended=extended,
- street=street,
- locality=city,
- region=region,
- postalcode=code,
- country=country,
- ),
- params={"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }
- ))
-
- # 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("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddress, label)
-
- for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddressContacts):
- addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
-
- address = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine1)
- addressLine2 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine2)
- if len(addressLine2) > 0:
- address += "\n" + addressLine2
- addressLine3 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine3)
- if len(addressLine3) > 0:
- address += "\n" + addressLine3
-
- if len(address) > 0:
- vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL", ]}))
-
- # 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 = {"TYPE": ["WORK", "PREF", "VOICE", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrPhoneNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPhoneNumber)
- params = {"TYPE": ["WORK", "VOICE", ], }
-
- params = {"TYPE": ["WORK", "PREF", "CELL", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrMobileNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrMobileNumber)
- params = {"TYPE": ["WORK", "CELL", ], }
-
- params = {"TYPE": ["WORK", "PREF", "FAX", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrFaxNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrFaxNumber)
- params = {"TYPE": ["WORK", "FAX", ], }
-
- params = {"TYPE": ["WORK", "PREF", "PAGER", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrPagerNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPagerNumber)
- params = {"TYPE": ["WORK", "PAGER", ], }
-
- params = {"TYPE": ["HOME", "PREF", "VOICE", ], }
- for phone in self.valuesForAttribute(dsattributes.kDSNAttrHomePhoneNumber):
- addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrHomePhoneNumber)
- params = {"TYPE": ["HOME", "VOICE", ], }
-
- addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "TEL", "work",
- ["VOICE", "CELL", "FAX", "PAGER", ], {},
- dsattributes.kDSNAttrPhoneContacts,)
-
- """
- # EXTEND: Use this attribute
- # dsattributes.kDSNAttrAreaCode, # Area code of a user's phone number.
- """
-
- # 3.3.2 EMAIL Type Definition
- # dsattributes.kDSNAttrEMailAddress, # Email address of usually a user record.
-
- # setup some params
- preferredWorkParams = {"TYPE": ["WORK", "PREF", "INTERNET", ], }
- workParams = {"TYPE": ["WORK", "INTERNET", ], }
- params = preferredWorkParams
- for emailAddress in self.valuesForAttribute(dsattributes.kDSNAttrEMailAddress):
- addUniqueProperty(vcard, Property("EMAIL", emailAddress, params=params), (("TYPE", "PREF"),), 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, "EMAIL", "work",
- ["WORK", "HOME", ], {},
- dsattributes.kDSNAttrEMailContacts,)
-
- """
- # UNIMPLEMENTED:
- # 3.3.3 MAILER Type Definition
- """
- # 3.4 GEOGRAPHICAL TYPES http://tools.ietf.org/html/rfc2426#section-3.4
- """
- # UNIMPLEMENTED:
- # 3.4.1 TZ Type Definition
- """
- # 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(",")
- if (len(parts) == 2):
- vcard.addProperty(Property("GEO", parts))
- else:
- self.log.info("Ignoring malformed attribute %r with value %r. Well-formed example: 7.7,10.6." % (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("TITLE", jobTitle), None, dsattributes.kDSNAttrJobTitle, jobTitle)
-
- """
- # UNIMPLEMENTED:
- # 3.5.2 ROLE Type Definition
- # 3.5.3 LOGO Type Definition
- # 3.5.4 AGENT Type Definition
- """
- # 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) > 0 or len(department) > 0:
- vcard.addProperty(Property("ORG", (company, department, extra,),))
-
- # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
- """
- # UNIMPLEMENTED:
- # 3.6.1 CATEGORIES Type Definition
- """
- # 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("NOTE", comment), None, dsattributes.kDS1AttrComment, comment)
-
- for note in self.valuesForAttribute(dsattributes.kDS1AttrNote):
- addUniqueProperty(vcard, Property("NOTE", note), None, dsattributes.kDS1AttrNote, note)
-
- # 3.6.3 PRODID Type Definition
- #vcard.addProperty(Property("PRODID", vCardProductID + "//BUILD %s" % twistedcaldav.__version__))
- #vcard.addProperty(Property("PRODID", vCardProductID))
- # ADDED WITH CONTSTANT PROPERTIES
-
- # 3.6.4 REV Type Definition
- revDate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrModificationTimestamp)
- if revDate:
- vcard.addProperty(Property("REV", DateTime.parseText(revDate, fullISO=True)))
-
- """
- # UNIMPLEMENTED:
- # 3.6.5 SORT-STRING Type Definition
- # 3.6.6 SOUND Type Definition
- """
- # 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 "A579E95E-CDFE-4EBC-B7E7-F2158562170F".
- # The standard format contains 32 hex characters and four hyphen characters.
- # !! don't use self.guid which is URL encoded
- vcard.addProperty(Property("UID", 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, "weblog", "URL", url, parameters={"TYPE": ["Weblog", ]})
-
- for url in self.valuesForAttribute(dsattributes.kDSNAttrURL):
- addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters={"TYPE": ["Homepage", ]})
-
- # 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("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["PGPPublicKey", ]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserCertificate):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserCertificate", ]}), None, dsattributes.kDS1AttrUserCertificate, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserPKCS12Data):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserPKCS12Data", ]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
-
- for key in self.valuesForAttribute(dsattributes.kDS1AttrUserSMIMECertificate):
- addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserSMIMECertificate", ]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
-
- """
- X- attributes, Address Book support
- """
- # 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, "X-", None, "aim",
- ["AIM", "JABBER", "MSN", "YAHOO", "ICQ"],
- {},
- 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, "X-ABRELATEDNAMES", "friend",
- [],
- {"FATHER": "_$!<Father>!$_",
- "MOTHER": "_$!<Mother>!$_",
- "PARENT": "_$!<Parent>!$_",
- "BROTHER": "_$!<Brother>!$_",
- "SISTER": "_$!<Sister>!$_",
- "CHILD": "_$!<Child>!$_",
- "FRIEND": "_$!<Friend>!$_",
- "SPOUSE": "_$!<Spouse>!$_",
- "PARTNER": "_$!<Partner>!$_",
- "ASSISTANT": "_$!<Assistant>!$_",
- "MANAGER": "_$!<Manager>!$_", },
- dsattributes.kDSNAttrRelationships,)
-
- # special case for Apple
- if self.service.appleInternalServer:
- for manager in self.valuesForAttribute("dsAttrTypeNative:appleManager"):
- splitManager = manager.split("|")
- if len(splitManager) >= 4:
- managerValue = "%s %s, %s" % (splitManager[0], splitManager[1], splitManager[3],)
- elif len(splitManager) >= 2:
- managerValue = "%s %s" % (splitManager[0], splitManager[1])
- else:
- managerValue = manager
- addPropertyAndLabel(groupCount, "_$!<Manager>!$_", "X-ABRELATEDNAMES", managerValue, parameters={"TYPE": ["Manager", ]})
-
- """
- # 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.
-
- """
-
- # 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("X-" + "-".join(attribute.split(":")), 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("UID").value() + ".vcf"
- #print("uriName():self._uriName=%s" % self._uriName)
- return self._uriName
-
-
- def hRef(self, parentURI="/directory/"):
- 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("VCardResource.readProperty: qname = %s" % (qname, ))
-
- if namespace == dav_namespace:
- if name == "resourcetype":
- result = davxml.ResourceType.empty #@UndefinedVariable
- #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "getetag":
- result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
- #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "getcontenttype":
- mimeType = MimeType('text', 'vcard', {})
- result = davxml.GETContentType(generateContentType(mimeType))
- #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "getcontentlength":
- result = davxml.GETContentLength.fromString(str(len(self.vCardText())))
- #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "getlastmodified":
- if self.vCard().hasProperty("REV"):
- modDatetime = parse_date(self.vCard().propertyValue("REV"))
- 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("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "creationdate":
- creationDateString = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrCreationTimestamp)
- if creationDateString:
- creationDatetime = parse_date(creationDateString)
- elif self.vCard().hasProperty("REV"): # use modification date property if it exists
- creationDatetime = parse_date(self.vCard().propertyValue("REV"))
- else:
- creationDatetime = datetime.datetime.utcnow()
- result = davxml.CreationDate.fromDate(creationDatetime)
- #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
- return result
- elif name == "displayname":
- # AddressBook.app uses N. Use FN or UID instead?
- result = davxml.DisplayName.fromString(self.vCard().propertyValue("N"))
- #print("VCardResource.readProperty: qname = %s, result = %s" % (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("VCardResource.listProperties()")
- qnames = set(self.liveProperties())
-
- # Add dynamic live properties that exist
- dynamicLiveProperties = (
- (dav_namespace, "quota-available-bytes"),
- (dav_namespace, "quota-used-bytes"),
- )
- for dqname in dynamicLiveProperties:
- #print("VCardResource.listProperties: removing dqname=%s" % (dqname,))
- qnames.remove(dqname)
-
- for qname in self.deadProperties().list():
- if (qname not in qnames) and (qname[0] != twisted_private_namespace):
- #print("listProperties: adding qname=%s" % (qname,))
- qnames.add(qname)
-
- #for qn in qnames: print("VCardResource.listProperties: qn=%s" % (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' <= a <= '\x1F':
- result = ""
- for c in utf8String:
- if '\x00' <= c <= '\x1F':
- pass
- else:
- result += c
- #if utf8String != result: print ("changed %r to %r" % (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"> "DirectoryCalendarPrincipalResource",
</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"> """ Converts calendar user types to OD type names """
</span><span class="cx">
</span><del>- return "recordType", DirectoryRecord.fromCUType(cuType)
</del><ins>+ return "recordType", 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("urn:uuid:"):
</span><del>- return "guid", cua[9:]
</del><ins>+ return "guid", uuid.UUID(cua[9:])
</ins><span class="cx">
</span><span class="cx"> elif cua.startswith("mailto:"):
</span><span class="cx"> return "emailAddresses", cua[7:]
</span><span class="lines">@@ -126,7 +126,7 @@
</span><span class="cx"> elif cua.startswith("/") or cua.startswith("http"):
</span><span class="cx"> ignored, collection, id = cua.rsplit("/", 2)
</span><span class="cx"> if collection == "__uids__":
</span><del>- return "guid", id
</del><ins>+ return "uid", id
</ins><span class="cx"> else:
</span><span class="cx"> return "recordName", 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 "<%s: %s %s>" % (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("user"), 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 -> 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 = "Kerberos:%s" % (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 = "http://calendarserver.org/ns/"
</span><span class="cx"> _fieldMap = {
</span><span class="cx"> ("DAV:" , "displayname") :
</span><del>- ("fullName", None, "Display Name", davxml.DisplayName),
</del><ins>+ ("fullNames", None, "Display Name", davxml.DisplayName),
</ins><span class="cx"> ("urn:ietf:params:xml:ns:caldav" , "calendar-user-type") :
</span><span class="cx"> ("", cuTypeConverter, "Calendar User Type",
</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"> "http" : config.HTTPPort,
</span><span class="cx"> "https": 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 == "urn":
</span><span class="cx"> if path.startswith("uuid:"):
</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("/").split("/")]
</span><span class="cx"> if segments[0] == "" 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("No principal for calendar user address: %r" % (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 == "":
</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):
- """
- 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}
- """
-
- 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"> """
</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"> """
</span><del>- def __init__(self, parent, recordType):
</del><ins>+ def __init__(self, parent, name, recordType):
</ins><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryProvisioningResource.__init__(
</span><span class="cx"> self,
</span><del>- joinURL(parent.principalCollectionURL(), recordType) + "/",
</del><ins>+ joinURL(parent.principalCollectionURL(), name) + "/",
</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 == "":
</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("Cannot list children of record type {rt}",
+ 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 == "":
</span><del>- return self
</del><ins>+ returnValue(self)
</ins><span class="cx">
</span><span class="cx"> if "#" 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("No principal found for UID: %s" % (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"> """
</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 = ""
+ 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=",".join(record.shortNames),
- securityIDs=",".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("utf-8"),
+ principalGUID=guid,
+ recordType=record.service.recordTypeToOldName(record.recordType),
+ shortNames=",".join([n.encode("utf-8") for n in record.shortNames]),
+ # MOVE2WHO: need this?
+ # securityIDs=",".join(record.authIDs),
+ fullName=record.displayName.encode("utf-8"),
+ # 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"> """
</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"> """
</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("utf-8"))
+ ) + 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"> """
</span><span class="cx"> Principals are the same if their principalURLs are the same.
</span><span class="cx"> """
</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("urn:uuid:%s" % (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, "guid"):
+ returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
+
</ins><span class="cx"> elif namespace == calendarserver_namespace:
</span><del>- if name == "first-name":
- firstName = self.record.firstName
- if firstName is not None:
- returnValue(customxml.FirstNameProperty(firstName))
- else:
- returnValue(None)
</del><span class="cx">
</span><del>- elif name == "last-name":
- lastName = self.record.lastName
- if lastName is not None:
- returnValue(customxml.LastNameProperty(lastName))
- else:
- returnValue(None)
</del><ins>+ # MOVE2WHO
+ # if name == "first-name":
+ # firstName = self.record.firstName
+ # if firstName is not None:
+ # returnValue(customxml.FirstNameProperty(firstName))
+ # else:
+ # returnValue(None)
</ins><span class="cx">
</span><del>- elif name == "email-address-set":
</del><ins>+ # elif name == "last-name":
+ # lastName = self.record.lastName
+ # if lastName is not None:
+ # returnValue(customxml.LastNameProperty(lastName))
+ # else:
+ # returnValue(None)
+
+ if name == "email-address-set":
+ 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):
+ """
+ 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
+ """
</ins><span class="cx"> proxyFors = set()
</span><span class="cx">
</span><del>- if resolve_memberships:
- cache = getattr(self.record.service, "groupMembershipCache", None)
- if cache:
- log.debug("proxyFor is using groupMembershipCache")
- 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("groups", 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("No principal found for directory record: %r" % (relative,))
</span><span class="cx"> else:
</span><span class="cx"> if proxy:
</span><span class="cx"> if proxy == "read-write":
</span><del>- found = found.getChild("calendar-proxy-write")
</del><ins>+ found = (yield found.getChild("calendar-proxy-write"))
</ins><span class="cx"> else:
</span><del>- found = found.getChild("calendar-proxy-read")
</del><ins>+ found = (yield found.getChild("calendar-proxy-read"))
</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("members"))
</del><ins>+ return self._getRelatives("members")
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def expandedGroupMembers(self):
</span><del>- return succeed(self._getRelatives("members", infinity=True))
</del><ins>+ return self._getRelatives("members", 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, "groupMembershipCache", None)
- if cache:
- log.debug("groupMemberships is using groupMembershipCache")
- 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("groups", infinity=infinity)
</del><ins>+ groups = yield self._getRelatives("groups", 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, "calendar-proxy-write"),
+ (False, "calendar-proxy-read")
+ ):
+ proxyFors = yield self.proxyFor(readWrite)
+ for proxyFor in proxyFors:
+ subPrincipal = yield self.parent.principalForUID(
+ "{}#{}".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"> """
</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"> """
</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"> """
</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"> """
</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 == "":
</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"> """
</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 = ""
</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 = ""
</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 == "":
</span><del>- return self
</del><ins>+ return succeed(self)
</ins><span class="cx">
</span><del>- if config.EnableProxyPrincipals and name in ("calendar-proxy-read",
- "calendar-proxy-write"):
</del><ins>+ if config.EnableProxyPrincipals and name in (
+ "calendar-proxy-read", "calendar-proxy-write",
+ "calendar-proxy-read-for", "calendar-proxy-write-for",
+ ):
</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 ("calendar-proxy-read", "calendar-proxy-write")
</del><ins>+ return (
+ "calendar-proxy-read", "calendar-proxy-write",
+ )
</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):
- """
- Format a list of principals into some twisted.web.template DOM objects.
- """
- 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, "record"):
- return " - %s" % (principal.record.fullName,)
- else:
- return ""
-
- return formatList(
- tags.a(href=principal.principalURL())(
- str(principal), describe(principal)
- )
- for principal in sorted(principals, key=recordKey)
- )
-
-
-
-def formatList(iterable):
- """
- Format a list of stuff as an interable.
- """
- thereAreAny = False
- try:
- item = None
- for item in iterable:
- thereAreAny = True
- yield " -> "
- if item is None:
- yield "None"
- else:
- yield item
- yield "\n"
- except Exception, e:
- log.error("Exception while rendering: %s" % (e,))
- Failure().printTraceback()
- yield " ** %s **: %s\n" % (e.__class__.__name__, e)
- if not thereAreAny:
- yield " '()\n"
-
-
-
-def formatLink(url):
- """
- Convert a URL string into some twisted.web.template DOM objects for
- rendering as a link to itself.
- """
- return tags.a(href=url)(url)
-
-
-
-def formatLinks(urls):
- """
- Format a list of URL strings as a list of twisted.web.template DOM links.
- """
- 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"> <!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test">
- <user>
- <uid>admin</uid>
- <guid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</guid>
</del><ins>+<directory realm="Test">
+ <record type="user">
+ <short-name>admin</short-name>
+ <uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
</ins><span class="cx"> <password>nimda</password>
</span><del>- <name>Administrators</name>
- </user>
- <user>
- <uid>wsanchez</uid>
</del><ins>+ <full-name>Administrators</full-name>
+ </record>
+ <record type="user">
+ <short-name>wsanchez</short-name>
+ <uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
</ins><span class="cx"> <guid>6423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
</span><span class="cx"> <password>zehcnasw</password>
</span><del>- <name>Wilfredo Sanchez</name>
- <email-address>wsanchez@example.com</email-address>
- </user>
- <user>
- <uid>cdaboo</uid>
</del><ins>+ <full-name>Wilfredo Sanchez</full-name>
+ <email>wsanchez@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>cdaboo</short-name>
+ <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
</ins><span class="cx"> <guid>5A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
</span><span class="cx"> <password>oobadc</password>
</span><del>- <name>Cyrus Daboo</name>
- <email-address>cdaboo@example.com</email-address>
- </user>
- <user>
- <uid>lecroy</uid>
- <guid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</guid>
</del><ins>+ <full-name>Cyrus Daboo</full-name>
+ <email>cdaboo@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>lecroy</short-name>
+ <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>
</ins><span class="cx"> <password>yorcel</password>
</span><del>- <name>Chris Lecroy</name>
- <email-address>lecroy@example.com</email-address>
- </user>
- <user>
- <uid>dreid</uid>
- <guid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
</del><ins>+ <full-name>Chris Lecroy</full-name>
+ <email>lecroy@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>dreid</short-name>
+ <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
</ins><span class="cx"> <password>dierd</password>
</span><del>- <name>David Reid</name>
- <email-address>dreid@example.com</email-address>
- </user>
- <user>
- <uid>doublequotes</uid>
</del><ins>+ <full-name>David Reid</full-name>
+ <email>dreid@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>doublequotes</short-name>
+ <uid>8E04787E-336D-41ED-A70B-D233AD0DCE6F</uid>
</ins><span class="cx"> <guid>8E04787E-336D-41ED-A70B-D233AD0DCE6F</guid>
</span><span class="cx"> <password>setouqelbuod</password>
</span><del>- <name>Double "quotey" Quotes</name>
- <email-address>doublequotes@example.com</email-address>
- </user>
- <user>
- <uid>nocalendar</uid>
- <guid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</guid>
</del><ins>+ <full-name>Double "quotey" Quotes</full-name>
+ <email>doublequotes@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>purge1</short-name>
+ <uid>C76DB741-5A2A-4239-8112-10CF152AFCA4</uid>
+ <guid>C76DB741-5A2A-4239-8112-10CF152AFCA4</guid>
+ <password>purge1</password>
+ <full-name>purge1</full-name>
+ <email>purge1@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>purge2</short-name>
+ <uid>FFED7B62-2E08-496E-BD32-B2F95FFDDB6B</uid>
+ <guid>FFED7B62-2E08-496E-BD32-B2F95FFDDB6B</guid>
+ <password>purge2</password>
+ <full-name>purge2</full-name>
+ <email>purge2@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>home1</short-name>
+ <uid>home1</uid>
+ <password>home1</password>
+ <full-name>Home One</full-name>
+ <email>home1@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>home2</short-name>
+ <uid>home2</uid>
+ <password>home2</password>
+ <full-name>Home Two</full-name>
+ <email>home2@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>nocalendar</short-name>
+ <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
</ins><span class="cx"> <password>radnelacon</password>
</span><del>- <name>No Calendar</name>
- <email-address>nocalendar@example.com</email-address>
- </user>
- <user>
- <uid>usera</uid>
- <guid>7423F94A-6B76-4A3A-815B-D52CFD77935D</guid>
</del><ins>+ <full-name>No Calendar</full-name>
+ <email>nocalendar@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>usera</short-name>
+ <uid>7423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>a</name>
- <email-address>a@example.com</email-address>
- </user>
- <user>
- <uid>userb</uid>
- <guid>8A985493-EE2C-4665-94CF-4DFEA3A89500</guid>
</del><ins>+ <full-name>a</full-name>
+ <email>a@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>userb</short-name>
+ <uid>8A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
</ins><span class="cx"> <password>b</password>
</span><del>- <name>b</name>
- <email-address>b@example.com</email-address>
- </user>
- <user>
- <uid>userc</uid>
- <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2</guid>
</del><ins>+ <full-name>b</full-name>
+ <email>b@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>userc</short-name>
+ <uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2</uid>
</ins><span class="cx"> <password>c</password>
</span><del>- <name>c</name>
- <email-address>c@example.com</email-address>
- </user>
- <user>
- <uid>usercalonly</uid>
- <guid>9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE</guid>
</del><ins>+ <full-name>c</full-name>
+ <email>c@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>usercalonly</short-name>
+ <uid>9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>a calonly</name>
- <email-address>a-calonly@example.com</email-address>
- </user>
- <user>
- <uid>useradbkonly</uid>
- <guid>7678EC8A-A069-4E82-9066-7279C6718507</guid>
</del><ins>+ <full-name>a calonly</full-name>
+ <email>a-calonly@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>useradbkonly</short-name>
+ <uid>7678EC8A-A069-4E82-9066-7279C6718507</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>a adbkonly</name>
- <email-address>a-adbkonly@example.com</email-address>
- </user>
- <user>
- <uid>nonascii</uid>
- <uid>nonascii佐藤</uid>
- <guid>320B73A1-46E2-4180-9563-782DFDBE1F63</guid>
</del><ins>+ <full-name>a adbkonly</full-name>
+ <email>a-adbkonly@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>nonascii</short-name>
+ <short-name>nonascii佐藤</short-name>
+ <uid>320B73A1-46E2-4180-9563-782DFDBE1F63</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>佐藤佐藤佐藤</name>
- <email-address>nonascii@example.com</email-address>
- </user>
- <user>
- <uid>delegator</uid>
- <guid>FC465590-E9E9-4746-ACE8-6C756A49FE4D</guid>
</del><ins>+ <full-name>佐藤佐藤佐藤</full-name>
+ <email>nonascii@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>delegator</short-name>
+ <uid>FC465590-E9E9-4746-ACE8-6C756A49FE4D</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>Calendar Delegator</name>
- <email-address>calendardelegator@example.com</email-address>
- </user>
- <user>
- <uid>occasionaldelegate</uid>
- <guid>EC465590-E9E9-4746-ACE8-6C756A49FE4D</guid>
</del><ins>+ <full-name>Calendar Delegator</full-name>
+ <email>calendardelegator@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>occasionaldelegate</short-name>
+ <uid>EC465590-E9E9-4746-ACE8-6C756A49FE4D</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>Occasional Delegate</name>
- <email-address>occasional@example.com</email-address>
- </user>
- <user>
- <uid>delegateviagroup</uid>
- <guid>46D9D716-CBEE-490F-907A-66FA6C3767FF</guid>
</del><ins>+ <full-name>Occasional Delegate</full-name>
+ <email>occasional@example.com</email>
+ </record>
+ <record type="user">
+ <short-name>delegateviagroup</short-name>
+ <uid>46D9D716-CBEE-490F-907A-66FA6C3767FF</uid>
</ins><span class="cx"> <password>a</password>
</span><del>- <name>Delegate Via Group</name>
- <email-address>delegateviagroup@example.com</email-address>
- </user>
- <group>
- <uid>delegategroup</uid>
- <guid>00599DAF-3E75-42DD-9DB7-52617E79943F</guid>
- <name>Delegate Group</name>
- <members>
- <member type="users">delegateviagroup</member>
- </members>
- </group>
- <user repeat="100">
- <uid>user%02d</uid>
- <guid>user%02d</guid>
- <password>%02duser</password>
- <name>~35 User %02d</name>
- <first-name>~5</first-name>
- <last-name>~9 User %02d</last-name>
- <email-address>~10@example.com</email-address>
- </user>
- <group>
- <uid>managers</uid>
- <guid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</guid>
</del><ins>+ <full-name>Delegate Via Group</full-name>
+ <email>delegateviagroup@example.com</email>
+ </record>
+ <record type="group">
+ <short-name>delegategroup</short-name>
+ <uid>00599DAF-3E75-42DD-9DB7-52617E79943F</uid>
+ <full-name>Delegate Group</full-name>
+ <member-uid>46D9D716-CBEE-490F-907A-66FA6C3767FF</member-uid>
+ </record>
+
+ <record type="user">
+ <short-name>user01</short-name>
+ <uid>user01</uid>
+ <password>user01</password>
+ <full-name>User 01</full-name>
+ <email>user01@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user02</short-name>
+ <uid>user02</uid>
+ <password>user02</password>
+ <full-name>User 02</full-name>
+ <email>user02@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user03</short-name>
+ <uid>user03</uid>
+ <password>user03</password>
+ <full-name>User 03</full-name>
+ <email>user03@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user04</short-name>
+ <uid>user04</uid>
+ <password>user04</password>
+ <full-name>User 04</full-name>
+ <email>user04@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user05</short-name>
+ <uid>user05</uid>
+ <password>user05</password>
+ <full-name>User 05</full-name>
+ <email>user05@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user06</short-name>
+ <uid>user06</uid>
+ <password>user06</password>
+ <full-name>User 06</full-name>
+ <email>user06@example.com</email>
+ </record>
+
+ <record type="user">
+ <uid>__wsanchez1__</uid>
+ <short-name>wsanchez1</short-name>
+ <short-name>wilfredo_sanchez</short-name>
+ <full-name>Wilfredo Sanchez</full-name>
+ <password>zehcnasw</password>
+ <email>wsanchez@bitbucket.calendarserver.org</email>
+ <email>wsanchez@devnull.twistedmatrix.com</email>
+ </record>
+
+ <record type="user">
+ <uid>__glyph1__</uid>
+ <short-name>glyph1</short-name>
+ <full-name>Glyph Lefkowitz</full-name>
+ <password>hpylg</password>
+ <email>glyph@bitbucket.calendarserver.org</email>
+ <email>glyph@devnull.twistedmatrix.com</email>
+ </record>
+
+ <record type="user">
+ <uid>__sagen1__</uid>
+ <short-name>sagen</short-name>
+ <short-name>sagen1</short-name>
+ <full-name>Morgen Sagen</full-name>
+ <password>negas</password>
+ <email>sagen@bitbucket.calendarserver.org</email>
+ </record>
+
+ <record type="user">
+ <uid>__cdaboo1__</uid>
+ <short-name>cdaboo1</short-name>
+ <full-name>Cyrus Daboo</full-name>
+ <password>suryc</password>
+ <email>cdaboo@bitbucket.calendarserver.org</email>
+ </record>
+
+ <record type="user">
+ <uid>__dre1__</uid>
+ <short-name>dre1</short-name>
+ <short-name>dre</short-name>
+ <full-name>Andre LaBranche</full-name>
+ <password>erd</password>
+ <email>dre@bitbucket.calendarserver.org</email>
+ </record>
+
+ <record type="group">
+ <uid>__top_group_1__</uid>
+ <short-name>top-group-1</short-name>
+ <full-name>Top Group 1</full-name>
+ <email>topgroup1@example.com</email>
+ <member-uid>__wsanchez1__</member-uid>
+ <member-uid>__glyph1__</member-uid>
+ <member-uid>__sub_group_1__</member-uid>
+ </record>
+
+ <record type="group">
+ <uid>__sub_group_1__</uid>
+ <short-name>sub-group-1</short-name>
+ <full-name>Sub Group 1</full-name>
+ <email>subgroup1@example.com</email>
+ <member-uid>__sagen1__</member-uid>
+ <member-uid>__cdaboo1__</member-uid>
+ </record>
+
+
+ <record type="group">
+ <short-name>managers</short-name>
+ <uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
</ins><span class="cx"> <password>managers</password>
</span><del>- <name>Managers</name>
- <members>
- <member type="users">lecroy</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Managers</full-name>
+ <member-uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</member-uid>
+ </record>
+ <record type="group">
+ <short-name>admin</short-name>
</ins><span class="cx"> <uid>admin</uid>
</span><del>- <guid>admin</guid>
</del><span class="cx"> <password>admin</password>
</span><del>- <name>Administrators</name>
- <members>
- <member type="groups">managers</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Administrators</full-name>
+ <member-uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</member-uid>
+ </record>
+ <record type="group">
+ <short-name>grunts</short-name>
</ins><span class="cx"> <uid>grunts</uid>
</span><del>- <guid>grunts</guid>
</del><span class="cx"> <password>grunts</password>
</span><del>- <name>We do all the work</name>
- <members>
- <member>wsanchez</member>
- <member>cdaboo</member>
- <member>dreid</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>We do all the work</full-name>
+ <member-uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</member-uid>
+ <member-uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</member-uid>
+ <member-uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</member-uid>
+ </record>
+ <record type="group">
+ <short-name>right_coast</short-name>
</ins><span class="cx"> <uid>right_coast</uid>
</span><del>- <guid>right_coast</guid>
</del><span class="cx"> <password>right_coast</password>
</span><del>- <name>East Coast</name>
- <members>
- <member>cdaboo</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>East Coast</full-name>
+ <member-uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</member-uid>
+ </record>
+ <record type="group">
+ <short-name>left_coast</short-name>
</ins><span class="cx"> <uid>left_coast</uid>
</span><del>- <guid>left_coast</guid>
</del><span class="cx"> <password>left_coast</password>
</span><del>- <name>West Coast</name>
- <members>
- <member>wsanchez</member>
- <member>lecroy</member>
- <member>dreid</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>West Coast</full-name>
+ <member-uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</member-uid>
+ <member-uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</member-uid>
+ <member-uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</member-uid>
+ </record>
+ <record type="group">
+ <short-name>both_coasts</short-name>
</ins><span class="cx"> <uid>both_coasts</uid>
</span><del>- <guid>both_coasts</guid>
</del><span class="cx"> <password>both_coasts</password>
</span><del>- <name>Both Coasts</name>
- <members>
- <member type="groups">right_coast</member>
- <member type="groups">left_coast</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Both Coasts</full-name>
+ <member-uid>right_coast</member-uid>
+ <member-uid>left_coast</member-uid>
+ </record>
+ <record type="group">
+ <short-name>recursive1_coasts</short-name>
</ins><span class="cx"> <uid>recursive1_coasts</uid>
</span><del>- <guid>recursive1_coasts</guid>
</del><span class="cx"> <password>recursive1_coasts</password>
</span><del>- <name>Recursive1 Coasts</name>
- <members>
- <member type="groups">recursive2_coasts</member>
- <member>wsanchez</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Recursive1 Coasts</full-name>
+ <member-uid>recursive2_coasts</member-uid>
+ <member-uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</member-uid>
+ </record>
+ <record type="group">
+ <short-name>recursive2_coasts</short-name>
</ins><span class="cx"> <uid>recursive2_coasts</uid>
</span><del>- <guid>recursive2_coasts</guid>
</del><span class="cx"> <password>recursive2_coasts</password>
</span><del>- <name>Recursive2 Coasts</name>
- <members>
- <member type="groups">recursive1_coasts</member>
- <member>cdaboo</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Recursive2 Coasts</full-name>
+ <member-uid>recursive1_coasts</member-uid>
+ <member-uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</member-uid>
+ </record>
+ <record type="group">
+ <short-name>non_calendar_group</short-name>
</ins><span class="cx"> <uid>non_calendar_group</uid>
</span><del>- <guid>non_calendar_group</guid>
</del><span class="cx"> <password>non_calendar_group</password>
</span><del>- <name>Non-calendar group</name>
- <members>
- <member>cdaboo</member>
- <member>lecroy</member>
- </members>
- </group>
- <location>
- <uid>mercury</uid>
- <guid>mercury</guid>
- <password>mercury</password>
- <name>Mercury Seven</name>
- <email-address>mercury@example.com</email-address>
- </location>
- <location>
- <uid>gemini</uid>
- <guid>gemini</guid>
- <password>gemini</password>
- <name>Gemini Twelve</name>
- <email-address>gemini@example.com</email-address>
- </location>
- <location>
- <uid>apollo</uid>
- <guid>apollo</guid>
- <password>apollo</password>
- <name>Apollo Eleven</name>
- <email-address>apollo@example.com</email-address>
- </location>
- <location>
- <uid>orion</uid>
- <guid>orion</guid>
- <password>orion</password>
- <name>Orion</name>
- <email-address>orion@example.com</email-address>
- </location>
- <resource>
- <uid>transporter</uid>
- <guid>transporter</guid>
- <password>transporter</password>
- <name>Mass Transporter</name>
- <email-address>transporter@example.com</email-address>
- </resource>
- <resource>
- <uid>ftlcpu</uid>
- <guid>ftlcpu</guid>
- <password>ftlcpu</password>
- <name>Faster-Than-Light Microprocessor</name>
- <email-address>ftlcpu@example.com</email-address>
- </resource>
- <resource>
- <uid>non_calendar_proxy</uid>
- <guid>non_calendar_proxy</guid>
- <password>non_calendar_proxy</password>
- <name>Non-calendar proxy</name>
- <email-address>non_calendar_proxy@example.com</email-address>
- </resource>
- <resource>
- <uid>disabled</uid>
- <guid>disabled</guid>
- <password>disabled</password>
- <name>Disabled Record</name>
- <email-address>disabled@example.com</email-address>
- </resource>
-</accounts>
</del><ins>+ <full-name>Non-calendar group</full-name>
+ <member-uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</member-uid>
+ <member-uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</member-uid>
+ </record>
+
+ <!-- Calverify test records -->
+
+ <record type="user">
+ <short-name>example1</short-name>
+ <uid>D46F3D71-04B7-43C2-A7B6-6F92F92E61D0</uid>
+ <guid>D46F3D71-04B7-43C2-A7B6-6F92F92E61D0</guid>
+ <password>example</password>
+ <full-name>Example User1</full-name>
+ <email>example1@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>example2</short-name>
+ <uid>47B16BB4-DB5F-4BF6-85FE-A7DA54230F92</uid>
+ <guid>47B16BB4-DB5F-4BF6-85FE-A7DA54230F92</guid>
+ <password>example</password>
+ <full-name>Example User2</full-name>
+ <email>example2@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>example3</short-name>
+ <uid>AC478592-7783-44D1-B2AE-52359B4E8415</uid>
+ <guid>AC478592-7783-44D1-B2AE-52359B4E8415</guid>
+ <password>example</password>
+ <full-name>Example User3</full-name>
+ <email>example3@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>example4</short-name>
+ <uid>A89E3A97-1658-4E45-A185-479F3E49D446</uid>
+ <guid>A89E3A97-1658-4E45-A185-479F3E49D446</guid>
+ <password>example</password>
+ <full-name>Example User4</full-name>
+ <email>example4@example.com</email>
+ </record>
+
+</directory>
</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"> <!DOCTYPE augments SYSTEM "../../../conf/auth/augments.dtd">
</span><span class="cx">
</span><span class="cx"> <augments realm="Test">
</span><ins>+ <!--
</ins><span class="cx"> <record>
</span><ins>+ <uid>Location-Default</uid>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>Resource-Default</uid>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+ </record>
+ -->
+
+ <record>
</ins><span class="cx"> <uid>D11F03A0-97EA-48AF-9A6C-FAC7F3975766</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>6423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> <server-id>00001</server-id>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>5A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> <server-id>00002</server-id>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>8B4288F6-CC82-491D-8EF9-642EF4F3E7D0</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>543D28BA-F74F-4D5F-9243-B3E3A61171E5</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>false</enable-calendar>
</span><span class="cx"> <enable-addressbook>false</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record repeat="100">
</span><span class="cx"> <uid>user%02d</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><del>- <uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>admin</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>grunts</uid>
- <enable>true</enable>
- </record>
- <record>
</del><span class="cx"> <uid>right_coast</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>left_coast</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><del>- <uid>both_coasts</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>recursive1_coasts</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>recursive2_coasts</uid>
- <enable>true</enable>
- </record>
- <record>
- <uid>non_calendar_group</uid>
- <enable>true</enable>
- </record>
- <record>
</del><span class="cx"> <uid>mercury</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>gemini</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>apollo</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> <auto-accept-group>both_coasts</auto-accept-group>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>orion</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>transporter</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>ftlcpu</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><ins>+ <!--
</ins><span class="cx"> <record>
</span><span class="cx"> <uid>non_calendar_proxy</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><ins>+-->
</ins><span class="cx"> <record>
</span><del>- <uid>disabled</uid>
- <enable>false</enable>
- </record>
- <record>
</del><span class="cx"> <uid>7423F94A-6B76-4A3A-815B-D52CFD77935D</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>8A985493-EE2C-4665-94CF-4DFEA3A89500</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>9E1FFAC4-3CCD-45A1-8272-D161C92D2EEE</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>7678EC8A-A069-4E82-9066-7279C6718507</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-addressbook>true</enable-addressbook>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>FC465590-E9E9-4746-ACE8-6C756A49FE4D</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>EC465590-E9E9-4746-ACE8-6C756A49FE4D</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>true</enable-calendar>
</span><span class="cx"> <enable-login>true</enable-login>
</span><span class="cx"> </record>
</span><span class="cx"> <record>
</span><span class="cx"> <uid>00599DAF-3E75-42DD-9DB7-52617E79943F</uid>
</span><del>- <enable>true</enable>
</del><span class="cx"> <enable-calendar>false</enable-calendar>
</span><span class="cx"> <enable-login>false</enable-login>
</span><span class="cx"> </record>
</span><ins>+ <record>
+ <uid>75EA36BE-F71B-40F9-81F9-CF59BF40CA8F</uid>
+ <enable-calendar>true</enable-calendar>
+ <auto-schedule>true</auto-schedule>
+ </record>
+
</ins><span class="cx"> </augments>
</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"> <!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test">
-</accounts>
</del><ins>+<directory realm="Test">
+ <record type="location">
+ <uid>mercury</uid>
+ <short-name>mercury</short-name>
+ <password>mercury</password>
+ <full-name>Mercury Seven</full-name>
+ <email>mercury@example.com</email>
+ </record>
+ <record type="location">
+ <uid>gemini</uid>
+ <short-name>gemini</short-name>
+ <password>gemini</password>
+ <full-name>Gemini Twelve</full-name>
+ <email>gemini@example.com</email>
+ </record>
+ <record type="location">
+ <uid>apollo</uid>
+ <short-name>apollo</short-name>
+ <password>apollo</password>
+ <full-name>Apollo Eleven</full-name>
+ <email>apollo@example.com</email>
+ </record>
+ <record type="location">
+ <uid>orion</uid>
+ <short-name>orion</short-name>
+ <password>orion</password>
+ <full-name>Orion</full-name>
+ <email>orion@example.com</email>
+ </record>
+ <record type="resource">
+ <uid>transporter</uid>
+ <short-name>transporter</short-name>
+ <password>transporter</password>
+ <full-name>Mass Transporter</full-name>
+ <email>transporter@example.com</email>
+ </record>
+ <record type="resource">
+ <uid>ftlcpu</uid>
+ <short-name>ftlcpu</short-name>
+ <password>ftlcpu</password>
+ <full-name>Faster-Than-Light Microprocessor</full-name>
+ <email>ftlcpu@example.com</email>
+ </record>
+ <record type="resource">
+ <uid>non_calendar_proxy</uid>
+ <short-name>non_calendar_proxy</short-name>
+ <password>non_calendar_proxy</password>
+ <full-name>Non-calendar proxy</full-name>
+ <email>non_calendar_proxy@example.com</email>
+ </record>
+ <record type="resource">
+ <uid>disabled</uid>
+ <short-name>disabled</short-name>
+ <password>disabled</password>
+ <full-name>Disabled Record</full-name>
+ <email>disabled@example.com</email>
+ </record>
+ <record type="location">
+ <uid>__sanchezoffice__</uid>
+ <short-name>sanchezoffice</short-name>
+ <full-name>Sanchez Office</full-name>
+ </record>
+ <record type="location">
+ <short-name>location01</short-name>
+ <uid>75EA36BE-F71B-40F9-81F9-CF59BF40CA8F</uid>
+ <guid>75EA36BE-F71B-40F9-81F9-CF59BF40CA8F</guid>
+ <password>location01</password>
+ <full-name>Room 01</full-name>
+ </record>
+ <record type="location">
+ <short-name>room-with-address-1</short-name>
+ <uid>room-addr-1</uid>
+ <guid>634A102B-6902-464F-9451-8A86A31628C1</guid>
+ <password>room-addr-2</password>
+ <full-name>Room with Address 1</full-name>
+ <associated-address>1-infinite-loop</associated-address>
+ </record>
+ <record type="location">
+ <short-name>room-with-address-2</short-name>
+ <uid>room-addr-2</uid>
+ <password>room-addr-2</password>
+ <full-name>Room with Address 2</full-name>
+ <associated-address>2-infinite-loop</associated-address>
+ </record>
+ <record type="address">
+ <short-name>il1</short-name>
+ <uid>1-infinite-loop</uid>
+ <full-name>One Infinite Loop</full-name>
+ <street-address>1 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.331741,-122.030333</geographic-location>
+ </record>
+ <record type="address">
+ <short-name>il2</short-name>
+ <uid>2-infinite-loop</uid>
+ <full-name>Two Infinite Loop</full-name>
+ <street-address>2 Infinite Loop, Cupertino, CA 95014</street-address>
+ <geographic-location>37.332633,-122.030502</geographic-location>
+ </record>
+
+</directory>
</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 "License");
-# 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 "AS IS" 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 = "xml:"
-
-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["prefix"] = prefix
- info["members"] = tuple(
- (t, prefix + s) for t, s in info.get("members", {})
- )
- records[prefix + record] = info
- return records
- return get
-
- recordTypes = property(_recordTypes)
- users = property(_records("users"))
- groups = property(_records("groups"))
- locations = property(_records("locations"))
- resources = property(_records("resources"))
- addresses = property(_records("addresses"))
-
- recordTypePrefixes = tuple(s[0] for s in testServices)
-
-
- def service(self):
- """
- Returns an IDirectoryService.
- """
- xmlService = XMLDirectoryService(
- {
- 'xmlFile' : xmlFile,
- 'augmentService' :
- augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
- }
- )
- xmlService.recordTypePrefix = xml_prefix
-
- return AggregateDirectoryService((xmlService,), None)
-
-
- def test_setRealm(self):
- """
- setRealm gets propagated to nested services
- """
- aggregatedService = self.service()
- aggregatedService.setRealm("foo.example.com")
- for service in aggregatedService._recordTypes.values():
- self.assertEquals("foo.example.com", 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="users"):
</ins><span class="cx">
</span><span class="cx"> record = (yield db.getAugmentRecord(items["uid"], recordType))
</span><span class="cx"> self.assertTrue(record is not None, "Failed record uid: %s" % (items["uid"],))
</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="users"):
</ins><span class="cx">
</span><span class="cx"> record = (yield db.getAugmentRecord(uid, recordType))
</span><span class="cx"> self.assertTrue(record is not None, "Failed record uid: %s" % (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 "License");
-# 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 "AS IS" 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],
- (
- ("firstName", "morgen", True, "starts-with"),
- ("lastName", "sagen", True, "starts-with"),
- ),
- OpenDirectoryService._ODFields
- ),
- {
- ('dsAttrTypeStandard:FirstName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
- ('dsAttrTypeStandard:LastName', 'sagen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
- }
- )
- self.assertEquals(
- buildQueries(
- [
- dsattributes.kDSStdRecordTypeUsers,
- ],
- (
- ("firstName", "morgen", True, "starts-with"),
- ("emailAddresses", "morgen", True, "contains"),
- ),
- OpenDirectoryService._ODFields
- ),
- {
- ('dsAttrTypeStandard:FirstName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeUsers],
- ('dsAttrTypeStandard:EMailAddress', 'morgen', True, 'contains') : [dsattributes.kDSStdRecordTypeUsers],
- }
- )
- self.assertEquals(
- buildQueries(
- [
- dsattributes.kDSStdRecordTypeGroups,
- ],
- (
- ("firstName", "morgen", True, "starts-with"),
- ("lastName", "morgen", True, "starts-with"),
- ("fullName", "morgen", True, "starts-with"),
- ("emailAddresses", "morgen", True, "contains"),
- ),
- OpenDirectoryService._ODFields
- ),
- {
- ('dsAttrTypeStandard:RealName', 'morgen', True, 'starts-with') : [dsattributes.kDSStdRecordTypeGroups],
- ('dsAttrTypeStandard:EMailAddress', 'morgen', True, 'contains') : [dsattributes.kDSStdRecordTypeGroups],
- }
- )
- self.assertEquals(
- buildQueries(
- [
- dsattributes.kDSStdRecordTypeUsers,
- dsattributes.kDSStdRecordTypeGroups,
- ],
- (
- ("firstName", "morgen", True, "starts-with"),
- ("lastName", "morgen", True, "starts-with"),
- ("fullName", "morgen", True, "starts-with"),
- ("emailAddresses", "morgen", True, "contains"),
- ),
- 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,
- ],
- (
- ("firstName", "morgen", True, "starts-with"),
- ),
- OpenDirectoryService._ODFields
- ),
- {
- }
- )
-
-
- def test_buildLocalQueryFromTokens(self):
- """
- Verify the generating of the simpler queries passed to /Local/Default
- """
- results = buildLocalQueriesFromTokens([], OpenDirectoryService._ODFields)
- self.assertEquals(results, None)
-
- results = buildLocalQueriesFromTokens(["foo"], OpenDirectoryService._ODFields)
- self.assertEquals(
- results[0].generate(),
- "(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*))"
- )
-
- results = buildLocalQueriesFromTokens(["foo", "bar"], OpenDirectoryService._ODFields)
- self.assertEquals(
- results[0].generate(),
- "(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*))"
- )
- self.assertEquals(
- results[1].generate(),
- "(|(dsAttrTypeStandard:RealName=*bar*)(dsAttrTypeStandard:EMailAddress=bar*))"
- )
-
-
- def test_buildNestedQueryFromTokens(self):
- """
- Verify the generating of the complex nested queries
- """
- query = buildNestedQueryFromTokens([], OpenDirectoryService._ODFields)
- self.assertEquals(query, None)
-
- query = buildNestedQueryFromTokens(["foo"], OpenDirectoryService._ODFields)
- self.assertEquals(
- query.generate(),
- "(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*)(dsAttrTypeStandard:RecordName=foo*))"
- )
-
- query = buildNestedQueryFromTokens(["foo", "bar"], OpenDirectoryService._ODFields)
- self.assertEquals(
- query.generate(),
- "(&(|(dsAttrTypeStandard:RealName=*foo*)(dsAttrTypeStandard:EMailAddress=foo*)(dsAttrTypeStandard:RecordName=foo*))(|(dsAttrTypeStandard:RealName=*bar*)(dsAttrTypeStandard:EMailAddress=bar*)(dsAttrTypeStandard:RecordName=bar*)))"
- )
-
- query = buildNestedQueryFromTokens(["foo", "bar", "baz"], OpenDirectoryService._ODFields)
- self.assertEquals(
- query.generate(),
- "(&(|(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*)))"
- )
</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 "License");
-# 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 "AS IS" 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 = "Dummy Realm"
- baseGUID = "20CB1593-DE3F-4422-A7D7-BA9C2099B317"
-
- 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("guid"),
- shortNames=record.get("shortname"),
- authIDs=record.get("authid"),
- fullName=record.get("fullName"),
- firstName="",
- lastName="",
- emailAddresses=record.get("email"),
- )
-
- 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 = ("%s@example.com" % (shortNames[0],),)
-
- attrs = {
- "fullName": fullName,
- "guid": guid,
- "shortname": shortNames,
- "email": emails,
- "cua": tuple(["mailto:%s" % email for email in emails]),
- "authid": tuple(["Kerberos:%s" % email for email in emails])
- }
-
- if members:
- attrs["members"] = members
-
- if resourceInfo:
- attrs["resourceInfo"] = resourceInfo
-
- return attrs
-
-
- def shortNameForFullName(self, fullName):
- return fullName.lower().replace(" ", "")
-
-
- def guidForShortName(self, shortName, recordType=""):
- return uuidFromName(self.baseGUID, "%s%s" % (recordType, shortName))
-
-
- def dummyRecords(self):
- SIZE = 10
- records = {
- DirectoryService.recordType_users: [
- self.fakeRecord("User %02d" % x, DirectoryService.recordType_users, multinames=(x > 5)) for x in range(1, SIZE + 1)
- ],
- DirectoryService.recordType_groups: [
- self.fakeRecord("Group %02d" % x, DirectoryService.recordType_groups) for x in range(1, SIZE + 1)
- ],
- DirectoryService.recordType_resources: [
- self.fakeRecord("Resource %02d" % x, DirectoryService.recordType_resources) for x in range(1, SIZE + 1)
- ],
- DirectoryService.recordType_locations: [
- self.fakeRecord("Location %02d" % x, DirectoryService.recordType_locations) for x in range(1, SIZE + 1)
- ],
- }
- # Add duplicate shortnames
- records[DirectoryService.recordType_users].append(self.fakeRecord("Duplicate", DirectoryService.recordType_users, multinames=True))
- records[DirectoryService.recordType_groups].append(self.fakeRecord("Duplicate", DirectoryService.recordType_groups, multinames=True))
- records[DirectoryService.recordType_resources].append(self.fakeRecord("Duplicate", DirectoryService.recordType_resources, multinames=True))
- records[DirectoryService.recordType_locations].append(self.fakeRecord("Duplicate", 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("user01", recordType=DirectoryService.recordType_users)) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user01", 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("user01", 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("user01", recordType=DirectoryService.recordType_users).lower()) is not None)
-
-
- def test_cacheoneshortname(self):
- self.dummyRecords()
-
- self.assertTrue(self.service.recordWithShortName(
- DirectoryService.recordType_users,
- "user02"
- ) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user02", 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,
- "user02"
- ) is not None)
- self.assertFalse(self.service.queried)
-
-
- def test_cacheoneemail(self):
- self.dummyRecords()
-
- self.assertTrue(self.service.recordWithCalendarUserAddress(
- "mailto:user03@example.com"
- ) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user03", 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(
- "mailto:user03@example.com"
- ) is not None)
- self.assertFalse(self.service.queried)
-
-
- def test_cacheonePrincipalsURLWithUIDS(self):
- self.dummyRecords()
-
- guid = self.guidForShortName("user03", "users")
- self.assertTrue(self.service.recordWithCalendarUserAddress(
- "/principals/__uids__/%s" % (guid,)
- ) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user03", 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(
- "/principals/__uids__/%s" % (guid,)
- ) is not None)
- self.assertFalse(self.service.queried)
-
-
- def test_cacheonePrincipalsURLWithUsers(self):
- self.dummyRecords()
-
- self.assertTrue(self.service.recordWithCalendarUserAddress(
- "/principals/users/user03"
- ) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user03", 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(
- "/principals/users/user03"
- ) is not None)
- self.assertFalse(self.service.queried)
-
-
- def test_cacheoneauthid(self):
- self.dummyRecords()
-
- self.assertTrue(self.service.recordWithAuthID(
- "Kerberos:user03@example.com"
- ) is not None)
- self.assertTrue(self.service.queried)
- self.verifyRecords(DirectoryService.recordType_users, set((
- self.guidForShortName("user03", 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(
- "Kerberos:user03@example.com"
- ) 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("missing")), None)
- self.assertTrue(self.service.queried)
-
- self.service.queried = False
- self.assertEquals(self.service.recordWithGUID(self.guidForShortName("missing")), 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("missing")), None)
- self.assertTrue(self.service.queried)
-
- self.service.queried = False
- self.assertEquals(self.service.recordWithGUID(self.guidForShortName("missing")), 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("missing")] = 0
-
- self.service.queried = False
- self.assertEquals(self.service.recordWithGUID(self.guidForShortName("missing")), None)
- self.assertTrue(self.service.queried)
-
-
- def test_duplicateShortNames(self):
- """
- Verify that when looking up records having duplicate short-names, the record of the
- proper type is returned
- """
-
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", True)
- self.dummyRecords()
-
- record = self.service.recordWithShortName(DirectoryService.recordType_users,
- "Duplicate")
- self.assertEquals(record.recordType, DirectoryService.recordType_users)
-
- record = self.service.recordWithShortName(DirectoryService.recordType_groups,
- "Duplicate")
- self.assertEquals(record.recordType, DirectoryService.recordType_groups)
-
- record = self.service.recordWithShortName(DirectoryService.recordType_resources,
- "Duplicate")
- self.assertEquals(record.recordType, DirectoryService.recordType_resources)
-
- record = self.service.recordWithShortName(DirectoryService.recordType_locations,
- "Duplicate")
- self.assertEquals(record.recordType, DirectoryService.recordType_locations)
-
-
- def test_generateMemcacheKey(self):
- """
- Verify keys are correctly generated based on the index type -- if index type is
- short-name, then the recordtype is encoded into the key.
- """
- self.assertEquals(
- self.service.generateMemcacheKey(self.service.INDEX_TYPE_GUID, "foo", "users"),
- "dir|v2|20CB1593-DE3F-4422-A7D7-BA9C2099B317|guid|foo",
- )
- self.assertEquals(
- self.service.generateMemcacheKey(self.service.INDEX_TYPE_SHORTNAME, "foo", "users"),
- "dir|v2|20CB1593-DE3F-4422-A7D7-BA9C2099B317|users|shortname|foo",
- )
</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 "License");
-# 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 "AS IS" 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 = {
- "calendar" : ["amanda", "betty"],
- "addressbook" : ["amanda", "carlene"],
- }
- if username in services[service]:
- return 0
- return 1
-
-
-
-class SACLTests(TestCase):
-
- def setUp(self):
- self.patch(DirectoryRecord, "CheckSACL", StubCheckSACL)
- self.patch(config, "EnableSACLs", True)
- self.service = DirectoryService()
- self.service.setRealm("test")
- self.service.baseGUID = "0E8E6EC2-8E52-4FF3-8F62-6F398B08A498"
-
-
- def test_applySACLs(self):
- """
- Users not in calendar SACL will have enabledForCalendaring set to
- False.
- Users not in addressbook SACL will have enabledForAddressBooks set to
- False.
- """
-
- data = [
- ("amanda", True, True,),
- ("betty", True, False,),
- ("carlene", False, True,),
- ("daniel", False, False,),
- ]
- for username, cal, ab in data:
- record = DirectoryRecord(self.service, "users", 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("proxies.sqlite")
-
- # Set up a principals hierarchy for each service we're testing with
- self.principalRootResources = {}
- name = self.directoryService.__class__.__name__
- url = "/" + name + "/"
-
- 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):
- """ Empty the proxy db between tests """
- 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):
- """
- Update a counter in the following test
- """
- self.count += 1
-
-
- def test_expandedMembers(self):
- """
- Make sure expandedMembers( ) returns a complete, flattened set of
- members of a group, including all sub-groups.
- """
- bothCoasts = self.directoryService.recordWithShortName(
- DirectoryService.recordType_groups, "both_coasts")
- 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):
- """
- Ensure we get back what we put in
- """
- cache = GroupMembershipCache("ProxyDB", expireSeconds=10)
-
- yield cache.setGroupsFor("a", set(["b", "c", "d"])) # a is in b, c, d
- members = (yield cache.getGroupsFor("a"))
- self.assertEquals(members, set(["b", "c", "d"]))
-
- yield cache.setGroupsFor("b", set()) # b not in any groups
- members = (yield cache.getGroupsFor("b"))
- self.assertEquals(members, set())
-
- cache._memcacheProtocol.advanceClock(10)
-
- members = (yield cache.getGroupsFor("a")) # has expired
- self.assertEquals(members, set())
-
-
- @inlineCallbacks
- def test_groupMembershipCacheUpdater(self):
- """
- Let the GroupMembershipCacheUpdater populate the cache, then make
- sure proxyFor( ) and groupMemberships( ) work from the cache
- """
- cache = GroupMembershipCache("ProxyDB", 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, "both_coasts"),
- 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)
-
- ("wsanchez",
- set(["mercury", "apollo", "orion", "gemini"]),
- set(["non_calendar_proxy"]),
- set(['left_coast',
- 'both_coasts',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'gemini#calendar-proxy-write',
- ]),
- ),
- ("cdaboo",
- set(["apollo", "orion", "non_calendar_proxy"]),
- set(["non_calendar_proxy"]),
- set(['both_coasts',
- 'non_calendar_group',
- 'recursive1_coasts',
- 'recursive2_coasts',
- ]),
- ),
- ("lecroy",
- set(["apollo", "mercury", "non_calendar_proxy"]),
- set(),
- set(['both_coasts',
- 'left_coast',
- 'non_calendar_group',
- ]),
- ),
- ("usera",
- set(),
- set(),
- set(),
- ),
- ("userb",
- set(['7423F94A-6B76-4A3A-815B-D52CFD77935D']),
- set(),
- set(['7423F94A-6B76-4A3A-815B-D52CFD77935D#calendar-proxy-write']),
- ),
- ("userc",
- 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, "mercury")
- proxyPrincipal = delegator.getChild("calendar-proxy-write")
- for expected, name in [(True, "wsanchez"), (False, "cdaboo")]:
- 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("accounts-modified.xml")
- self.directoryService._alwaysStat = True
- self.assertEquals((False, 8, 1), (yield updater.updateCache()))
- delegate = self._getPrincipalByShortName(DirectoryService.recordType_users, "wsanchez")
- proxyFor = (yield delegate.proxyFor(True))
- self.assertEquals(
- set([p.record.guid for p in proxyFor]),
- set(['gemini'])
- )
-
-
- @inlineCallbacks
- def test_groupMembershipCacheUpdaterExternalProxies(self):
- """
- Exercise external proxy assignment support (assignments come from the
- directory service itself)
- """
- cache = GroupMembershipCache("ProxyDB", 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
- # "transporter" resource...
- def fakeExternalProxies():
- return [
- (
- "transporter#calendar-proxy-write",
- set(["6423F94A-6B76-4A3A-815B-D52CFD77935D",
- "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"])
- ),
- (
- "transporter#calendar-proxy-read",
- set(["5A985493-EE2C-4665-94CF-4DFEA3A89500"])
- ),
- ]
-
- 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)
-
- ("wsanchez",
- set(["mercury", "apollo", "orion", "gemini", "transporter"]),
- set(["non_calendar_proxy"]),
- set(['left_coast',
- 'both_coasts',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'gemini#calendar-proxy-write',
- 'transporter#calendar-proxy-write',
- ]),
- ),
- ("cdaboo",
- set(["apollo", "orion", "non_calendar_proxy"]),
- set(["non_calendar_proxy", "transporter"]),
- set(['both_coasts',
- 'non_calendar_group',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'transporter#calendar-proxy-read',
- ]),
- ),
- ("lecroy",
- set(["apollo", "mercury", "non_calendar_proxy", "transporter"]),
- 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 [
- (
- "transporter#calendar-proxy-write",
- set(["8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"])
- ),
- ]
-
- 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: "transporter" is now gone for wsanchez and cdaboo
-
- ("wsanchez",
- set(["mercury", "apollo", "orion", "gemini"]),
- set(["non_calendar_proxy"]),
- set(['left_coast',
- 'both_coasts',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'gemini#calendar-proxy-write',
- ]),
- ),
- ("cdaboo",
- set(["apollo", "orion", "non_calendar_proxy"]),
- set(["non_calendar_proxy"]),
- set(['both_coasts',
- 'non_calendar_group',
- 'recursive1_coasts',
- 'recursive2_coasts',
- ]),
- ),
- ("lecroy",
- set(["apollo", "mercury", "non_calendar_proxy", "transporter"]),
- 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: "transporter" is now gone for everyone
-
- ("wsanchez",
- set(["mercury", "apollo", "orion", "gemini"]),
- set(["non_calendar_proxy"]),
- set(['left_coast',
- 'both_coasts',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'gemini#calendar-proxy-write',
- ]),
- ),
- ("cdaboo",
- set(["apollo", "orion", "non_calendar_proxy"]),
- set(["non_calendar_proxy"]),
- set(['both_coasts',
- 'non_calendar_group',
- 'recursive1_coasts',
- 'recursive2_coasts',
- ]),
- ),
- ("lecroy",
- set(["apollo", "mercury", "non_calendar_proxy"]),
- 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 [
- (
- "transporter#calendar-proxy-write",
- set(["8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"])
- ),
- ]
-
- 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)
-
- ("wsanchez",
- set(["mercury", "apollo", "orion", "gemini"]),
- set(["non_calendar_proxy"]),
- set(['left_coast',
- 'both_coasts',
- 'recursive1_coasts',
- 'recursive2_coasts',
- 'gemini#calendar-proxy-write',
- ]),
- ),
- ("cdaboo",
- set(["apollo", "orion", "non_calendar_proxy"]),
- set(["non_calendar_proxy"]),
- set(['both_coasts',
- 'non_calendar_group',
- 'recursive1_coasts',
- 'recursive2_coasts',
- ]),
- ),
- ("lecroy",
- set(["apollo", "mercury", "non_calendar_proxy", "transporter"]),
- 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):
- """
- Ensure external proxy assignment diffing works
- """
-
- self.assertEquals(
- (
- # changed
- [],
- # removed
- [],
- ),
- diffAssignments(
- # old
- [],
- # new
- [],
- )
- )
-
- self.assertEquals(
- (
- # changed
- [],
- # removed
- [],
- ),
- diffAssignments(
- # old
- [("B", set(["3"])), ("A", set(["1", "2"])), ],
- # new
- [("A", set(["1", "2"])), ("B", set(["3"])), ],
- )
- )
-
- self.assertEquals(
- (
- # changed
- [("A", set(["1", "2"])), ("B", set(["3"])), ],
- # removed
- [],
- ),
- diffAssignments(
- # old
- [],
- # new
- [("A", set(["1", "2"])), ("B", set(["3"])), ],
- )
- )
-
- self.assertEquals(
- (
- # changed
- [],
- # removed
- ["A", "B"],
- ),
- diffAssignments(
- # old
- [("A", set(["1", "2"])), ("B", set(["3"])), ],
- # new
- [],
- )
- )
-
- self.assertEquals(
- (
- # changed
- [("A", set(["2"])), ("C", set(["4", "5"])), ("D", set(["6"])), ],
- # removed
- ["B"],
- ),
- diffAssignments(
- # old
- [("A", set(["1", "2"])), ("B", set(["3"])), ("C", set(["4"])), ],
- # new
- [("D", set(["6"])), ("C", set(["4", "5"])), ("A", set(["2"])), ],
- )
- )
-
-
- @inlineCallbacks
- def test_groupMembershipCacheSnapshot(self):
- """
- The group membership cache creates a snapshot (a pickle file) of
- the member -> groups dictionary, and can quickly refresh memcached
- from that snapshot when restarting the server.
- """
- cache = GroupMembershipCache("ProxyDB", 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("memberships_cache")
-
- # 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 "fast". 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 = {
- "46D9D716-CBEE-490F-907A-66FA6C3767FF":
- set([
- u"00599DAF-3E75-42DD-9DB7-52617E79943F",
- ]),
- "5A985493-EE2C-4665-94CF-4DFEA3A89500":
- set([
- u"non_calendar_group",
- u"recursive1_coasts",
- u"recursive2_coasts",
- u"both_coasts"
- ]),
- "6423F94A-6B76-4A3A-815B-D52CFD77935D":
- set([
- u"left_coast",
- u"recursive1_coasts",
- u"recursive2_coasts",
- u"both_coasts"
- ]),
- "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1":
- set([
- u"left_coast",
- u"both_coasts"
- ]),
- "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0":
- set([
- u"non_calendar_group",
- u"left_coast",
- u"both_coasts"
- ]),
- "left_coast":
- set([
- u"both_coasts"
- ]),
- "recursive1_coasts":
- set([
- u"recursive1_coasts",
- u"recursive2_coasts"
- ]),
- "recursive2_coasts":
- set([
- u"recursive1_coasts",
- u"recursive2_coasts"
- ]),
- "right_coast":
- set([
- u"both_coasts"
- ])
- }
- members = pickle.loads(snapshotFile.getContent())
- self.assertEquals(members, expected)
-
- # "Corrupt" the snapshot and verify it is regenerated properly
- snapshotFile.setContent("xyzzy")
- cache.delete("group-cacher-populated")
- 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):
- """
- autoAcceptMembers( ) returns an empty list if no autoAcceptGroup is
- assigned, or the expanded membership if assigned.
- """
-
- # No auto-accept-group for "orion" in augments.xml
- orion = self.directoryService.recordWithGUID("orion")
- self.assertEquals(orion.autoAcceptMembers(), [])
-
- # "both_coasts" group assigned to "apollo" in augments.xml
- apollo = self.directoryService.recordWithGUID("apollo")
- self.assertEquals(
- set(apollo.autoAcceptMembers()),
- set([
- "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0",
- "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
- "5A985493-EE2C-4665-94CF-4DFEA3A89500",
- "6423F94A-6B76-4A3A-815B-D52CFD77935D",
- "right_coast",
- "left_coast",
- ])
- )
-
-
- # @inlineCallbacks
- # def testScheduling(self):
- # """
- # Exercise schedulePolledGroupCachingUpdate
- # """
-
- # 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 = "Fix WorkProposal to track delayed calls and cancel them"
-
-
-
-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("proxies.sqlite")
-
- # Set up a principals hierarchy for each service we're testing with
- self.principalRootResources = {}
- name = self.directoryService.__class__.__name__
- url = "/" + name + "/"
-
- 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):
- """ Empty the proxy db between tests """
- return calendaruserproxy.ProxyDBService.clean() #@UndefinedVariable
-
-
- @inlineCallbacks
- def test_recordsMatchingTokens(self):
- """
- Exercise the default recordsMatchingTokens implementation
- """
- records = list((yield self.directoryService.recordsMatchingTokens(["Use", "01"])))
- self.assertNotEquals(len(records), 0)
- shorts = [record.shortNames[0] for record in records]
- self.assertTrue("user01" in shorts)
-
- records = list((yield self.directoryService.recordsMatchingTokens(['"quotey"'],
- context=self.directoryService.searchContext_attendee)))
- self.assertEquals(len(records), 1)
- self.assertEquals(records[0].shortNames[0], "doublequotes")
-
- records = list((yield self.directoryService.recordsMatchingTokens(["coast"])))
- self.assertEquals(len(records), 5)
-
- records = list((yield self.directoryService.recordsMatchingTokens(["poll"],
- context=self.directoryService.searchContext_location)))
- self.assertEquals(len(records), 1)
- self.assertEquals(records[0].shortNames[0], "apollo")
-
-
- def test_recordTypesForSearchContext(self):
- self.assertEquals(
- [self.directoryService.recordType_locations],
- self.directoryService.recordTypesForSearchContext("location")
- )
- self.assertEquals(
- [self.directoryService.recordType_resources],
- self.directoryService.recordTypesForSearchContext("resource")
- )
- self.assertEquals(
- [self.directoryService.recordType_users],
- self.directoryService.recordTypesForSearchContext("user")
- )
- self.assertEquals(
- [self.directoryService.recordType_groups],
- self.directoryService.recordTypesForSearchContext("group")
- )
- self.assertEquals(
- set([
- self.directoryService.recordType_resources,
- self.directoryService.recordType_users,
- self.directoryService.recordType_groups
- ]),
- set(self.directoryService.recordTypesForSearchContext("attendee"))
- )
-
-
-
-class GUIDTests(TestCase):
-
- def setUp(self):
- self.service = DirectoryService()
- self.service.setRealm("test")
- self.service.baseGUID = "0E8E6EC2-8E52-4FF3-8F62-6F398B08A498"
-
-
- 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 = (
- (
- "0543A85A-D446-4CF6-80AE-6579FA60957F",
- "0543A85A-D446-4CF6-80AE-6579FA60957F"
- ),
- (
- "0543a85a-d446-4cf6-80ae-6579fa60957f",
- "0543A85A-D446-4CF6-80AE-6579FA60957F"
- ),
- (
- "0543A85AD4464CF680AE-6579FA60957F",
- "0543A85A-D446-4CF6-80AE-6579FA60957F"
- ),
- (
- "0543a85ad4464cf680ae6579fa60957f",
- "0543A85A-D446-4CF6-80AE-6579FA60957F"
- ),
- (
- "foo",
- "foo"
- ),
- (
- None,
- None
- ),
- )
- for original, expected in data:
- self.assertEquals(expected, normalizeUUID(original))
- record = DirectoryRecord(self.service, "users", original,
- shortNames=("testing",))
- self.assertEquals(expected, record.guid)
-
-
-
-class DirectoryServiceTests(TestCase):
- """
- Test L{DirectoryService} apis.
- """
-
- 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):
- """
- Create/persist a directory record based on the given values
- """
-
- 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("test")
- self.service.baseGUID = "0E8E6EC2-8E52-4FF3-8F62-6F398B08A498"
-
-
- def test_recordWithCalendarUserAddress_principal_uris(self):
- """
- Make sure that recordWithCalendarUserAddress handles percent-encoded
- principal URIs.
- """
-
- self.service.createRecord(
- DirectoryService.recordType_users,
- guid="user01",
- shortNames=("user 01", "User 01"),
- fullName="User 01",
- enabledForCalendaring=True,
- )
- self.service.createRecord(
- DirectoryService.recordType_users,
- guid="user02",
- shortNames=("user02", "User 02"),
- fullName="User 02",
- enabledForCalendaring=True,
- )
-
- record = self.service.recordWithCalendarUserAddress("/principals/users/user%2001")
- self.assertTrue(record is not None)
- record = self.service.recordWithCalendarUserAddress("/principals/users/user02")
- self.assertTrue(record is not None)
- record = self.service.recordWithCalendarUserAddress("/principals/users/user%0202")
- self.assertTrue(record is None)
-
-
-
-class DirectoryRecordTests(TestCase):
- """
- Test L{DirectoryRecord} apis.
- """
-
- def setUp(self):
- self.service = DirectoryService()
- self.service.setRealm("test")
- self.service.baseGUID = "0E8E6EC2-8E52-4FF3-8F62-6F398B08A498"
-
-
- def test_cacheToken(self):
- """
- Test that DirectoryRecord.cacheToken is different for different records, and its value changes
- as attributes on the record change.
- """
-
- record1 = DirectoryRecord(self.service, "users", str(uuid.uuid4()), shortNames=("testing1",))
- record2 = DirectoryRecord(self.service, "users", str(uuid.uuid4()), shortNames=("testing2",))
- 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 "License");
-# 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 "AS IS" 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):
- """
- Directory service provisioned principals.
- """
- 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["calendars"]
- self.setupCalendars()
-
-
- def test_guidchange(self):
- """
- DirectoryPrincipalResource.proxies()
- """
- oldUID = "5A985493-EE2C-4665-94CF-4DFEA3A89500"
- newUID = "38D8AC00-5490-4425-BE3A-05FFB9862444"
-
- homeResource = "/calendars/users/cdaboo/"
-
- 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, "cdaboo")
-
- # 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("/principals/__uids__/" + newUID + "/"), davxml.Write, False)
-
- # Make sure current user has access to their calendar home
- d = self._checkPrivileges(None, homeResource, davxml.HRef("/principals/__uids__/" + oldUID + "/"), 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 = "Test no longer works."
-
- def _checkPrivileges(self, resource, url, principal, privilege, allowed):
- request = SimpleRequest(self.site, "GET", "/")
-
- 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("%s should have %s privilege on %r" % (principal, privilege.sname(), resource))
- d.addErrback(onError)
- else:
- def onError(f):
- f.trap(AccessDeniedError)
- def onSuccess(_):
- #print(resource.readDeadProperty(davxml.ACL).toxml())
- self.fail("%s should not have %s privilege on %r" % (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 "License");
-# 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 "AS IS" 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("Skipping because ldap module not installed")
-else:
- from twistedcaldav.test.util import TestCase
-
- class BuildFilterTestCase(TestCase):
-
- def test_buildFilter(self):
- mapping = {
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": "mail",
- "firstName": "givenName",
- "lastName": "sn",
- "guid": "generateduid",
- "memberIDAttr": "generateduid",
- }
-
- entries = [
- {
- "fields": [
- ("fullName", "mor", True, u"starts-with"),
- ("emailAddresses", "mor", True, u"starts-with"),
- ("firstName", "mor", True, u"starts-with"),
- ("lastName", "mor", True, u"starts-with")
- ],
- "operand": "or",
- "recordType": "users",
- "expected": "(&(uid=*)(generateduid=*)(|(cn=mor*)(mail=mor*)(givenName=mor*)(sn=mor*)))",
- "optimize": False,
- },
- {
- "fields": [
- ("fullName", "mor(", True, u"starts-with"),
- ("emailAddresses", "mor)", True, u"contains"),
- ("firstName", "mor*", True, u"exact"),
- ("lastName", "mor\\", True, u"starts-with")
- ],
- "operand": "or",
- "recordType": "users",
- "expected": "(&(uid=*)(generateduid=*)(|(cn=mor\\28*)(mail=*mor\\29*)(givenName=mor\\2a)(sn=mor\\5c*)))",
- "optimize": False,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"starts-with"),
- ],
- "operand": "or",
- "recordType": "users",
- "expected": "(&(uid=*)(generateduid=*)(cn=mor*))",
- "optimize": False,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"contains"),
- ("emailAddresses", "mor", True, u"equals"),
- ("invalid", "mor", True, u"starts-with"),
- ],
- "operand": "and",
- "recordType": "users",
- "expected": "(&(uid=*)(generateduid=*)(&(cn=*mor*)(mail=mor)))",
- "optimize": False,
- },
- {
- "fields": [
- ("invalid", "mor", True, u"contains"),
- ("invalid", "mor", True, u"starts-with"),
- ],
- "operand": "and",
- "recordType": "users",
- "expected": None,
- "optimize": False,
- },
- {
- "fields": [],
- "operand": "and",
- "recordType": "users",
- "expected": None,
- "optimize": False,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"starts-with"),
- ("fullName", "sag", True, u"starts-with"),
- ("emailAddresses", "mor", True, u"starts-with"),
- ("emailAddresses", "sag", True, u"starts-with"),
- ("firstName", "mor", True, u"starts-with"),
- ("firstName", "sag", True, u"starts-with"),
- ("lastName", "mor", True, u"starts-with"),
- ("lastName", "sag", True, u"starts-with"),
- ],
- "operand": "or",
- "recordType": "users",
- "expected": "(&(uid=*)(generateduid=*)(|(&(givenName=mor*)(sn=sag*))(&(givenName=sag*)(sn=mor*))))",
- "optimize": True,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"starts-with"),
- ("fullName", "sag", True, u"starts-with"),
- ("emailAddresses", "mor", True, u"starts-with"),
- ("emailAddresses", "sag", True, u"starts-with"),
- ("firstName", "mor", True, u"starts-with"),
- ("firstName", "sag", True, u"starts-with"),
- ("lastName", "mor", True, u"starts-with"),
- ("lastName", "sag", True, u"starts-with"),
- ],
- "operand": "or",
- "recordType": "groups",
- "expected": None,
- "optimize": True,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"starts-with"),
- ("fullName", "sag", True, u"starts-with"),
- ("emailAddresses", "mor", True, u"starts-with"),
- ("emailAddresses", "sag", True, u"starts-with"),
- ("firstName", "mor", True, u"starts-with"),
- ("firstName", "sag", True, u"starts-with"),
- ("lastName", "mor", True, u"starts-with"),
- ("lastName", "sag", True, u"starts-with"),
- ],
- "operand": "or",
- "recordType": "groups",
- "expected": None,
- "optimize": True,
- },
- {
- "fields": [
- ("guid", "xyzzy", True, u"equals"),
- ("guid", "plugh", True, u"equals"),
- ],
- "operand": "or",
- "recordType": "groups",
- "expected": "(&(uid=*)(generateduid=*)(|(generateduid=xyzzy)(generateduid=plugh)))",
- "optimize": True,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"contains"),
- ("fullName", "sag", True, u"contains"),
- ],
- "operand": "or",
- "recordType": "locations",
- "expected": "(&(uid=*)(generateduid=*)(|(cn=*mor*)(cn=*sag*)))",
- "optimize": True,
- },
- {
- "fields": [
- ("fullName", "mor", True, u"contains"),
- ("fullName", "sag", True, u"contains"),
- ],
- "operand": "or",
- "recordType": "resources",
- "expected": "(&(uid=*)(generateduid=*)(|(cn=*mor*)(cn=*sag*)))",
- "optimize": True,
- },
- ]
- for entry in entries:
- self.assertEquals(
- buildFilter(entry["recordType"], mapping, entry["fields"],
- operand=entry["operand"], optimizeMultiName=entry["optimize"]),
- entry["expected"]
- )
-
-
- class BuildFilterFromTokensTestCase(TestCase):
-
- def test_buildFilterFromTokens(self):
-
- entries = [
- {
- "tokens": ["foo"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": "(&(a=b)(|(cn=*foo*)(mail=foo*)))",
- "extra": "(a=b)",
- },
- {
- "tokens": ["foo", "foo", "oo", "fo", "bar"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": "(&(a=b)(|(cn=*bar*)(mail=bar*))(|(cn=*foo*)(mail=foo*)))",
- "extra": "(a=b)",
- },
- {
- "tokens": ["fo", "foo", "foooo", "ooo", "fooo"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": "(&(a=b)(|(cn=*foooo*)(mail=foooo*)))",
- "extra": "(a=b)",
- },
- {
- "tokens": ["foo"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": ["mail", "mailAliases"],
- },
- "expected": "(&(a=b)(|(cn=*foo*)(mail=foo*)(mailAliases=foo*)))",
- "extra": "(a=b)",
- },
- {
- "tokens": [],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": None,
- "extra": None,
- },
- {
- "tokens": ["foo", "bar"],
- "mapping": {},
- "expected": None,
- "extra": None,
- },
- {
- "tokens": ["foo", "bar"],
- "mapping": {
- "emailAddresses": "mail",
- },
- "expected": "(&(mail=bar*)(mail=foo*))",
- "extra": None,
- },
- {
- "tokens": ["foo", "bar"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": "(&(|(cn=*bar*)(mail=bar*))(|(cn=*foo*)(mail=foo*)))",
- "extra": None,
- },
- {
- "tokens": ["foo", "bar"],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": ["mail", "mailAliases"],
- },
- "expected": "(&(|(cn=*bar*)(mail=bar*)(mailAliases=bar*))(|(cn=*foo*)(mail=foo*)(mailAliases=foo*)))",
- "extra": None,
- },
- {
- "tokens": ["foo", "bar", "baz("],
- "mapping": {
- "fullName": "cn",
- "emailAddresses": "mail",
- },
- "expected": "(&(|(cn=*bar*)(mail=bar*))(|(cn=*baz\\28*)(mail=baz\\28*))(|(cn=*foo*)(mail=foo*)))",
- "extra": None,
- },
- ]
- for entry in entries:
- self.assertEquals(
- buildFilterFromTokens(None, entry["mapping"], entry["tokens"], extra=entry["extra"]),
- entry["expected"]
- )
-
-
- 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):
- """
- A test stub which replaces search_s( ) with a version that will return
- whatever you have previously called addTestResults( ) with.
- """
-
- 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="(objectClass=*)",
- attrlist=None):
- """ A simple implementation of LDAP search filter processing """
-
- base = normalizeDNstr(base)
- results = []
- for dn, attrs in self.records:
- dn = normalizeDNstr(dn)
- if dn == base:
- results.append(("ignored", (dn, attrs)))
- elif dnContainedIn(ldap.dn.str2dn(dn), ldap.dn.str2dn(base)):
- if filterstr in ("(objectClass=*)", "(!(objectClass=organizationalUnit))"):
- results.append(("ignored", (dn, attrs)))
- else:
- trans = maketrans("&(|)", " |")
- fragments = filterstr.encode("utf-8").translate(trans).split("|")
- for fragment in fragments:
- if not fragment:
- continue
- fragment = fragment.strip()
- key, value = fragment.split("=")
- if value in attrs.get(key, []):
- results.append(("ignored", (dn, attrs)))
- break
- elif value == "*" and key in attrs:
- results.append(("ignored", (dn, attrs)))
- break
-
- return results
-
-
- class LdapDirectoryServiceTestCase(TestCase):
-
- nestedUsingDifferentAttributeUsingDN = (
- (
- (
- "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "uid=odtestamanda,cn=users,dc=example,dc=com",
- {
- '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']
- }
- ),
- (
- "uid=odtestbetty,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestbetty'],
- 'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
- 'sn': ['Test'],
- 'mail': ['odtestbetty@example.com'],
- 'givenName': ['Betty'],
- 'cn': ['Betty Test']
- }
- ),
- (
- "uid=odtestcarlene,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestcarlene'],
- # Note: no guid here, to test this record is skipped
- 'sn': ['Test'],
- 'mail': ['odtestcarlene@example.com'],
- 'givenName': ['Carlene'],
- 'cn': ['Carlene Test']
- }
- ),
- (
- "uid=cdaboo,cn=users,dc=example,dc=com",
- {
- 'uid': ['cdaboo'],
- 'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
- 'sn': ['Daboo'],
- 'mail': ['daboo@example.com'],
- 'givenName': ['Cyrus'],
- 'cn': ['Cyrus Daboo']
- }
- ),
- (
- "uid=wsanchez , cn=users , dc=example,dc=com",
- {
- 'uid': ['wsanchez'],
- 'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
- 'sn': ['Sanchez'],
- 'mail': ['wsanchez@example.com'],
- 'givenName': ['Wilfredo'],
- 'cn': ['Wilfredo Sanchez']
- }
- ),
- (
- "uid=testresource , cn=resources , dc=example,dc=com",
- {
- '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'],
- }
- ),
- (
- "uid=testresource2 , cn=resources , dc=example,dc=com",
- {
- 'uid': ['testresource2'],
- 'apple-generateduid': ['753E5A60-AFFD-45E4-BF2C-31DAB459353F'],
- 'sn': ['Resource2'],
- 'givenName': ['Test'],
- 'cn': ['Test Resource2'],
- 'read-write-proxy': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
- }
- ),
- ),
- {
- "augmentService": None,
- "groupMembershipCache": None,
- "cacheTimeout": 1, # Minutes
- "negativeCaching": False,
- "warningThresholdSeconds": 3,
- "batchSize": 500,
- "queryLocationsImplicitly": True,
- "restrictEnabledRecords": True,
- "restrictToGroup": "both_coasts",
- "recordTypes": ("users", "groups", "locations", "resources"),
- "uri": "ldap://localhost/",
- "tls": False,
- "tlsCACertFile": None,
- "tlsCACertDir": None,
- "tlsRequireCert": None, # never, allow, try, demand, hard
- "credentials": {
- "dn": None,
- "password": None,
- },
- "authMethod": "LDAP",
- "rdnSchema": {
- "base": "dc=example,dc=com",
- "guidAttr": "apple-generateduid",
- "users": {
- "rdn": "cn=Users",
- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "loginEnabledAttr": "", # attribute controlling login
- "loginEnabledValue": "yes", # "True" value of above attribute
- "calendarEnabledAttr": "enable-calendar", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "groups": {
- "rdn": "cn=Groups",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- },
- },
- "locations": {
- "rdn": "cn=Places",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "associatedAddressAttr": "assocAddr",
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": "", # old style, single string
- },
- },
- "resources": {
- "rdn": "cn=Resources",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": [], # new style, array
- },
- },
- "addresses": {
- "rdn": "cn=Buildings",
- "geoAttr": "coordinates",
- "streetAddressAttr": "postal",
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "uniqueMember", # how members are specified
- "nestedGroupsAttr": "nestedGroups", # how nested groups are specified
- "memberIdAttr": "", # which attribute the above refer to
- },
- "resourceSchema": {
- "resourceInfoAttr": "apple-resource-info", # contains location/resource info
- "autoScheduleAttr": None,
- "proxyAttr": "read-write-proxy",
- "readOnlyProxyAttr": "read-only-proxy",
- "autoAcceptGroupAttr": None,
- },
- "poddingSchema": {
- "serverIdAttr": "server-id", # maps to augments server-id
- },
- }
- )
- nestedUsingSameAttributeUsingDN = (
- (
- (
- "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "uid=odtestamanda,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestamanda'],
- 'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
- 'sn': ['Test'],
- 'mail': ['odtestamanda@example.com', 'alternate@example.com'],
- 'givenName': ['Amanda'],
- 'cn': ['Amanda Test']
- }
- ),
- (
- "uid=odtestbetty,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestbetty'],
- 'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
- 'sn': ['Test'],
- 'mail': ['odtestbetty@example.com'],
- 'givenName': ['Betty'],
- 'cn': ['Betty Test']
- }
- ),
- (
- "uid=odtestcarlene,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestcarlene'],
- # Note: no guid here, to test this record is skipped
- 'sn': ['Test'],
- 'mail': ['odtestcarlene@example.com'],
- 'givenName': ['Carlene'],
- 'cn': ['Carlene Test']
- }
- ),
- (
- "uid=cdaboo,cn=users,dc=example,dc=com",
- {
- 'uid': ['cdaboo'],
- 'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
- 'sn': ['Daboo'],
- 'mail': ['daboo@example.com'],
- 'givenName': ['Cyrus'],
- 'cn': ['Cyrus Daboo']
- }
- ),
- (
- "uid=wsanchez , cn=users , dc=example,dc=com",
- {
- 'uid': ['wsanchez'],
- 'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
- 'sn': ['Sanchez'],
- 'mail': ['wsanchez@example.com'],
- 'givenName': ['Wilfredo'],
- 'cn': ['Wilfredo Sanchez']
- }
- ),
- ),
- {
- "augmentService": None,
- "groupMembershipCache": None,
- "cacheTimeout": 1, # Minutes
- "negativeCaching": False,
- "warningThresholdSeconds": 3,
- "batchSize": 500,
- "queryLocationsImplicitly": True,
- "restrictEnabledRecords": True,
- "restrictToGroup": "both_coasts",
- "recordTypes": ("users", "groups", "locations", "resources"),
- "uri": "ldap://localhost/",
- "tls": False,
- "tlsCACertFile": None,
- "tlsCACertDir": None,
- "tlsRequireCert": None, # never, allow, try, demand, hard
- "credentials": {
- "dn": None,
- "password": None,
- },
- "authMethod": "LDAP",
- "rdnSchema": {
- "base": "dc=example,dc=com",
- "guidAttr": "apple-generateduid",
- "users": {
- "rdn": "cn=Users",
- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "loginEnabledAttr": "", # attribute controlling login
- "loginEnabledValue": "yes", # "True" value of above attribute
- "calendarEnabledAttr": "enable-calendar", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "groups": {
- "rdn": "cn=Groups",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "locations": {
- "rdn": "cn=Places",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": "", # old style, single string
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "resources": {
- "rdn": "cn=Resources",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": [], # new style, array
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "uniqueMember", # how members are specified
- "nestedGroupsAttr": "", # how nested groups are specified
- "memberIdAttr": "", # which attribute the above refer to
- },
- "resourceSchema": {
- "resourceInfoAttr": "apple-resource-info", # contains location/resource info
- "autoScheduleAttr": None,
- "proxyAttr": None,
- "readOnlyProxyAttr": None,
- "autoAcceptGroupAttr": None,
- },
- "poddingSchema": {
- "serverIdAttr": "server-id", # maps to augments server-id
- },
- }
- )
- nestedUsingDifferentAttributeUsingGUID = (
- (
- (
- "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
- {
- 'cn': ['recursive1_coasts'],
- 'apple-generateduid': ['recursive1_coasts'],
- 'uniqueMember': [
- '6423F94A-6B76-4A3A-815B-D52CFD77935D',
- ],
- 'nestedGroups': [
- 'recursive2_coasts',
- ],
- }
- ),
- (
- "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "uid=odtestamanda,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestamanda'],
- 'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
- 'sn': ['Test'],
- 'mail': ['odtestamanda@example.com', 'alternate@example.com'],
- 'givenName': ['Amanda'],
- 'cn': ['Amanda Test']
- }
- ),
- (
- "uid=odtestbetty,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestbetty'],
- 'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
- 'sn': ['Test'],
- 'mail': ['odtestbetty@example.com'],
- 'givenName': ['Betty'],
- 'cn': ['Betty Test']
- }
- ),
- (
- "uid=odtestcarlene,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestcarlene'],
- # Note: no guid here, to test this record is skipped
- 'sn': ['Test'],
- 'mail': ['odtestcarlene@example.com'],
- 'givenName': ['Carlene'],
- 'cn': ['Carlene Test']
- }
- ),
- (
- "uid=cdaboo,cn=users,dc=example,dc=com",
- {
- 'uid': ['cdaboo'],
- 'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
- 'sn': ['Daboo'],
- 'mail': ['daboo@example.com'],
- 'givenName': ['Cyrus'],
- 'cn': ['Cyrus Daboo']
- }
- ),
- (
- "uid=wsanchez , cn=users , dc=example,dc=com",
- {
- 'uid': ['wsanchez'],
- 'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
- 'sn': ['Sanchez'],
- 'mail': ['wsanchez@example.com'],
- 'givenName': ['Wilfredo'],
- 'cn': ['Wilfredo Sanchez']
- }
- ),
- ),
- {
- "augmentService": None,
- "groupMembershipCache": None,
- "cacheTimeout": 1, # Minutes
- "negativeCaching": False,
- "warningThresholdSeconds": 3,
- "batchSize": 500,
- "queryLocationsImplicitly": True,
- "restrictEnabledRecords": True,
- "restrictToGroup": "both_coasts",
- "recordTypes": ("users", "groups", "locations", "resources"),
- "uri": "ldap://localhost/",
- "tls": False,
- "tlsCACertFile": None,
- "tlsCACertDir": None,
- "tlsRequireCert": None, # never, allow, try, demand, hard
- "credentials": {
- "dn": None,
- "password": None,
- },
- "authMethod": "LDAP",
- "rdnSchema": {
- "base": "dc=example,dc=com",
- "guidAttr": "apple-generateduid",
- "users": {
- "rdn": "cn=Users",
- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "loginEnabledAttr": "", # attribute controlling login
- "loginEnabledValue": "yes", # "True" value of above attribute
- "calendarEnabledAttr": "enable-calendar", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "groups": {
- "rdn": "cn=Groups",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "locations": {
- "rdn": "cn=Places",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": "", # old style, single string
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "resources": {
- "rdn": "cn=Resources",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": [], # new style, array
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "uniqueMember", # how members are specified
- "nestedGroupsAttr": "nestedGroups", # how nested groups are specified
- "memberIdAttr": "apple-generateduid", # which attribute the above refer to
- },
- "resourceSchema": {
- "resourceInfoAttr": "apple-resource-info", # contains location/resource info
- "autoScheduleAttr": None,
- "proxyAttr": None,
- "readOnlyProxyAttr": None,
- "autoAcceptGroupAttr": None,
- },
- "poddingSchema": {
- "serverIdAttr": "server-id", # maps to augments server-id
- },
- }
- )
- nestedUsingSameAttributeUsingGUID = (
- (
- (
- "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
- {
- 'cn': ['recursive1_coasts'],
- 'apple-generateduid': ['recursive1_coasts'],
- 'uniqueMember': [
- '6423F94A-6B76-4A3A-815B-D52CFD77935D',
- 'recursive2_coasts',
- ],
- }
- ),
- (
- "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
- {
- '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',
- ],
- }
- ),
- (
- "uid=odtestamanda,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestamanda'],
- 'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
- 'sn': ['Test'],
- 'mail': ['odtestamanda@example.com', 'alternate@example.com'],
- 'givenName': ['Amanda'],
- 'cn': ['Amanda Test']
- }
- ),
- (
- "uid=odtestbetty,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestbetty'],
- 'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
- 'sn': ['Test'],
- 'mail': ['odtestbetty@example.com'],
- 'givenName': ['Betty'],
- 'cn': ['Betty Test']
- }
- ),
- (
- "uid=odtestcarlene,cn=users,dc=example,dc=com",
- {
- 'uid': ['odtestcarlene'],
- # Note: no guid here, to test this record is skipped
- 'sn': ['Test'],
- 'mail': ['odtestcarlene@example.com'],
- 'givenName': ['Carlene'],
- 'cn': ['Carlene Test']
- }
- ),
- (
- "uid=cdaboo,cn=users,dc=example,dc=com",
- {
- 'uid': ['cdaboo'],
- 'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
- 'sn': ['Daboo'],
- 'mail': ['daboo@example.com'],
- 'givenName': ['Cyrus'],
- 'cn': ['Cyrus Daboo']
- }
- ),
- (
- "uid=wsanchez , cn=users , dc=example,dc=com",
- {
- 'uid': ['wsanchez'],
- 'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
- 'sn': ['Sanchez'],
- 'mail': ['wsanchez@example.com'],
- 'givenName': ['Wilfredo'],
- 'cn': ['Wilfredo Sanchez']
- }
- ),
- ),
- {
- "augmentService": None,
- "groupMembershipCache": None,
- "cacheTimeout": 1, # Minutes
- "negativeCaching": False,
- "warningThresholdSeconds": 3,
- "batchSize": 500,
- "queryLocationsImplicitly": True,
- "restrictEnabledRecords": True,
- "restrictToGroup": "both_coasts",
- "recordTypes": ("users", "groups", "locations", "resources"),
- "uri": "ldap://localhost/",
- "tls": False,
- "tlsCACertFile": None,
- "tlsCACertDir": None,
- "tlsRequireCert": None, # never, allow, try, demand, hard
- "credentials": {
- "dn": None,
- "password": None,
- },
- "authMethod": "LDAP",
- "rdnSchema": {
- "base": "dc=example,dc=com",
- "guidAttr": "apple-generateduid",
- "users": {
- "rdn": "cn=Users",
- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "loginEnabledAttr": "", # attribute controlling login
- "loginEnabledValue": "yes", # "True" value of above attribute
- "calendarEnabledAttr": "enable-calendar", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "groups": {
- "rdn": "cn=Groups",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "", # additional filter for this type
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": ["mail", "emailAliases"],
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "locations": {
- "rdn": "cn=Places",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": "", # old style, single string
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- "resources": {
- "rdn": "cn=Resources",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": "(objectClass=apple-resource)", # additional filter for this type
- "calendarEnabledAttr": "", # attribute controlling calendaring
- "calendarEnabledValue": "yes", # "True" value of above attribute
- "mapping": { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName": "cn",
- "emailAddresses": [], # new style, array
- "firstName": "givenName",
- "lastName": "sn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "uniqueMember", # how members are specified
- "nestedGroupsAttr": "", # how nested groups are specified
- "memberIdAttr": "apple-generateduid", # which attribute the above refer to
- },
- "resourceSchema": {
- "resourceInfoAttr": "apple-resource-info", # contains location/resource info
- "autoScheduleAttr": None,
- "proxyAttr": None,
- "readOnlyProxyAttr": None,
- "autoAcceptGroupAttr": None,
- },
- "poddingSchema": {
- "serverIdAttr": "server-id", # 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, "async", StubAsync())
-
-
- def test_ldapWrapper(self):
- """
- Exercise the fake search_s implementation
- """
- self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
- # Get all groups
- self.assertEquals(
- len(self.service.ldap.search_s("cn=groups,dc=example,dc=com", 0, "(objectClass=*)", [])), 5)
-
- self.assertEquals(
- len(self.service.ldap.search_s("cn=recursive1_coasts,cn=groups,dc=example,dc=com", 2, "(objectClass=*)", [])), 1)
-
- self.assertEquals(
- len(self.service.ldap.search_s("cn=groups,dc=example,dc=com", 0, "(|(apple-generateduid=right_coast)(apple-generateduid=left_coast))", [])), 2)
-
-
- def test_ldapRecordCreation(self):
- """
- Exercise _ldapResultToRecord(), which converts a dictionary
- of LDAP attributes into an LdapDirectoryRecord
- """
- self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
- # User without enabled-for-calendaring specified
-
- dn = "uid=odtestamanda,cn=users,dc=example,dc=com"
- 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 = "uid=odtestamanda,cn=users,dc=example,dc=com"
- guid = '9DC04A70-E6DD-11DF-9492-0800200C9A66'
- attrs = {
- 'uid': ['odtestamanda'],
- 'apple-generateduid': [guid],
- 'enable-calendar': ["yes"],
- '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 "podding" info
-
- dn = "uid=odtestamanda,cn=users,dc=example,dc=com"
- guid = '9DC04A70-E6DD-11DF-9492-0800200C9A66'
- attrs = {
- 'uid': ['odtestamanda'],
- 'apple-generateduid': [guid],
- 'cn': ['Amanda Test'],
- 'server-id': ["test-server-id"],
- }
-
- record = self.service._ldapResultToRecord(
- dn, attrs, self.service.recordType_users
- )
- self.assertEquals(record.serverID, "test-server-id")
-
- # User missing guidAttr
-
- dn = "uid=odtestamanda,cn=users,dc=example,dc=com"
- attrs = {
- 'uid': ['odtestamanda'],
- 'cn': ['Amanda Test'],
- }
-
- self.assertRaises(
- MissingGuidException,
- self.service._ldapResultToRecord, dn, attrs,
- self.service.recordType_users
- )
-
- # User missing record name
-
- dn = "uid=odtestamanda,cn=users,dc=example,dc=com"
- 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 = "cn=odtestgrouptop,cn=groups,dc=example,dc=com"
- 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 = "cn=odtestgrouptop,cn=groups,dc=example,dc=com"
- 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 = "cn=odtestresource,cn=resources,dc=example,dc=com"
- guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
- attrs = {
- 'apple-generateduid': [guid],
- 'cn': ['odtestresource'],
- 'apple-resource-info': ["""<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-<key>com.apple.WhitePagesFramework</key>
-<dict>
- <key>AutoAcceptsInvitation</key>
-<true/>
-<key>CalendaringDelegate</key>
-<string>6C6CD280-E6E3-11DF-9492-0800200C9A66</string>
-<key>ReadOnlyCalendaringDelegate</key>
-<string>6AA1AE12-592F-4190-A069-547CD83C47C0</string>
-<key>AutoAcceptGroup</key>
-<string>77A8EB52-AA2A-42ED-8843-B2BEE863AC70</string>
-</dict>
-</dict>
-</plist>"""]
- }
- 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 = "cn=odtestresource,cn=resources,dc=example,dc=com"
- guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
- attrs = {
- 'apple-generateduid': [guid],
- 'cn': ['odtestresource'],
- 'apple-resource-info': ["""<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-<key>com.apple.WhitePagesFramework</key>
-<dict>
- <key>AutoAcceptsInvitation</key>
-<false/>
-</dict>
-</dict>
-</plist>"""]
- }
- 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, "")
-
- # Now switch off the resourceInfoAttr and switch to individual
- # attributes...
- self.service.resourceSchema = {
- "resourceInfoAttr": "",
- "autoScheduleAttr": "auto-schedule",
- "autoScheduleEnabledValue": "yes",
- "proxyAttr": "proxy",
- "readOnlyProxyAttr": "read-only-proxy",
- "autoAcceptGroupAttr": "auto-accept-group",
- }
-
- # Resource with delegates and autoSchedule = True
-
- dn = "cn=odtestresource,cn=resources,dc=example,dc=com"
- 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 = "uid=odtestamanda,cn=users,dc=example,dc=com"
- 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 = "cn=odtestlocation,cn=locations,dc=example,dc=com"
- guid = "D3094652-344B-4633-8DB8-09639FA00FB6"
- attrs = {
- "apple-generateduid": [guid],
- "cn": ["odtestlocation"],
- "assocAddr": ["6C6CD280-E6E3-11DF-9492-0800200C9A66"],
- }
- record = self.service._ldapResultToRecord(
- dn, attrs, self.service.recordType_locations
- )
- self.assertEquals(record.extras, {
- "associatedAddress": "6C6CD280-E6E3-11DF-9492-0800200C9A66"
- })
-
- # Address with street and geo
-
- dn = "cn=odtestaddress,cn=buildings,dc=example,dc=com"
- guid = "6C6CD280-E6E3-11DF-9492-0800200C9A66"
- attrs = {
- "apple-generateduid": [guid],
- "cn": ["odtestaddress"],
- "coordinates": ["geo:1,2"],
- "postal": ["1 Infinite Loop, Cupertino, CA"],
- }
- record = self.service._ldapResultToRecord(
- dn, attrs, self.service.recordType_addresses
- )
- self.assertEquals(record.extras, {
- "geo": "geo:1,2",
- "streetAddress": "1 Infinite Loop, Cupertino, CA",
- })
-
- def test_listRecords(self):
- """
- listRecords makes an LDAP query (with fake results in this test)
- and turns the results into records
- """
- 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(["Amanda", "Betty", "Cyrus", "Wilfredo"]) # Carlene is skipped because no guid in LDAP
- )
-
- def test_restrictedPrincipalsUsingDN(self):
- """
- 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
- """
- for scenario in (
- self.nestedUsingSameAttributeUsingDN,
- self.nestedUsingDifferentAttributeUsingDN,
- ):
- self.setupService(scenario)
-
- self.assertEquals(
- set(
- [
- "cn=left_coast,cn=groups,dc=example,dc=com",
- "cn=right_coast,cn=groups,dc=example,dc=com",
- "uid=cdaboo,cn=users,dc=example,dc=com",
- "uid=dreid,cn=users,dc=example,dc=com",
- "uid=lecroy,cn=users,dc=example,dc=com",
- "uid=wsanchez,cn=users,dc=example,dc=com",
- ]
- ),
- self.service.restrictedPrincipals)
-
- dn = "uid=cdaboo,cn=users,dc=example,dc=com"
- 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 = "uid=unknown,cn=users,dc=example,dc=com"
- 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):
- """
- 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
- """
- for scenario in (
- self.nestedUsingDifferentAttributeUsingGUID,
- self.nestedUsingSameAttributeUsingGUID,
- ):
- self.setupService(scenario)
-
- self.assertEquals(
- set(
- [
- "left_coast",
- "right_coast",
- "5A985493-EE2C-4665-94CF-4DFEA3A89500",
- "6423F94A-6B76-4A3A-815B-D52CFD77935D",
- ]
- ),
- self.service.restrictedPrincipals)
-
- dn = "uid=cdaboo,cn=users,dc=example,dc=com"
- 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 = "uid=unknown,cn=users,dc=example,dc=com"
- 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):
- """
- Exercise a directory environment where group membership does not refer
- to guids but instead uses LDAP DNs. This example uses the LDAP attribute
- "uniqueMember" 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().
- """
- self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
- # Set up proxydb and preload it from xml
- calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB("proxies.sqlite")
- yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
-
- # Set up the GroupMembershipCache
- cache = GroupMembershipCache("ProxyDB", 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 [
- ("cdaboo", set(["both_coasts", "recursive1_coasts", "recursive2_coasts"])),
- ("wsanchez", set(["both_coasts", "left_coast", "recursive1_coasts", "recursive2_coasts"])),
- ]:
-
- record = self.service.recordWithShortName(users, shortName)
- self.assertEquals(groups, (yield record.cachedGroups()))
-
-
- def test_getExternalProxyAssignments(self):
- """
- Verify getExternalProxyAssignments can extract assignments from the
- directory, and that guids are normalized.
- """
- 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 = "uid=foo,cn=USers ,dc=EXAMple,dc=com"
- self.assertEquals(self.service.recordTypeForDN(dnStr), "users")
- dnStr = "uid=foo,cn=PLaces,dc=EXAMple,dc=com"
- self.assertEquals(self.service.recordTypeForDN(dnStr), "locations")
- dnStr = "uid=foo,cn=Groups ,dc=EXAMple,dc=com"
- self.assertEquals(self.service.recordTypeForDN(dnStr), "groups")
- dnStr = "uid=foo,cn=Resources ,dc=EXAMple,dc=com"
- self.assertEquals(self.service.recordTypeForDN(dnStr), "resources")
-
- # No Match
- dnStr = "uid=foo,cn=US ers ,dc=EXAMple,dc=com"
- self.assertEquals(self.service.recordTypeForDN(dnStr), None)
-
- def test_normalizeDN(self):
- for input, expected in (
- ("uid=foo,cn=users,dc=example,dc=com",
- "uid=foo,cn=users,dc=example,dc=com"),
- ("uid=FoO,cn=uSeRs,dc=ExAmPlE,dc=CoM",
- "uid=foo,cn=users,dc=example,dc=com"),
- ("uid=FoO , cn=uS eRs , dc=ExA mPlE , dc=CoM",
- "uid=foo,cn=us ers,dc=exa mple,dc=com"),
- ("uid=FoO , cn=uS eRs , dc=ExA mPlE , dc=CoM",
- "uid=foo,cn=us ers,dc=exa mple,dc=com"),
- ):
- self.assertEquals(expected, normalizeDNstr(input))
-
- def test_queryDirectory(self):
- """
- Verify queryDirectory skips LDAP queries where there has been no
- LDAP attribute mapping provided for the given index type.
- """
- self.setupService(self.nestedUsingDifferentAttributeUsingDN)
-
- self.history = []
-
- def stubSearchMethod(base, scope, filterstr="(objectClass=*)",
- 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,
- "mailto:test@example.com",
- queryMethod=stubSearchMethod
- )
- self.assertEquals(
- self.history,
- [('cn=users,dc=example,dc=com', 2, '(&(!(objectClass=organizationalUnit))(|(mail=test@example.com)(emailAliases=test@example.com)))'), ('cn=groups,dc=example,dc=com', 2, '(&(!(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 "License");
-# 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 "AS IS" 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 = "localhost"
- base = ",".join(["dc=%s" % (p,) for p in socket.gethostname().split(".")])
- print("Using base: %s" % (base,))
-
- try:
- cxn = ldap.open(testServer)
- results = cxn.search_s(base, ldap.SCOPE_SUBTREE, "(uid=odtestamanda)",
- ["cn"])
- if len(results) == 1:
- runLDAPTests = True
- except ldap.LDAPError:
- pass # Don't run live tests
-
-except ImportError:
- print("Could not import ldap module (skipping LDAP tests)")
-
-try:
- from calendarserver.platform.darwin.od import opendirectory, dsattributes
-
- directory = opendirectory.odInit("/Search")
-
- results = list(opendirectory.queryRecordsWithAttribute_list(
- directory,
- dsattributes.kDS1AttrGeneratedUID,
- "9DC04A70-E6DD-11DF-9492-0800200C9A66",
- dsattributes.eDSExact,
- False,
- dsattributes.kDSStdRecordTypeUsers,
- None,
- count=0
- ))
- recordNames = [x[0] for x in results]
- if "odtestamanda" in recordNames:
- runODTests = True
- else:
- print("Test OD records not found (skipping OD tests)")
-
-except ImportError:
- print("Could not import OpenDirectory framework (skipping OD tests)")
-
-
-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("users", "odtestamanda")
- self.assertTrue(record is not None)
-
- def test_ldapRecordWithGUID(self):
- record = self.svc.recordWithGUID("9DC04A70-E6DD-11DF-9492-0800200C9A66")
- self.assertTrue(record is not None)
-
- @inlineCallbacks
- def test_ldapRecordsMatchingFields(self):
- fields = (
- ("firstName", "Amanda", True, "exact"),
- ("lastName", "Te", True, "starts-with"),
- )
- records = list(
- (yield self.svc.recordsMatchingFields(fields, operand="and"))
- )
- self.assertEquals(1, len(records))
- record = self.svc.recordWithGUID("9DC04A70-E6DD-11DF-9492-0800200C9A66")
- self.assertEquals(records, [record])
-
- @inlineCallbacks
- def test_restrictToGroup(self):
- self.svc.restrictEnabledRecords = True
- self.svc.restrictToGroup = "odtestgrouptop"
-
- # 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("users", "odtestamanda")
- self.assertTrue(record.enabledForCalendaring)
- self.assertTrue(record.enabledForAddressBooks)
-
- # Betty is a direct member of that group
- record = self.svc.recordWithShortName("users", "odtestbetty")
- self.assertTrue(record.enabledForCalendaring)
- self.assertTrue(record.enabledForAddressBooks)
-
- # Carlene is in a nested group
- record = self.svc.recordWithShortName("users", "odtestcarlene")
- self.assertTrue(record.enabledForCalendaring)
- self.assertTrue(record.enabledForAddressBooks)
-
- # Denise is not in the group
- record = self.svc.recordWithShortName("users", "odtestdenise")
- self.assertFalse(record.enabledForCalendaring)
- self.assertFalse(record.enabledForAddressBooks)
-
- # Searching for records using principal-property-search will not
- # yield records outside of the restrictToGroup:
-
- fields = (
- ("lastName", "Test", True, "exact"),
- )
- records = list(
- (yield self.svc.recordsMatchingFields(fields))
- )
- self.assertEquals(3, len(records))
-
- # These two are directly in the restrictToGroup:
- record = self.svc.recordWithShortName("users", "odtestamanda")
- self.assertTrue(record in records)
- record = self.svc.recordWithShortName("users", "odtestbetty")
- self.assertTrue(record in records)
- # Carlene is still picked up because she is in a nested group
- record = self.svc.recordWithShortName("users", "odtestcarlene")
- self.assertTrue(record in records)
-
- if runLDAPTests:
-
- from twistedcaldav.directory.ldapdirectory import LdapDirectoryService
- print("Running live LDAP tests against %s" % (testServer,))
-
- class LiveLDAPDirectoryServiceCase(LiveDirectoryTests, TestCase):
-
- def setUp(self):
- params = {
- "augmentService":
- augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
- "uri": "ldap://%s" % (testServer,),
- "rdnSchema": {
- "base": base,
- "guidAttr": "apple-generateduid",
- "users": {
- "rdn": "cn=users",
- "attr": "uid", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": None, # additional filter for this type
- "loginEnabledAttr" : "", # attribute controlling login
- "loginEnabledValue" : "yes", # "True" value of above attribute
- "mapping" : { # maps internal record names to LDAP
- "recordName": "uid",
- "fullName" : "cn",
- "emailAddresses" : ["mail"], # multiple LDAP fields supported
- "firstName" : "givenName",
- "lastName" : "sn",
- },
- },
- "groups": {
- "rdn": "cn=groups",
- "attr": "cn", # used only to synthesize email address
- "emailSuffix": None, # used only to synthesize email address
- "filter": None, # additional filter for this type
- "mapping" : { # maps internal record names to LDAP
- "recordName": "cn",
- "fullName" : "cn",
- "emailAddresses" : ["mail"], # multiple LDAP fields supported
- "firstName" : "givenName",
- "lastName" : "sn",
- },
- },
- },
- "groupSchema": {
- "membersAttr": "apple-group-memberguid", # how members are specified
- "nestedGroupsAttr" : "apple-group-nestedgroup", # how nested groups are specified
- "memberIdAttr": "apple-generateduid", # which attribute the above refers to
- },
- }
- self.svc = LdapDirectoryService(params)
-
- if runODTests:
-
- from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
- print("Running live OD tests")
-
- class LiveODDirectoryServiceCase(LiveDirectoryTests, TestCase):
-
- def setUp(self):
- params = {
- "augmentService":
- 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 "License");
-# 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 "AS IS" 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__), "modify")
- #configFileName = os.path.join(testRoot, "caldavd.plist")
- #config.load(configFileName)
-
- usersFile = os.path.join(testRoot, "users-groups.xml")
- 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__),
- "modify", "resources-locations.xml"))
- copyResourcesFile = FilePath(self.mktemp())
- origResourcesFile.copyTo(copyResourcesFile)
- config.ResourceService.params.xmlFile = copyResourcesFile
- config.ResourceService.Enabled = True
-
- augmentsFile = os.path.join(testRoot, "augments.xml")
- config.AugmentService.params.xmlFiles = (augmentsFile,)
-
-
- def test_createRecord(self):
- directory = getDirectory()
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record, None)
-
- directory.createRecord("resources", guid="resource01",
- shortNames=("resource01",), uid="resource01",
- emailAddresses=("res1@example.com", "res2@example.com"),
- comment="Test Comment")
-
- record = directory.recordWithUID("resource01")
- self.assertNotEquals(record, None)
-
- self.assertEquals(len(record.emailAddresses), 2)
- self.assertEquals(record.extras['comment'], "Test Comment")
-
- directory.createRecord("resources", guid="resource02", shortNames=("resource02",), uid="resource02")
-
- record = directory.recordWithUID("resource02")
- self.assertNotEquals(record, None)
-
- # Make sure old records are still there:
- record = directory.recordWithUID("resource01")
- self.assertNotEquals(record, None)
- record = directory.recordWithUID("location01")
- self.assertNotEquals(record, None)
-
-
- def test_destroyRecord(self):
- directory = getDirectory()
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record, None)
-
- directory.createRecord("resources", guid="resource01", shortNames=("resource01",), uid="resource01")
-
- record = directory.recordWithUID("resource01")
- self.assertNotEquals(record, None)
-
- directory.destroyRecord("resources", guid="resource01")
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record, None)
-
- # Make sure old records are still there:
- record = directory.recordWithUID("location01")
- self.assertNotEquals(record, None)
-
-
- def test_updateRecord(self):
- directory = getDirectory()
-
- directory.createRecord("resources", guid="resource01",
- shortNames=("resource01",), uid="resource01",
- fullName="Resource number 1")
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record.fullName, "Resource number 1")
-
- directory.updateRecord("resources", guid="resource01",
- shortNames=("resource01", "r01"), uid="resource01",
- fullName="Resource #1", firstName="First", lastName="Last",
- emailAddresses=("resource01@example.com", "r01@example.com"),
- comment="Test Comment")
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record.fullName, "Resource #1")
- self.assertEquals(record.firstName, "First")
- self.assertEquals(record.lastName, "Last")
- self.assertEquals(set(record.shortNames), set(["resource01", "r01"]))
- self.assertEquals(record.emailAddresses,
- set(["resource01@example.com", "r01@example.com"]))
- self.assertEquals(record.extras['comment'], "Test Comment")
-
- # Make sure old records are still there:
- record = directory.recordWithUID("location01")
- self.assertNotEquals(record, None)
-
-
- def test_createDuplicateRecord(self):
- directory = getDirectory()
-
- directory.createRecord("resources", guid="resource01", shortNames=("resource01",), uid="resource01")
- self.assertRaises(DirectoryError, directory.createRecord, "resources", guid="resource01", shortNames=("resource01",), uid="resource01")
-
-
- def test_missingShortNames(self):
- directory = getDirectory()
-
- directory.createRecord("resources", guid="resource01")
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record.shortNames[0], "resource01")
-
- directory.updateRecord("resources", guid="resource01",
- fullName="Resource #1")
-
- record = directory.recordWithUID("resource01")
- self.assertEquals(record.shortNames[0], "resource01")
- self.assertEquals(record.fullName, "Resource #1")
-
-
- def test_missingGUID(self):
- directory = getDirectory()
-
- record = directory.createRecord("resources")
-
- 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 "License");
-# 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 "AS IS" 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):
- """
- Stand-in for either configurable OD module, that verifies the response
- according to its '.response' attribute, set by the test.
- """
- 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
- ):
- """
- Test Open Directory directory implementation.
- """
- if not platform.isMacOSX():
- skip = "Currently, OpenDirectory backend only works on MacOS X."
- recordTypes = set((
- DirectoryService.recordType_users,
- DirectoryService.recordType_groups,
- ))
-
- users = groups = {}
-
- def setUp(self):
- super(OpenDirectory, self).setUp()
- try:
- self._service = OpenDirectoryService(
- {
- "node" : "/Search",
- "augmentService": augment.AugmentXMLDB(xmlFiles=()),
- },
- odModule=deriveValue(self, "odModule", lambda self: None)
- )
- except ImportError, e:
- raise SkipTest("OpenDirectory module is not available: %s" % (e,))
-
- def service(self):
- return self._service
-
- def test_fullNameNone(self):
- record = OpenDirectoryRecord(
- service=self.service(),
- recordType=DirectoryService.recordType_users,
- guid="B1F93EB1-DA93-4772-9141-81C250DA36C2",
- nodeName="/LDAPv2/127.0.0.1",
- shortNames=("user",),
- authIDs=set(),
- fullName=None,
- firstName="Some",
- lastName="User",
- emailAddresses=set(("someuser@example.com",)),
- memberGUIDs=[],
- nestedGUIDs=[],
- extProxies=[],
- extReadOnlyProxies=[],
- )
- self.assertEquals(record.fullName, "")
-
-
- @withSpecialValue("odModule", DigestAuthModule())
- def test_invalidODDigest(self):
- record = OpenDirectoryRecord(
- service=self.service(),
- recordType=DirectoryService.recordType_users,
- guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
- nodeName="/LDAPv2/127.0.0.1",
- shortNames=("user",),
- authIDs=set(),
- fullName="Some user",
- firstName="Some",
- lastName="User",
- emailAddresses=set(("someuser@example.com",)),
- memberGUIDs=[],
- nestedGUIDs=[],
- extProxies=[],
- extReadOnlyProxies=[],
- )
-
- digestFields = defaultdict(lambda: "...")
- digested = DigestedCredentials("user", "GET", "example.com",
- digestFields)
- od = deriveValue(self, "odModule", lambda x: None)
- od.response = "invalid"
-
- self.assertFalse(record.verifyCredentials(digested))
-
-
- @withSpecialValue("odModule", DigestAuthModule())
- def test_validODDigest(self):
- record = OpenDirectoryRecord(
- service=self.service(),
- recordType=DirectoryService.recordType_users,
- guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
- nodeName="/LDAPv2/127.0.0.1",
- shortNames=("user",),
- authIDs=set(),
- fullName="Some user",
- firstName="Some",
- lastName="User",
- emailAddresses=set(("someuser@example.com",)),
- memberGUIDs=[],
- nestedGUIDs=[],
- extProxies=[],
- extReadOnlyProxies=[],
- )
-
- digestFields = {
- "username": "user",
- "realm": "/Search",
- "nonce": "ABC",
- "uri": "/",
- "response": "123",
- "algorithm": "md5",
- }
- od = deriveValue(self, "odModule", lambda self: None)
- od.response = (
- 'Digest username="%(username)s", '
- 'realm="%(realm)s", '
- 'nonce="%(nonce)s", '
- 'uri="%(uri)s", '
- 'response="%(response)s",'
- 'algorithm=%(algorithm)s'
- ) % digestFields
-
- digested = DigestedCredentials("user", "GET", "example.com",
- digestFields)
-
- self.assertTrue(record.verifyCredentials(digested))
-
- # This should be defaulted
- del digestFields["algorithm"]
-
- self.assertTrue(record.verifyCredentials(digested))
-
- def test_queryDirectorySingleGUID(self):
- """ Test for lookup on existing and non-existing GUIDs """
-
- def lookupMethod(obj, attr, value, matchType, casei, recordTypes, attributes, count=0):
-
- data = {
- "dsRecTypeStandard:Users" : [
- {
- dsattributes.kDS1AttrGeneratedUID : "1234567890",
- dsattributes.kDSNAttrRecordName : ["user1", "User 1"],
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- ],
- "dsRecTypeStandard:Groups" : [],
- }
- results = []
- for recordType in recordTypes:
- for entry in data[recordType]:
- if entry[attr] == value:
- results.append(("", entry))
- return results
-
- recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
- self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, "1234567890", lookupMethod=lookupMethod)
- self.assertTrue(self.service().recordWithGUID("1234567890"))
- self.assertFalse(self.service().recordWithGUID("987654321"))
-
-
- def test_queryDirectoryDuplicateGUIDs(self):
- """ Test for lookup on duplicate GUIDs, ensuring they don't get
- faulted in """
-
- def lookupMethod(obj, attr, value, matchType, casei, recordType, attributes, count=0):
-
- data = [
- {
- dsattributes.kDS1AttrGeneratedUID : "1234567890",
- dsattributes.kDSNAttrRecordName : ["user1", "User 1"],
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- {
- dsattributes.kDS1AttrGeneratedUID : "1234567890",
- dsattributes.kDSNAttrRecordName : ["user2", "User 2"],
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- ]
- results = []
- for entry in data:
- if entry[attr] == value:
- results.append(("", entry))
- return results
-
- recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
- self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, "1234567890", lookupMethod=lookupMethod)
- self.assertFalse(self.service().recordWithGUID("1234567890"))
-
- def test_queryDirectoryLocalUsers(self):
- """ Test for lookup on local users, ensuring they do get
- faulted in """
-
- def lookupMethod(obj, attr, value, matchType, casei, recordTypes, attributes, count=0):
- data = {
- "dsRecTypeStandard:Users" : [
- {
- dsattributes.kDS1AttrGeneratedUID : "1234567890",
- dsattributes.kDSNAttrRecordName : ["user1", "User 1"],
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- dsattributes.kDSNAttrMetaNodeLocation : "/Local/Default",
- },
- {
- dsattributes.kDS1AttrGeneratedUID : "987654321",
- dsattributes.kDSNAttrRecordName : ["user2", "User 2"],
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- },
- ],
- "dsRecTypeStandard:Groups" : [],
- }
- results = []
- for recordType in recordTypes:
- for entry in data[recordType]:
- if entry[attr] == value:
- results.append(("", entry))
- return results
-
- recordTypes = [DirectoryService.recordType_users, DirectoryService.recordType_groups]
- self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, "1234567890", lookupMethod=lookupMethod)
- self.service().queryDirectory(recordTypes, self.service().INDEX_TYPE_GUID, "987654321", lookupMethod=lookupMethod)
- self.assertTrue(self.service().recordWithGUID("1234567890"))
- self.assertTrue(self.service().recordWithGUID("987654321"))
-
- def test_queryDirectoryEmailAddresses(self):
- """ Test to ensure we only ask for users when email address is
- part of the query """
-
- 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, "mailto:user1@example.com", lookupMethod=lookupMethod)
-
-
- @inlineCallbacks
- def test_recordsMatchingFields(self):
-
-
- def lookupMethod(obj, attribute, value, matchType, caseless,
- recordTypes, attributes):
-
- data = {
- dsattributes.kDSStdRecordTypeUsers : (
- {
- dsattributes.kDS1AttrDistinguishedName : "Morgen Sagen",
- dsattributes.kDSNAttrRecordName : "morgen",
- dsattributes.kDS1AttrFirstName : "Morgen",
- dsattributes.kDS1AttrLastName : "Sagen",
- dsattributes.kDSNAttrEMailAddress : "morgen@example.com",
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- dsattributes.kDS1AttrGeneratedUID : "83479230-821E-11DE-B6B0-DBB02C6D659D",
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- {
- dsattributes.kDS1AttrDistinguishedName : "Morgan Sagan",
- dsattributes.kDSNAttrRecordName : "morgan",
- dsattributes.kDS1AttrFirstName : "Morgan",
- dsattributes.kDS1AttrLastName : "Sagan",
- dsattributes.kDSNAttrEMailAddress : "morgan@example.com",
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- dsattributes.kDS1AttrGeneratedUID : "93479230-821E-11DE-B6B0-DBB02C6D659D",
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- {
- dsattributes.kDS1AttrDistinguishedName : "Shari Sagen",
- dsattributes.kDSNAttrRecordName : "shari",
- dsattributes.kDS1AttrFirstName : "Shari",
- dsattributes.kDS1AttrLastName : "Sagen",
- dsattributes.kDSNAttrEMailAddress : "shari@example.com",
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- dsattributes.kDS1AttrGeneratedUID : "A3479230-821E-11DE-B6B0-DBB02C6D659D",
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- {
- dsattributes.kDS1AttrDistinguishedName : "Local Morgen",
- dsattributes.kDSNAttrRecordName : "localmorgen",
- dsattributes.kDS1AttrFirstName : "Local",
- dsattributes.kDS1AttrLastName : "Morgen",
- dsattributes.kDSNAttrEMailAddress : "localmorgen@example.com",
- dsattributes.kDSNAttrMetaNodeLocation : "/Local/Default",
- dsattributes.kDS1AttrGeneratedUID : "B3479230-821E-11DE-B6B0-DBB02C6D659D",
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeUsers,
- },
- ),
- dsattributes.kDSStdRecordTypeGroups : (
- {
- dsattributes.kDS1AttrDistinguishedName : "Test Group",
- dsattributes.kDSNAttrRecordName : "testgroup",
- dsattributes.kDS1AttrFirstName : None,
- dsattributes.kDS1AttrLastName : None,
- dsattributes.kDSNAttrEMailAddress : None,
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- dsattributes.kDS1AttrGeneratedUID : "C3479230-821E-11DE-B6B0-DBB02C6D659D",
- dsattributes.kDSNAttrRecordType : dsattributes.kDSStdRecordTypeGroups,
- },
- {
- dsattributes.kDS1AttrDistinguishedName : "Morgen's Group",
- dsattributes.kDSNAttrRecordName : "morgensgroup",
- dsattributes.kDS1AttrFirstName : None,
- dsattributes.kDS1AttrLastName : None,
- dsattributes.kDSNAttrEMailAddress : None,
- dsattributes.kDSNAttrMetaNodeLocation : "/LDAPv3/127.0.0.1",
- dsattributes.kDS1AttrGeneratedUID : "D3479230-821E-11DE-B6B0-DBB02C6D659D",
- 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 = [
- ("fullName", "mor", True, u"starts-with"),
- ("emailAddresses", "mor", True, u"starts-with"),
- ("firstName", "mor", True, u"starts-with"),
- ("lastName", "mor", True, u"starts-with"),
- ]
-
- # 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="users",
- lookupMethod=lookupMethod))
- results = list(results)
- self.assertEquals(len(results), 3)
-
- # just groups
- results = (yield self.service().recordsMatchingFields(fields,
- recordType="groups",
- lookupMethod=lookupMethod))
- results = list(results)
- self.assertEquals(len(results), 1)
-
- #
- # AND
- #
- fields = [
- ("firstName", "morgen", True, u"equals"),
- ("lastName", "age", True, u"contains")
- ]
- results = (yield self.service().recordsMatchingFields(fields,
- operand="and", lookupMethod=lookupMethod))
- results = list(results)
- self.assertEquals(len(results), 1)
-
- #
- # case sensitivity
- #
- fields = [
- ("firstName", "morgen", False, u"equals"),
- ]
- results = (yield self.service().recordsMatchingFields(fields,
- lookupMethod=lookupMethod))
- results = list(results)
- self.assertEquals(len(results), 0)
-
- fields = [
- ("firstName", "morgen", True, u"equals"),
- ]
- results = (yield self.service().recordsMatchingFields(
- fields,
- lookupMethod=lookupMethod
- ))
- results = list(results)
- self.assertEquals(len(results), 1)
-
- #
- # no matches
- #
- fields = [
- ("firstName", "xyzzy", True, u"starts-with"),
- ("lastName", "plugh", True, u"contains")
- ]
- results = (yield self.service().recordsMatchingFields(
- fields,
- operand="and",
- lookupMethod=lookupMethod
- ))
- results = list(results)
- self.assertEquals(len(results), 0)
-
-
- class OpenDirectorySubset (OpenDirectory):
- """
- Test the recordTypes subset feature of Apple OpenDirectoryService.
- """
- recordTypes = set((
- DirectoryService.recordType_users,
- DirectoryService.recordType_groups,
- ))
-
- def setUp(self):
- super(OpenDirectorySubset, self).setUp()
- self._service = OpenDirectoryService(
- {
- "node" : "/Search",
- "recordTypes" : (DirectoryService.recordType_users, DirectoryService.recordType_groups),
- "augmentService" : augment.AugmentXMLDB(xmlFiles=()),
- },
- odModule=deriveValue(self, "odModule", 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 "License");
-# 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 "AS IS" 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("TEL")])
- self.assertEquals(properties, set(["408 555-1212", "415 555-1212"]))
- properties = set([prop.value() for prop in vcard.properties("EMAIL")])
- self.assertEquals(properties, set(["amanda@example.com", "second@example.com"]))
-
-
-
- class StubService(object):
- addDSAttrXProperties = False
- directoryBackedAddressBook = None
- appleInternalServer = False
- realmName = "testing"
</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"> """
</span><span class="cx"> Directory service provisioned principals.
</span><span class="cx"> """
</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("principals")
</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 = "/" + name + "/"
</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"> """
</span><span class="lines">@@ -95,57 +69,109 @@
</span><span class="cx">
</span><span class="cx"> DirectoryPrincipalResource.principalURL(),
</span><span class="cx"> """
</span><del>- for directory in self.directoryServices:
- #print("\n -> %s" % (directory.__class__.__name__,))
- provisioningResource = self.principalRootResources[directory.__class__.__name__]
</del><ins>+ provisioningResource = self.principalRootResource
</ins><span class="cx">
</span><del>- provisioningURL = "/" + directory.__class__.__name__ + "/"
- self.assertEquals(provisioningURL, provisioningResource.principalCollectionURL())
</del><ins>+ provisioningURL = "/principals/"
+ 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(" -> %s" % (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 + "/"
- self.assertEquals(typeURL, typeResource.principalCollectionURL())
</del><ins>+ typeURL = provisioningURL + recordType + "/"
+ 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 != "disabled":
- 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(" -> %s" % (shortName,))
- recordResource = typeResource.getChild(shortName)
- self.failUnless(isinstance(recordResource, DirectoryPrincipalResource))
</del><ins>+ for shortName in shortNames:
+ #print(" -> %s" % (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) + "/"
- self.assertIn(recordURL, (recordResource.principalURL(),) + tuple(recordResource.alternateURIs()))
</del><ins>+ # shortName may be non-ascii
+ recordURL = typeURL + quote(shortName.encode("utf-8")) + "/"
+ 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"> """
</span><span class="cx"> Test of a test routine...
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalProvisioningResource.principalForShortName()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalProvisioningResource.principalForUser()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalProvisioningResource.principalForAuthID()
</span><span class="cx"> """
</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], "bogus")
- 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], "bogus")
+ 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"> """
</span><span class="cx"> DirectoryPrincipalProvisioningResource.principalForUID()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalProvisioningResource.principalForRecord()
</span><span class="cx"> """
</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"> """
</span><del>- DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+ DirectoryPrincipalProvisioningResource
+ .principalForCalendarUserAddress()
</ins><span class="cx"> """
</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("principals")
</ins><span class="cx">
</span><span class="cx"> self.failUnlessIdentical(
</span><del>- provisioningResource.principalForCalendarUserAddress(
- "mailto:nocalendar@example.com"
</del><ins>+ (
+ yield provisioningResource.principalForCalendarUserAddress(
+ "mailto:nocalendar@example.com"
+ )
</ins><span class="cx"> ),
</span><span class="cx"> None
</span><span class="cx"> )
</span><span class="cx"> self.failUnlessIdentical(
</span><del>- provisioningResource.principalForCalendarUserAddress(
- "urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5"
</del><ins>+ (
+ yield provisioningResource.principalForCalendarUserAddress(
+ "urn:uuid:543D28BA-F74F-4D5F-9243-B3E3A61171E5"
+ )
</ins><span class="cx"> ),
</span><span class="cx"> None
</span><span class="cx"> )
</span><span class="cx"> self.failUnlessIdentical(
</span><del>- provisioningResource.principalForCalendarUserAddress(
- "/principals/users/nocalendar/"
</del><ins>+ (
+ yield provisioningResource.principalForCalendarUserAddress(
+ "/principals/users/nocalendar/"
+ )
</ins><span class="cx"> ),
</span><span class="cx"> None
</span><span class="cx"> )
</span><span class="cx"> self.failUnlessIdentical(
</span><del>- provisioningResource.principalForCalendarUserAddress(
- "/principals/__uids__/543D28BA-F74F-4D5F-9243-B3E3A61171E5/"
</del><ins>+ (
+ yield provisioningResource.principalForCalendarUserAddress(
+ "/principals/__uids__/"
+ "543D28BA-F74F-4D5F-9243-B3E3A61171E5/"
+ )
</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"> """
</span><del>- DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+ DirectoryPrincipalProvisioningResource
+ .principalForCalendarUserAddress()
</ins><span class="cx"> """
</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("Wrong exception type")
</span><span class="cx"> else:
</span><del>- self.fail("No exception principal: %s, property %s" % (principal, property,))
</del><ins>+ self.fail(
+ "No exception principal: %s, property %s"
+ % (principal, property,)
+ )
</ins><span class="cx">
</span><del>- if record.enabledForCalendaring:
- yield hasProperty((caldav_namespace, "calendar-home-set"))
- yield hasProperty((caldav_namespace, "calendar-user-address-set"))
- yield hasProperty((caldav_namespace, "schedule-inbox-URL"))
- yield hasProperty((caldav_namespace, "schedule-outbox-URL"))
- yield hasProperty((caldav_namespace, "calendar-user-type"))
- yield hasProperty((calendarserver_namespace, "calendar-proxy-read-for"))
- yield hasProperty((calendarserver_namespace, "calendar-proxy-write-for"))
- yield hasProperty((calendarserver_namespace, "auto-schedule"))
</del><ins>+ if record.hasCalendars:
+ yield hasProperty(
+ (caldav_namespace, "calendar-home-set")
+ )
+ yield hasProperty(
+ (caldav_namespace, "calendar-user-address-set")
+ )
+ yield hasProperty(
+ (caldav_namespace, "schedule-inbox-URL")
+ )
+ yield hasProperty(
+ (caldav_namespace, "schedule-outbox-URL")
+ )
+ yield hasProperty(
+ (caldav_namespace, "calendar-user-type")
+ )
+ yield hasProperty(
+ (calendarserver_namespace, "calendar-proxy-read-for")
+ )
+ yield hasProperty(
+ (calendarserver_namespace, "calendar-proxy-write-for")
+ )
+ # yield hasProperty(
+ # (calendarserver_namespace, "auto-schedule")
+ # )
</ins><span class="cx"> else:
</span><del>- yield doesNotHaveProperty((caldav_namespace, "calendar-home-set"))
- yield doesNotHaveProperty((caldav_namespace, "calendar-user-address-set"))
- yield doesNotHaveProperty((caldav_namespace, "schedule-inbox-URL"))
- yield doesNotHaveProperty((caldav_namespace, "schedule-outbox-URL"))
- yield doesNotHaveProperty((caldav_namespace, "calendar-user-type"))
- yield doesNotHaveProperty((calendarserver_namespace, "calendar-proxy-read-for"))
- yield doesNotHaveProperty((calendarserver_namespace, "calendar-proxy-write-for"))
- yield doesNotHaveProperty((calendarserver_namespace, "auto-schedule"))
</del><ins>+ yield doesNotHaveProperty(
+ (caldav_namespace, "calendar-home-set")
+ )
+ yield doesNotHaveProperty(
+ (caldav_namespace, "calendar-user-address-set")
+ )
+ yield doesNotHaveProperty(
+ (caldav_namespace, "schedule-inbox-URL")
+ )
+ yield doesNotHaveProperty(
+ (caldav_namespace, "schedule-outbox-URL")
+ )
+ yield doesNotHaveProperty(
+ (caldav_namespace, "calendar-user-type")
+ )
+ yield doesNotHaveProperty(
+ (calendarserver_namespace, "calendar-proxy-read-for")
+ )
+ yield doesNotHaveProperty(
+ (calendarserver_namespace, "calendar-proxy-write-for")
+ )
+ # yield doesNotHaveProperty(
+ # (calendarserver_namespace, "auto-schedule")
+ # )
</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"> """
</span><del>- DirectoryPrincipalProvisioningResource.principalForCalendarUserAddress()
</del><ins>+ DirectoryPrincipalProvisioningResource
+ .principalForCalendarUserAddress()
</ins><span class="cx"> """
</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"> """
</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"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalResource.displayName()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalResource.groupMembers()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalResource.groupMemberships()
</span><span class="cx"> """
</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, "record"))))
</del><ins>+ self.failUnless(
+ set((yield record.groups())).issubset(
+ set(
+ r.record
+ for r in memberships if hasattr(r, "record")
+ )
+ )
+ )
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_principalUID(self):
</span><span class="cx"> """
</span><span class="cx"> DirectoryPrincipalResource.principalUID()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalResource.calendarUserAddresses()
</span><span class="cx"> """
</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"> """
</span><span class="cx"> DirectoryPrincipalResource.canonicalCalendarUserAddress()
</span><span class="cx"> """
</span><del>- for _ignore_provisioningResource, _ignore_recordType, recordResource, record in self._allRecords():
- if record.enabledForCalendaring:
- self.failUnless(recordResource.canonicalCalendarUserAddress().startswith("urn:uuid:"))
</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("urn:uuid:")
+ )
+ elif self.directory.fieldName.emailAddresses in record.fields:
+ self.failUnless(
+ recordResource.canonicalCalendarUserAddress()
+ .startswith("mailto:")
+ )
+ else:
+ self.failUnless(
+ recordResource.canonicalCalendarUserAddress()
+ .startswith("/principals/__uids__/")
+ )
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_addressBookHomeURLs(self):
</span><span class="cx"> """
</span><span class="cx"> DirectoryPrincipalResource.addressBookHomeURLs(),
</span><span class="cx"> """
</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,
- "/addressbooks/",
- _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("addressbooks")
+ 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"> """
</span><span class="cx"> DirectoryPrincipalResource.calendarHomeURLs(),
</span><span class="cx"> DirectoryPrincipalResource.scheduleInboxURL(),
</span><span class="cx"> DirectoryPrincipalResource.scheduleOutboxURL()
</span><span class="cx"> """
</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,
- "/calendars/",
- _newStore
- )
</del><ins>+ # provisioningResource = DirectoryCalendarHomeProvisioningResource(
+ # self.directory,
+ # "/calendars/",
+ # _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("calendars")
+ 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"> """
</span><span class="cx"> DirectoryPrincipalResource.canAutoSchedule()
</span><span class="cx"> """
</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 ("locations", "resources") or record.uid == "cdaboo":
- 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("before", record, record.recordType, record.uid)
+ # if (
+ # recordType in (CalRecordType.location, CalRecordType.resource) or
+ # record.uid == "5A985493-EE2C-4665-94CF-4DFEA3A89500"
+ # ):
+ # record.fields[record.service.fieldName.lookupByName("autoScheduleMode")] = AutoScheduleMode.acceptIfFreeDeclineIfBusy
+ # print("modifying", 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 ("locations", "resources"):
- 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, "AllowUsers", True)
</span><del>- for _ignore_provisioningResource, recordType, recordResource, record in self._allRecords():
- if record.enabledForCalendaring:
- if recordType in ("locations", "resources") or record.uid == "cdaboo":
- 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"5A985493-EE2C-4665-94CF-4DFEA3A89500"
+ ):
+ record.fields[
+ record.service.fieldName.lookupByName(
+ "autoScheduleMode"
+ )
+ ] = 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, "Enabled", 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"> """
</span><span class="cx"> DirectoryPrincipalResource.canAutoSchedule(organizer)
</span><span class="cx"> """
</span><span class="cx">
</span><del>- # Location "apollo" has an auto-accept group ("both_coasts") set in augments.xml,
- # therefore any organizer in that group should be able to auto schedule
</del><ins>+ # Location "apollo" has an auto-accept group ("both_coasts") 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 == "apollo":
</del><ins>+ record = yield self.directory.recordWithUID(u"apollo")
</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(
+ "autoScheduleMode"
+ )
+ ] = AutoScheduleMode.none
</ins><span class="cx">
</span><del>- # Organizer in auto-accept group
- self.assertTrue(recordResource.canAutoSchedule(organizer="mailto:wsanchez@example.com"))
- # Organizer not in auto-accept group
- self.assertFalse(recordResource.canAutoSchedule(organizer="mailto:a@example.com"))
</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="mailto:wsanchez@example.com"
+ )
+ )
+ )
+ # Organizer not in auto-accept group
+ self.assertFalse(
+ (
+ yield record.canAutoSchedule(
+ organizer="mailto:a@example.com"
+ )
+ )
+ )
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def test_defaultAccessControlList_principals(self):
</span><span class="cx"> """
</span><span class="cx"> Default access controls for principals.
</span><span class="cx"> """
</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"> """
</span><span class="cx"> Default access controls for principal provisioning resources.
</span><span class="cx"> """
</span><del>- for directory in self.directoryServices:
- #print("\n -> %s" % (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(" -> %s" % (recordType,))
- typeResource = provisioningResource.getChild(recordType)
</del><ins>+ for recordType in (yield provisioningResource.listChildren()):
+ #print(" -> %s" % (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>- ("DAV:", "displayname", "morgen", "fullName", "morgen"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-type", "INDIVIDUAL", "recordType", "users"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-type", "GROUP", "recordType", "groups"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-type", "RESOURCE", "recordType", "resources"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-type", "ROOM", "recordType", "locations"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/", "guid", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "http://example.com:8008/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/", "guid", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "urn:uuid:AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "guid", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "/principals/users/example/", "recordName", "example"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "https://example.com:8443/principals/users/example/", "recordName", "example"),
- ("urn:ietf:params:xml:ns:caldav", "calendar-user-address-set", "mailto:example@example.com", "emailAddresses", "example@example.com"),
- ("http://calendarserver.org/ns/", "first-name", "morgen", "firstName", "morgen"),
- ("http://calendarserver.org/ns/", "last-name", "sagen", "lastName", "sagen"),
- ("http://calendarserver.org/ns/", "email-address-set", "example@example.com", "emailAddresses", "example@example.com"),
</del><ins>+ (
+ "DAV:", "displayname",
+ "morgen", "fullNames", "morgen"
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-type",
+ "INDIVIDUAL", "recordType", RecordType.user
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-type",
+ "GROUP", "recordType", RecordType.group
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-type",
+ "RESOURCE", "recordType", CalRecordType.resource
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-type",
+ "ROOM", "recordType", CalRecordType.location
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "/principals/__uids__/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/",
+ "uid", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "http://example.com:8008/principals/__uids__/"
+ "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/",
+ "uid", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "urn:uuid:AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA",
+ "guid", UUID("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA")
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "/principals/users/example/", "recordName", "example"
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "https://example.com:8443/principals/users/example/",
+ "recordName", "example"
+ ),
+ (
+ "urn:ietf:params:xml:ns:caldav", "calendar-user-address-set",
+ "mailto:example@example.com",
+ "emailAddresses", "example@example.com"
+ ),
+ (
+ "http://calendarserver.org/ns/", "first-name",
+ "morgen", "firstName", "morgen"
+ ),
+ (
+ "http://calendarserver.org/ns/", "last-name",
+ "sagen", "lastName", "sagen"
+ ),
+ (
+ "http://calendarserver.org/ns/", "email-address-set",
+ "example@example.com", "emailAddresses", "example@example.com"
+ ),
</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"> """
</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"> """
</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, "GET", "/")
</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("%s should have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
</del><ins>+ self.fail(
+ "%s should have %s privilege on %r"
+ % (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("%s should not have %s privilege on %r" % (principal.sname(), privilege.sname(), resource))
</del><ins>+ self.fail(
+ "%s should not have %s privilege on %r"
+ % (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 "License");
-# 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 "AS IS" 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):
- """
- Directory service provisioned principals.
- """
-
- @inlineCallbacks
- def setUp(self):
- super(ProxyPrincipals, self).setUp()
-
- self.directoryFixture.addDirectoryService(XMLDirectoryService(
- {
- 'xmlFile' : xmlFile,
- 'augmentService' :
- augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
- }
- ))
- calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB("proxies.sqlite")
-
- # Set up a principals hierarchy for each service we're testing with
- self.principalRootResources = {}
- name = self.directoryService.__class__.__name__
- url = "/" + name + "/"
-
- 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):
- """ Empty the proxy db between tests """
- 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, "userb",
- ('a',),
- True
- )
- yield self._proxyForTest(
- DirectoryService.recordType_users, "userc",
- ('a',),
- True
- )
-
-
- def test_groupMembersRegular(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_groups, "both_coasts", None,
- ("Chris Lecroy", "David Reid", "Wilfredo Sanchez", "West Coast", "East Coast", "Cyrus Daboo",),
- )
-
-
- def test_groupMembersRecursive(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_groups, "recursive1_coasts", None,
- ("Wilfredo Sanchez", "Recursive2 Coasts", "Cyrus Daboo",),
- )
-
-
- def test_groupMembersProxySingleUser(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_locations, "gemini", "calendar-proxy-write",
- ("Wilfredo Sanchez",),
- )
-
-
- def test_groupMembersProxySingleGroup(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_locations, "mercury", "calendar-proxy-write",
- ("Chris Lecroy", "David Reid", "Wilfredo Sanchez", "West Coast",),
- )
-
-
- def test_groupMembersProxySingleGroupWithNestedGroups(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_locations, "apollo", "calendar-proxy-write",
- ("Chris Lecroy", "David Reid", "Wilfredo Sanchez", "West Coast", "East Coast", "Cyrus Daboo", "Both Coasts",),
- )
-
-
- def test_groupMembersProxySingleGroupWithNestedRecursiveGroups(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- return self._groupMembersTest(
- DirectoryService.recordType_locations, "orion", "calendar-proxy-write",
- ("Wilfredo Sanchez", "Cyrus Daboo", "Recursive1 Coasts", "Recursive2 Coasts",),
- )
-
-
- def test_groupMembersProxySingleGroupWithNonCalendarGroup(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- ds = []
-
- ds.append(self._groupMembersTest(
- DirectoryService.recordType_resources, "non_calendar_proxy", "calendar-proxy-write",
- ("Chris Lecroy", "Cyrus Daboo", "Non-calendar group"),
- ))
-
- ds.append(self._groupMembershipsTest(
- DirectoryService.recordType_groups, "non_calendar_group", None,
- ("non_calendar_proxy#calendar-proxy-write",),
- ))
-
- return DeferredList(ds)
-
-
- def test_groupMembersProxyMissingUser(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- proxy = self._getPrincipalByShortName(DirectoryService.recordType_users, "cdaboo")
- proxyGroup = proxy.getChild("calendar-proxy-write")
-
- def gotMembers(members):
- members.add("12345")
- return proxyGroup._index().setGroupMembers("%s#calendar-proxy-write" % (proxy.principalUID(),), members)
-
- def check(_):
- return self._groupMembersTest(
- DirectoryService.recordType_users, "cdaboo", "calendar-proxy-write",
- (),
- )
-
- # Setup the fake entry in the DB
- d = proxyGroup._index().getMembers("%s#calendar-proxy-write" % (proxy.principalUID(),))
- d.addCallback(gotMembers)
- d.addCallback(check)
- return d
-
-
- def test_groupMembershipsMissingUser(self):
- """
- DirectoryPrincipalResource.expandedGroupMembers()
- """
- # Setup the fake entry in the DB
- fake_uid = "12345"
- proxy = self._getPrincipalByShortName(DirectoryService.recordType_users, "cdaboo")
- proxyGroup = proxy.getChild("calendar-proxy-write")
-
- def gotMembers(members):
- members.add("%s#calendar-proxy-write" % (proxy.principalUID(),))
- return proxyGroup._index().setGroupMembers("%s#calendar-proxy-write" % (fake_uid,), members)
-
- def check(_):
- return self._groupMembershipsTest(
- DirectoryService.recordType_users, "cdaboo", "calendar-proxy-write",
- (),
- )
-
- d = proxyGroup._index().getMembers("%s#calendar-proxy-write" % (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,
- "cdaboo")
-
- proxyGroup = user.getChild("calendar-proxy-write")
-
- memberdb = StubMemberDB()
-
- proxyGroup._index = (lambda: memberdb)
-
- new_members = davxml.GroupMemberSet(
- davxml.HRef.fromString(
- "/XMLDirectoryService/__uids__/8B4288F6-CC82-491D-8EF9-642EF4F3E7D0/"),
- davxml.HRef.fromString(
- "/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/"))
-
- yield proxyGroup.setGroupMemberSet(new_members, None)
-
- self.assertEquals(
- set([str(p) for p in memberdb.members]),
- set(["5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
- "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"]))
-
-
- @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, "cdaboo")
-
- proxyGroup = user.getChild("calendar-proxy-write")
-
- 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(
- "/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/")),
- None)
-
- self.assertEquals(notifier.changedCount, 1)
- finally:
- DirectoryPrincipalResource.cacheNotifierFactory = oldCacheNotifier
-
-
- def test_proxyFor(self):
-
- return self._proxyForTest(
- DirectoryService.recordType_users, "wsanchez",
- ("Mercury Seven", "Gemini Twelve", "Apollo Eleven", "Orion",),
- True
- )
-
-
- @inlineCallbacks
- def test_proxyForDuplicates(self):
-
- yield self._addProxy(
- (DirectoryService.recordType_locations, "gemini",),
- "calendar-proxy-write",
- (DirectoryService.recordType_groups, "grunts",),
- )
-
- yield self._proxyForTest(
- DirectoryService.recordType_users, "wsanchez",
- ("Mercury Seven", "Gemini Twelve", "Apollo Eleven", "Orion",),
- True
- )
-
-
- def test_readOnlyProxyFor(self):
-
- return self._proxyForTest(
- DirectoryService.recordType_users, "wsanchez",
- ("Non-calendar proxy",),
- False
- )
-
-
- @inlineCallbacks
- def test_UserProxy(self):
-
- for proxyType in ("calendar-proxy-read", "calendar-proxy-write"):
-
- yield self._addProxy(
- (DirectoryService.recordType_users, "wsanchez",),
- proxyType,
- (DirectoryService.recordType_users, "cdaboo",),
- )
-
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "wsanchez",
- proxyType,
- ("Cyrus Daboo",),
- )
-
- yield self._addProxy(
- (DirectoryService.recordType_users, "wsanchez",),
- proxyType,
- (DirectoryService.recordType_users, "lecroy",),
- )
-
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "wsanchez",
- proxyType,
- ("Cyrus Daboo", "Chris Lecroy",),
- )
-
- yield self._removeProxy(
- DirectoryService.recordType_users, "wsanchez",
- proxyType,
- DirectoryService.recordType_users, "cdaboo",
- )
-
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "wsanchez",
- proxyType,
- ("Chris Lecroy",),
- )
-
-
- @inlineCallbacks
- def test_NonAsciiProxy(self):
- """
- Ensure that principalURLs with non-ascii don't cause problems
- within CalendarUserProxyPrincipalResource
- """
-
- recordType = DirectoryService.recordType_users
- proxyType = "calendar-proxy-read"
-
- record = self.directoryService.recordWithGUID("320B73A1-46E2-4180-9563-782DFDBE1F63")
- provisioningResource = self.principalRootResources[self.directoryService.__class__.__name__]
- principal = provisioningResource.principalForRecord(record)
- proxyPrincipal = provisioningResource.principalForShortName(recordType,
- "wsanchez")
-
- 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):
- """
- getAllMembers( ) returns the unique set of guids that have been
- delegated-to directly
- """
- 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):
- """
- Delegates who are not enabledForLogin are "hidden" from the delegate lists
- (but groups *are* allowed)
- """
-
- record = self.directoryService.recordWithGUID("EC465590-E9E9-4746-ACE8-6C756A49FE4D")
-
- record.enabledForLogin = True
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
- ("Occasional Delegate", "Delegate Via Group", "Delegate Group"),
- )
-
- # Login disabled -- no longer shown as a delegate
- record.enabledForLogin = False
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
- ("Delegate Via Group", "Delegate Group"),
- )
-
- # Login re-enabled -- once again a delegate (it wasn't not removed from proxydb)
- record.enabledForLogin = True
- yield self._groupMembersTest(
- DirectoryService.recordType_users, "delegator", "calendar-proxy-write",
- ("Occasional Delegate", "Delegate Via Group", "Delegate Group"),
- )
</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 "License");
-# 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 "AS IS" 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(".", os.path.dirname(__file__), "resources")
-
- xmlFile = os.path.join(testRoot, "users-groups.xml")
- config.DirectoryService.params.xmlFile = xmlFile
-
- xmlFile = os.path.join(testRoot, "resources-locations.xml")
- config.ResourceService.params.xmlFile = xmlFile
- config.ResourceService.Enabled = True
-
- xmlFile = os.path.join(testRoot, "augments.xml")
- config.AugmentService.type = "twistedcaldav.directory.augment.AugmentXMLDB"
- 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("user01")
- self.assertNotEquals(record, None)
-
-
- def test_recordInSupplementalDirectory(self):
- directory = getDirectory()
-
- # Look up a resource, which comes out of locations/resources service
- record = directory.recordWithUID("resource01")
- self.assertNotEquals(record, None)
-
-
- def test_augments(self):
- directory = getDirectory()
-
- # Primary directory
- record = directory.recordWithUID("user01")
- self.assertEquals(record.enabled, True)
- self.assertEquals(record.enabledForCalendaring, True)
- record = directory.recordWithUID("user02")
- self.assertEquals(record.enabled, False)
- self.assertEquals(record.enabledForCalendaring, False)
-
- # Supplemental directory
- record = directory.recordWithUID("resource01")
- self.assertEquals(record.enabled, True)
- self.assertEquals(record.enabledForCalendaring, True)
- self.assertEquals(record.autoSchedule, True)
- record = directory.recordWithUID("resource02")
- 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 "License");
-# 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 "AS IS" 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):
- """
- Test the Wiki Directory Service
- """
-
- def test_enabled(self):
- service = WikiDirectoryService()
- service.realmName = "Test"
- record = WikiDirectoryRecord(service,
- WikiDirectoryService.recordType_wikis,
- "test",
- None
- )
- self.assertTrue(record.enabled)
- self.assertTrue(record.enabledForCalendaring)
-
-
- @inlineCallbacks
- def test_getWikiAccessLion(self):
- """
- XMLRPC Faults result in HTTPErrors
- """
- self.patch(config.Authentication.Wiki, "LionCompatibility", True)
-
- def successful(self, user, wiki):
- return succeed("read")
-
- def fault2(self, user, wiki):
- raise Fault(2, "Bad session")
-
- def fault12(self, user, wiki):
- raise Fault(12, "Non-existent wiki")
-
- def fault13(self, user, wiki):
- raise Fault(13, "Non-existent wiki")
-
- access = (yield getWikiAccess("user", "wiki", method=successful))
- self.assertEquals(access, "read")
-
- for (method, code) in (
- (fault2, responsecode.FORBIDDEN),
- (fault12, responsecode.NOT_FOUND),
- (fault13, responsecode.SERVICE_UNAVAILABLE),
- ):
- try:
- access = (yield getWikiAccess("user", "wiki", method=method))
- except HTTPError, e:
- self.assertEquals(e.response.code, code)
- except:
- self.fail("Incorrect exception")
- else:
- self.fail("Didn't raise exception")
-
-
- @inlineCallbacks
- def test_getWikiAccess(self):
- """
- Non-200 GET responses result in HTTPErrors
- """
- self.patch(config.Authentication.Wiki, "LionCompatibility", False)
-
- def successful(user, wiki, host=None, port=None):
- return succeed("read")
-
- def forbidden(user, wiki, host=None, port=None):
- raise WebError(403, message="Unknown user")
-
- def notFound(user, wiki, host=None, port=None):
- raise WebError(404, message="Non-existent wiki")
-
- def other(user, wiki, host=None, port=None):
- raise WebError(500, "Error")
-
- access = (yield getWikiAccess("user", "wiki", method=successful))
- self.assertEquals(access, "read")
-
- for (method, code) in (
- (forbidden, responsecode.FORBIDDEN),
- (notFound, responsecode.NOT_FOUND),
- (other, responsecode.SERVICE_UNAVAILABLE),
- ):
- try:
- access = (yield getWikiAccess("user", "wiki", method=method))
- except HTTPError, e:
- self.assertEquals(e.response.code, code)
- except:
- self.fail("Incorrect exception")
- else:
- self.fail("Didn't raise exception")
</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 "License");
-# 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 "AS IS" 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):
- """
- 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).
- """
- recordTypes = set((
- DirectoryService.recordType_users,
- DirectoryService.recordType_groups,
- DirectoryService.recordType_locations,
- DirectoryService.recordType_resources,
- DirectoryService.recordType_addresses,
- ))
-
- users = {
- "admin" : {"password": "nimda", "guid": "D11F03A0-97EA-48AF-9A6C-FAC7F3975766", "addresses": ()},
- "wsanchez" : {"password": "zehcnasw", "guid": "6423F94A-6B76-4A3A-815B-D52CFD77935D", "addresses": ("mailto:wsanchez@example.com",)},
- "cdaboo" : {"password": "oobadc", "guid": "5A985493-EE2C-4665-94CF-4DFEA3A89500", "addresses": ("mailto:cdaboo@example.com",) },
- "lecroy" : {"password": "yorcel", "guid": "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0", "addresses": ("mailto:lecroy@example.com",) },
- "dreid" : {"password": "dierd", "guid": "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "addresses": ("mailto:dreid@example.com",) },
- "nocalendar" : {"password": "radnelacon", "guid": "543D28BA-F74F-4D5F-9243-B3E3A61171E5", "addresses": ()},
- "user01" : {"password": "01user", "guid": None, "addresses": ("mailto:c4ca4238a0@example.com",)},
- "user02" : {"password": "02user", "guid": None, "addresses": ("mailto:c81e728d9d@example.com",)},
- }
-
- groups = {
- "admin" : {"password": "admin", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_groups, "managers"),)},
- "managers" : {"password": "managers", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users , "lecroy"),)},
- "grunts" : {"password": "grunts", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users , "wsanchez"),
- (DirectoryService.recordType_users , "cdaboo"),
- (DirectoryService.recordType_users , "dreid"))},
- "right_coast": {"password": "right_coast", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users , "cdaboo"),)},
- "left_coast" : {"password": "left_coast", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users , "wsanchez"),
- (DirectoryService.recordType_users , "dreid"),
- (DirectoryService.recordType_users , "lecroy"))},
- "both_coasts": {"password": "both_coasts", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_groups, "right_coast"),
- (DirectoryService.recordType_groups, "left_coast"))},
- "recursive1_coasts": {"password": "recursive1_coasts", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_groups, "recursive2_coasts"),
- (DirectoryService.recordType_users, "wsanchez"))},
- "recursive2_coasts": {"password": "recursive2_coasts", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_groups, "recursive1_coasts"),
- (DirectoryService.recordType_users, "cdaboo"))},
- "non_calendar_group": {"password": "non_calendar_group", "guid": None, "addresses": (), "members": ((DirectoryService.recordType_users , "cdaboo"),
- (DirectoryService.recordType_users , "lecroy"))},
- }
-
- locations = {
- "mercury": {"password": "mercury", "guid": None, "addresses": ("mailto:mercury@example.com",)},
- "gemini" : {"password": "gemini", "guid": None, "addresses": ("mailto:gemini@example.com",)},
- "apollo" : {"password": "apollo", "guid": None, "addresses": ("mailto:apollo@example.com",)},
- "orion" : {"password": "orion", "guid": None, "addresses": ("mailto:orion@example.com",)},
- }
-
- resources = {
- "transporter" : {"password": "transporter", "guid": None, "addresses": ("mailto:transporter@example.com",) },
- "ftlcpu" : {"password": "ftlcpu", "guid": None, "addresses": ("mailto:ftlcpu@example.com",) },
- "non_calendar_proxy" : {"password": "non_calendar_proxy", "guid": "non_calendar_proxy", "addresses": ("mailto:non_calendar_proxy@example.com",)},
- }
-
-
- def xmlFile(self):
- """
- 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}
- """
- if not hasattr(self, "_xmlFile"):
- self._xmlFile = FilePath(self.mktemp())
- xmlFile.copyTo(self._xmlFile)
- return self._xmlFile
-
-
- def augmentsFile(self):
- """
- 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}
- """
- if not hasattr(self, "_augmentsFile"):
- self._augmentsFile = FilePath(self.mktemp())
- augmentsFile.copyTo(self._augmentsFile)
- return self._augmentsFile
-
-
- def service(self):
- """
- Create an L{XMLDirectoryService} based on the contents of the paths
- returned by L{XMLFileBase.augmentsFile} and L{XMLFileBase.xmlFile}.
-
- @rtype: L{XMLDirectoryService}
- """
- 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
-):
- """
- Test XML file based directory implementation.
- """
-
- def test_changedXML(self):
- service = self.service()
-
- self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
- <user>
- <uid>admin</uid>
- <guid>admin</guid>
- <password>nimda</password>
- <name>Super User</name>
- </user>
-</accounts>
-"""
- )
- for recordType, expectedRecords in (
- (DirectoryService.recordType_users , ("admin",)),
- (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("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
- <location>
- <uid>my office</uid>
- <guid>myoffice</guid>
- <password>nimda</password>
- <name>Super User</name>
- </location>
-</accounts>
-"""
- )
- self.augmentsFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<augments>
- <record>
- <uid>myoffice</uid>
- <enable>true</enable>
- <enable-calendar>true</enable-calendar>
- <auto-schedule>true</auto-schedule>
- </record>
-</augments>
-"""
- )
- service.augmentService.refresh()
-
- for recordType, expectedRecords in (
- (DirectoryService.recordType_users , ()),
- (DirectoryService.recordType_groups , ()),
- (DirectoryService.recordType_locations , ("my office",)),
- (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, "my office").autoSchedule)
-
-
- def test_okDisableCalendar(self):
- service = self.service()
-
- self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
- <group>
- <uid>enabled</uid>
- <guid>enabled</guid>
- <password>enabled</password>
- <name>Enabled</name>
- </group>
- <group>
- <uid>disabled</uid>
- <guid>disabled</guid>
- <password>disabled</password>
- <name>Disabled</name>
- </group>
-</accounts>
-"""
- )
-
- for recordType, expectedRecords in (
- (DirectoryService.recordType_users , ()),
- (DirectoryService.recordType_groups , ("enabled", "disabled")),
- (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, "enabled").enabledForCalendaring)
- self.assertFalse(service.recordWithShortName(DirectoryService.recordType_groups, "disabled").enabledForCalendaring)
-
-
- def test_readExtras(self):
- service = self.service()
-
- self.xmlFile().open("w").write(
-"""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-<accounts realm="Test Realm">
- <location>
- <uid>my office</uid>
- <guid>myoffice</guid>
- <name>My Office</name>
- <extras>
- <comment>This is the comment</comment>
- <capacity>40</capacity>
- </extras>
- </location>
-</accounts>
-"""
- )
-
- record = service.recordWithShortName(
- DirectoryService.recordType_locations, "my office")
- self.assertEquals(record.guid, "myoffice")
- self.assertEquals(record.extras["comment"], "This is the comment")
- self.assertEquals(record.extras["capacity"], "40")
-
-
- def test_writeExtras(self):
- service = self.service()
-
- service.createRecord(DirectoryService.recordType_locations, "newguid",
- shortNames=("New office",),
- fullName="My New Office",
- address="1 Infinite Loop, Cupertino, CA",
- capacity="10",
- comment="Test comment",
- )
-
- record = service.recordWithShortName(
- DirectoryService.recordType_locations, "New office")
- self.assertEquals(record.extras["comment"], "Test comment")
- self.assertEquals(record.extras["capacity"], "10")
-
- service.updateRecord(DirectoryService.recordType_locations, "newguid",
- shortNames=("New office",),
- fullName="My Newer Office",
- address="2 Infinite Loop, Cupertino, CA",
- capacity="20",
- comment="Test comment updated",
- )
-
- record = service.recordWithShortName(
- DirectoryService.recordType_locations, "New office")
- self.assertEquals(record.fullName, "My Newer Office")
- self.assertEquals(record.extras["address"], "2 Infinite Loop, Cupertino, CA")
- self.assertEquals(record.extras["comment"], "Test comment updated")
- self.assertEquals(record.extras["capacity"], "20")
-
- service.destroyRecord(DirectoryService.recordType_locations, "newguid")
-
- record = service.recordWithShortName(
- DirectoryService.recordType_locations, "New office")
- self.assertEquals(record, None)
-
-
- def test_indexing(self):
- service = self.service()
- self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_SHORTNAME, "usera"))
- self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_CUA, "mailto:wsanchez@example.com"))
- self.assertNotEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_GUID, "9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD2"))
- self.assertNotEquals(None, service._lookupInIndex(service.recordType_locations, service.INDEX_TYPE_SHORTNAME, "orion"))
- self.assertEquals(None, service._lookupInIndex(service.recordType_users, service.INDEX_TYPE_CUA, "mailto:nobody@example.com"))
-
-
- def test_repeat(self):
- service = self.service()
- record = service.recordWithShortName(
- DirectoryService.recordType_users, "user01")
- self.assertEquals(record.fullName, "c4ca4238a0b923820dcc509a6f75849bc4c User 01")
- self.assertEquals(record.firstName, "c4ca4")
- self.assertEquals(record.lastName, "c4ca4238a User 01")
- self.assertEquals(record.emailAddresses, set(['c4ca4238a0@example.com']))
-
-
-
-class XMLFileSubset (XMLFileBase, TestCase):
- """
- Test the recordTypes subset feature of XMLFile service.
- """
- 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(("users", "groups")), 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("addresses"))
</span><del>- if record.enabledForCalendaring:
</del><ins>+ if record.hasCalendars:
</ins><span class="cx"> addresses.add("urn:uuid:%s" % (record.guid,))
</span><del>- addresses.add("/principals/__uids__/%s/" % (record.guid,))
</del><ins>+ addresses.add("/principals/__uids__/%s/" % (record.uid,))
</ins><span class="cx"> addresses.add("/principals/%s/%s/" % (record.recordType, record.shortNames[0],))
</span><span class="cx">
</span><span class="cx"> if hasattr(record.service, "recordTypePrefix"):
</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, "Resource not found")
</span><span class="cx"> returnValue(response)
</span><ins>+
+
+
+
+def formatLink(url):
+ """
+ Convert a URL string into some twisted.web.template DOM objects for
+ rendering as a link to itself.
+ """
+ return tags.a(href=url)(url)
+
+
+
+def formatLinks(urls):
+ """
+ Format a list of URL strings as a list of twisted.web.template DOM links.
+ """
+ return formatList(formatLink(link) for link in urls)
+
+
+def formatPrincipals(principals):
+ """
+ Format a list of principals into some twisted.web.template DOM objects.
+ """
+ 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, "record"):
+ return " - %s" % (principal.record.displayName,)
+ else:
+ return ""
+
+ return formatList(
+ tags.a(href=principal.principalURL())(
+ str(principal), describe(principal)
+ )
+ for principal in sorted(principals, key=recordKey)
+ )
+
+
+
+def formatList(iterable):
+ """
+ Format a list of stuff as an interable.
+ """
+ thereAreAny = False
+ try:
+ item = None
+ for item in iterable:
+ thereAreAny = True
+ yield " -> "
+ if item is None:
+ yield "None"
+ else:
+ yield item
+ yield "\n"
+ except Exception, e:
+ log.error("Exception while rendering: %s" % (e,))
+ Failure().printTraceback()
+ yield " ** %s **: %s\n" % (e.__class__.__name__, e)
+ if not thereAreAny:
+ yield " '()\n"
+
+
</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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-Directory service implementation for users who are allowed to authorize
-as other principals.
-"""
-
-__all__ = [
- "WikiDirectoryService",
-]
-
-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):
- """
- L{IDirectoryService} implementation for Wikis.
- """
- baseGUID = "D79EF1E0-9A42-11DD-AD8B-0800200C9A66"
-
- realmName = None
-
- recordType_wikis = "wikis"
-
- UIDPrefix = "wiki-"
-
-
- def __repr__(self):
- return "<%s %r>" % (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):
- """
- L{DirectoryRecord} implementation for Wikis.
- """
-
- def __init__(self, service, recordType, shortName, entry):
- super(WikiDirectoryRecord, self).__init__(
- service=service,
- recordType=recordType,
- guid=None,
- shortNames=(shortName,),
- fullName=shortName,
- enabledForCalendaring=True,
- uid="%s%s" % (WikiDirectoryService.UIDPrefix, shortName),
- )
- # Wiki enabling doesn't come from augments db, so enable here...
- self.enabled = True
-
-
-
-@inlineCallbacks
-def getWikiAccess(userID, wikiID, method=None):
- """
- Ask the wiki server we're paired with what level of access the userID has
- for the given wikiID. Possible values are "read", "write", and "admin"
- (which we treat as "write").
-
- @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"no-access"} for read-only access
-
- 2. C{b"no-access"} for read/write access
-
- 3. C{b"no-access"} for administrative access (which, for calendaring
- purposes, should be equialent to read/write)
-
- 4. C{b"no-access"} 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.
- """
- wikiConfig = config.Authentication.Wiki
- if method is None:
- if wikiConfig.LionCompatibility:
- method = Proxy(wikiConfig["URL"]).callRemote
- else:
- method = accessForUserToWiki
- try:
-
- log.debug("Looking up Wiki ACL for: user [%s], wiki [%s]" % (userID,
- wikiID))
- if wikiConfig.LionCompatibility:
- access = (yield method(wikiConfig["WikiMethod"],
- userID, wikiID))
- else:
- access = (yield method(userID, wikiID,
- host=wikiConfig.CollabHost, port=wikiConfig.CollabPort))
-
- log.debug("Wiki ACL result: user [%s], wiki [%s], access [%s]" %
- (userID, wikiID, access))
- returnValue(access)
-
- except MultiFailure, e:
- log.error("Wiki ACL error: user [%s], wiki [%s], MultiFailure [%s]" %
- (userID, wikiID, e))
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
- "\n".join([str(f) for f in e.failures])))
-
- except Fault, fault:
-
- log.debug("Wiki ACL result: user [%s], wiki [%s], FAULT [%s]" % (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("Wiki ACL error: user [%s], wiki [%s], FAULT [%s]" %
- (userID, wikiID, fault))
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
- fault.faultString))
-
- except WebError, w:
- status = int(w.status)
-
- log.debug("Wiki ACL result: user [%s], wiki [%s], status [%s]" %
- (userID, wikiID, status))
-
- if status == responsecode.FORBIDDEN: # non-existent user
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN,
- "Unknown User"))
-
- elif status == responsecode.NOT_FOUND: # non-existent wiki
- raise HTTPError(StatusResponse(responsecode.NOT_FOUND,
- "Unknown Wiki"))
-
- else:
- # Unknown fault returned from wiki server. Log the error and
- # return 503 Service Unavailable to the client.
- log.error("Wiki ACL error: user [%s], wiki [%s], status [%s]" %
- (userID, wikiID, status))
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,
- w.message))
-
-
-
-@inlineCallbacks
-def getWikiACL(resource, request):
- """
- 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.
- """
- from twistedcaldav.directory.principal import DirectoryPrincipalResource
-
- if (not hasattr(resource, "record") or
- resource.record.recordType != WikiDirectoryService.recordType_wikis):
- returnValue(None)
-
- if hasattr(request, 'wikiACL'):
- returnValue(request.wikiACL)
-
- userID = "unauthenticated"
- 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 == "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("/principals/wikis/%s/" % (wikiID,))
- ),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ),
- TwistedACLInheritable(),
- )
- )
- returnValue(request.wikiACL)
-
- elif access in ("write", "admin"):
- 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("/principals/wikis/%s/" % (wikiID,))
- ),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- davxml.Privilege(davxml.Write()),
- ),
- TwistedACLInheritable(),
- )
- )
- returnValue(request.wikiACL)
-
- else: # "no-access":
-
- if userID == "unauthenticated":
- # 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,
- "You are not allowed to access this wiki"
- )
- )
-
- except HTTPError:
- # pass through the HTTPError we might have raised above
- raise
-
- except Exception, e:
- log.error("Wiki ACL lookup failed: %s" % (e,))
- raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Wiki ACL lookup failed"))
</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 "License");
-# 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 "AS IS" 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.
-##
-
-
-"""
-XML based user/group/resource configuration file handling.
-"""
-
-__all__ = [
- "XMLAccountsParser",
-]
-
-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 = "accounts"
-ELEMENT_USER = "user"
-ELEMENT_GROUP = "group"
-ELEMENT_LOCATION = "location"
-ELEMENT_RESOURCE = "resource"
-ELEMENT_ADDRESS = "address"
-
-ELEMENT_SHORTNAME = "uid"
-ELEMENT_GUID = "guid"
-ELEMENT_PASSWORD = "password"
-ELEMENT_NAME = "name"
-ELEMENT_FIRST_NAME = "first-name"
-ELEMENT_LAST_NAME = "last-name"
-ELEMENT_EMAIL_ADDRESS = "email-address"
-ELEMENT_MEMBERS = "members"
-ELEMENT_MEMBER = "member"
-ELEMENT_EXTRAS = "extras"
-
-ATTRIBUTE_REALM = "realm"
-ATTRIBUTE_REPEAT = "repeat"
-ATTRIBUTE_RECORDTYPE = "type"
-
-VALUE_TRUE = "true"
-VALUE_FALSE = "false"
-
-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):
- """
- XML account configuration file parser.
- """
- def __repr__(self):
- return "<%s %r>" % (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("XML parse error for '%s' because: %s" % (self.xmlFile, e,))
- self._parseXML(accounts_node)
-
-
- def _parseXML(self, node):
- """
- Parse the XML root node from the accounts configuration document.
- @param node: the L{Node} to parse.
- """
- self.realm = node.get(ATTRIBUTE_REALM, "").encode("utf-8")
-
- 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("Unknown account type: %s" % (child.tag,))
-
- repeat = int(child.get(ATTRIBUTE_REPEAT, 0))
-
- principal = XMLAccountRecord(recordType)
- principal.parseXML(child)
- if repeat > 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):
- """
- Contains provision information for one user.
- """
- def __init__(self, recordType):
- """
- @param recordType: record type for directory entry.
- """
- 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):
- """
- 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.
- """
-
- # Regular expression which matches ~ followed by a number
- matchNumber = re.compile(r"(~\d+)")
-
- def expand(text, ctr):
- """
- Returns a string where ~<number> is replaced by the first <number>
- characters from the md5 hexdigest of str(ctr), e.g.::
-
- expand("~9 foo", 1)
-
- returns::
-
- "c4ca4238a foo"
-
- ...since "c4ca4238a" is the first 9 characters of::
-
- hashlib.md5(str(1)).hexdigest()
-
- If <number> is larger than 32, the hash will repeat as needed.
- """
- 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("%") != -1:
- shortNames.append(shortName % ctr)
- else:
- shortNames.append(shortName)
- if self.guid and self.guid.find("%") != -1:
- guid = self.guid % ctr
- else:
- guid = self.guid
- if self.password.find("%") != -1:
- password = self.password % ctr
- else:
- password = self.password
- if self.fullName.find("%") != -1:
- fullName = self.fullName % ctr
- else:
- fullName = self.fullName
- fullName = expand(fullName, ctr)
- if self.firstName and self.firstName.find("%") != -1:
- firstName = self.firstName % ctr
- else:
- firstName = self.firstName
- firstName = expand(firstName, ctr)
- if self.lastName and self.lastName.find("%") != -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("%") != -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("utf-8"))
- elif child.tag == ELEMENT_GUID:
- self.guid = normalizeUUID(child.text.encode("utf-8"))
- if len(self.guid) < 4:
- self.guid += "?" * (4 - len(self.guid))
- elif child.tag == ELEMENT_PASSWORD:
- self.password = child.text.encode("utf-8")
- elif child.tag == ELEMENT_NAME:
- self.fullName = child.text.encode("utf-8")
- elif child.tag == ELEMENT_FIRST_NAME:
- self.firstName = child.text.encode("utf-8")
- elif child.tag == ELEMENT_LAST_NAME:
- self.lastName = child.text.encode("utf-8")
- elif child.tag == ELEMENT_EMAIL_ADDRESS:
- self.emailAddresses.add(child.text.encode("utf-8").lower())
- elif child.tag == ELEMENT_MEMBERS:
- self._parseMembers(child, self.members)
- elif child.tag == ELEMENT_EXTRAS:
- self._parseExtras(child, self.extras)
- else:
- raise RuntimeError("Unknown account attribute: %s" % (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("utf-8")))
-
-
- def _parseExtras(self, node, addto):
- for child in node:
- addto[child.tag] = child.text.encode("utf-8")
</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 "License");
-# 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 "AS IS" 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.
-##
-
-"""
-XML based user/group/resource directory service implementation.
-"""
-
-__all__ = [
- "XMLDirectoryService",
-]
-
-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):
- """
- XML based implementation of L{IDirectoryService}.
- """
- baseGUID = "9CA8DEC5-5A17-43A9-84A8-BE77C1FB9172"
-
- realmName = None
-
- INDEX_TYPE_GUID = "guid"
- INDEX_TYPE_SHORTNAME = "shortname"
- INDEX_TYPE_CUA = "cua"
- INDEX_TYPE_AUTHID = "authid"
-
-
- def __repr__(self):
- return "<%s %r: %r>" % (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("xmlFile"))
- if type(xmlFile) is str:
- xmlFile = FilePath(xmlFile)
-
- if not xmlFile.exists():
- xmlFile.setContent("""<?xml version="1.0" encoding="utf-8"?>
-
-<accounts realm="%s">
-</accounts>
-""" % (self.realmName,))
-
- uid = -1
- if config.UserName:
- try:
- uid = pwd.getpwnam(config.UserName).pw_uid
- except KeyError:
- self.log.error("User not found: %s" % (config.UserName,))
-
- gid = -1
- if config.GroupName:
- try:
- gid = grp.getgrnam(config.GroupName).gr_gid
- except KeyError:
- self.log.error("Group not found: %s" % (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):
- """
- Create empty indexes
- """
- 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):
- """
- 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.
- """
- currentTime = time()
- if self._alwaysStat or currentTime - self._lastCheck > 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):
- """
- Index the record by GUID, shortName(s), authID(s) and CUA(s)
- """
-
- 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):
- """
- Removes a record from all indexes. Note this is only used for unit
- testing, to simulate a user being removed from the directory.
- """
- 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):
- """
- Look for an existing record of the given recordType with the key for
- the given index type. Returns None if no match.
- """
- self._accounts()
- return self.recordIndexes.get(recordType, {}).get(indexType, {}).get(key, None)
-
-
- def _initCaches(self):
- """
- Invalidates the indexes
- """
- self._lastCheck = 0
- self._initIndexes()
-
-
- def _forceReload(self):
- """
- Invalidates the indexes, re-reads the XML file and re-indexes
- """
- 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):
- """
- No-op to pacify addressbook backing.
- """
-
- def recordTypes(self):
- return self._recordTypes
-
-
- def listRecords(self, recordType):
- self._accounts()
- return self.records[recordType]
-
-
- def recordsMatchingFields(self, fields, operand="or", 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 == "and":
- 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 => no match
- return False
- # we hit on every property
- return True
- else: # "or"
- 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):
- """
- Create an XML element from principal and add it as a child of parent
- """
-
- # 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, "uid", text=value.decode("utf-8"))
- addSubElement(element, "guid", text=principal.guid)
- if principal.fullName is not None:
- addSubElement(element, "name", text=principal.fullName.decode("utf-8"))
- if principal.firstName is not None:
- addSubElement(element, "first-name", text=principal.firstName.decode("utf-8"))
- if principal.lastName is not None:
- addSubElement(element, "last-name", text=principal.lastName.decode("utf-8"))
- for value in principal.emailAddresses:
- addSubElement(element, "email-address", text=value.decode("utf-8"))
- if principal.extras:
- extrasElement = addSubElement(element, "extras")
- for key, value in principal.extras.iteritems():
- addSubElement(extrasElement, key, text=value.decode("utf-8"))
-
- return element
-
-
- def _persistRecords(self, element):
-
- def indent(elem, level=0):
- i = "\n" + level * " "
- if len(elem):
- if not elem.text or not elem.text.strip():
- elem.text = i + " "
- 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("User not found: %s" % (config.UserName,))
-
- gid = -1
- if config.GroupName:
- try:
- gid = grp.getgrnam(config.GroupName).gr_gid
- except KeyError:
- self.log.error("Group not found: %s" % (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):
- """
- 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.
- """
- 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("accounts", realm=self.realmName)
- for recType in self.recordTypes():
- for xmlPrincipal in accounts[recType].itervalues():
- if xmlPrincipal.guid == guid:
- raise DirectoryError("Duplicate guid: %s" % (guid,))
- for shortName in shortNames:
- if shortName in xmlPrincipal.shortNames:
- raise DirectoryError("Duplicate shortName: %s" %
- (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):
- """
- 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.
- """
-
- guid = normalizeUUID(guid)
-
- # Make sure latest XML records are read in
- accounts = self._forceReload()
-
- accountsElement = createElement("accounts", 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):
- """
- 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.
- """
-
- guid = normalizeUUID(guid)
-
- # Make sure latest XML records are read in
- accounts = self._forceReload()
-
- accountsElement = createElement("accounts", 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):
- """
- Create records in bulk
- """
-
- # Make sure latest XML records are read in
- accounts = self._forceReload()
-
- knownGUIDs = {}
- knownShortNames = {}
-
- accountsElement = createElement("accounts", 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["guid"]
- if guid is None:
- guid = str(uuid4())
-
- shortNames = recordData["shortNames"]
- if not shortNames:
- shortNames = (guid,)
-
- if guid in knownGUIDs:
- raise DirectoryError("Duplicate guid: %s" % (guid,))
-
- for shortName in shortNames:
- if shortName in knownShortNames:
- raise DirectoryError("Duplicate shortName: %s" %
- (shortName,))
-
- xmlPrincipal = XMLAccountRecord(recordType)
- xmlPrincipal.shortNames = shortNames
- xmlPrincipal.guid = guid
- xmlPrincipal.fullName = recordData["fullName"]
- self._addElement(accountsElement, xmlPrincipal)
-
- self._persistRecords(accountsElement)
- self._forceReload()
-
-
-
-class XMLDirectoryRecord(DirectoryRecord):
- """
- XML based implementation implementation of L{IDirectoryRecord}.
- """
- 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"> "DirectoryBackedAddressBookResource",
</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 & ~MatchFlags.NOT # can't import MatchFlags_none
</ins><span class="cx">
</span><del>-
</del><span class="cx"> class DirectoryBackedAddressBookResource (CalDAVResource):
</span><span class="cx"> """
</span><span class="cx"> Directory-backed address book
</span><span class="cx"> """
</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("Created %s" % (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>- "Configuring: {t}:{p}",
- t=config.DirectoryAddressBook.type,
- p=config.DirectoryAddressBook.params,
- )
</del><ins>+ "Setting search directory to {principalDirectory}",
+ principalDirectory=self.principalDirectory)
+ self.directory = self.principalDirectory
+ # future: instantiate another directory based on /Search/Contacts (?)
</ins><span class="cx">
</span><del>- #add self as "directoryBackedAddressBook" parameter
- params = config.DirectoryAddressBook.params.copy()
- params["directoryBackedAddressBook"] = self
-
- try:
- self.directory = directoryClass(params)
- except ImportError, e:
- log.error("Unable to set up directory address book: %s" % (e,))
- return succeed(None)
-
- return self.directory.createCache()
-
- #print ("DirectoryBackedAddressBookResource.provisionDirectory: provisioned")
-
</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( "DirectoryBackedAddressBookResource.defaultAccessControlList" )
</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("5AAD67BF-86DD-42D7-9161-6AF977E4DAA3"), self.directory.baseGUID).urn
</del><ins>+ resource_id = uuid.uuid5(uuid.UUID("5AAD67BF-86DD-42D7-9161-6AF977E4DAA3"), self.directory.guid).urn
</ins><span class="cx"> else:
</span><span class="cx"> resource_id = "tag:unknown"
</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( "DirectoryBackedAddressBookResource.isAddressBookCollection: return True" )
</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, "Service is starting up"))
</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, "Service is starting up"))
</del><ins>+ response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
+ returnValue(response)
+
+
+ @inlineCallbacks
+ def doAddressBookDirectoryQuery(self, addressBookFilter, addressBookQuery, maxResults, defaultKind=None):
+ """
+ Get vCards for a given addressBookFilter and addressBookQuery
+ """
+
+ log.debug("doAddressBookDirectoryQuery: directory={directory} addressBookFilter={addressBookFilter}, addressBookQuery={addressBookQuery}, maxResults={maxResults}",
+ directory=self.directory, addressBookFilter=addressBookFilter, addressBookQuery=addressBookQuery, maxResults=maxResults)
+ results = []
+ limited = False
+ maxQueryRecords = 0
+
+ vcardPropToRecordFieldMap = {
+ "FN": FieldName.fullNames,
+ "N": FieldName.fullNames,
+ "EMAIL": FieldName.emailAddresses,
+ "UID": FieldName.uid,
+ "ADR": (
+ CalFieldName.streetAddress,
+ CalFieldName.floor,
+ ),
+ "KIND": FieldName.recordType,
+ # LATER "X-ADDRESSBOOKSERVER-MEMBER": FieldName.membersUIDs,
+ }
+
+ propNames, expression = expressionFromABFilter(
+ addressBookFilter, vcardPropToRecordFieldMap, vCardConstantProperties
+ )
+
+ if expression:
+ if defaultKind and "KIND" 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()) & 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 "all results" query
+ while True:
+
+ log.debug("doAddressBookDirectoryQuery: expression={expression!r}, propNames={propNames}", expression=expression, propNames=propNames)
+
+ records = yield self.directory.recordsFromExpression(expression)
+ log.debug("doAddressBookDirectoryQuery: #records={n}, records={records!r}", 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("doAddressBookDirectoryQuery: vCard did not match filter:\n{vcard}", vcard=vCardResult.vCard())
+
+ #no more results
+ if not queryLimited:
+ break
+
+ # more than requested results
+ if maxResults and len(filteredResults) >= maxResults:
+ break
+
+ # more than max report results
+ if len(filteredResults) >= config.MaxQueryWithDataResults:
+ break
+
+ # more than self limit
+ if maxQueryRecords and maxRecords >= maxQueryRecords:
+ break
+
+ # try again with 2x
+ maxRecords *= 2
+ if maxQueryRecords and maxRecords > maxQueryRecords:
+ maxRecords = maxQueryRecords
+
+ results = sorted(list(filteredResults), key=lambda result: result.vCard().propertyValue("UID"))
+ limited = maxResults and len(results) >= maxResults
+
+ log.info("limited={l} #results={n}", l=limited, n=len(results))
+ returnValue((results, limited,))
+
+
+
+def propertiesInAddressBookQuery(addressBookQuery):
+ """
+ Get the vCard properties requested by a given query
+ """
+
+ etagRequested = False
+ propertyNames = []
+ if addressBookQuery.qname() == ("DAV:", "prop"):
+
+ for property in addressBookQuery.children:
+ if isinstance(property, carddavxml.AddressData):
+ for addressProperty in property.children:
+ if isinstance(addressProperty, carddavxml.Property):
+ propertyNames.append(addressProperty.attributes["name"])
+
+ elif property.qname() == ("DAV:", "getetag"):
+ # 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={}):
+ """
+ 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
+ """
+
+ def propFilterListQuery(filterAllOf, propFilters):
+
+ """
+ Create an expression for a list of prop-filter elements.
+
+ @param filterAllOf: the C{True} if parent filter test is "allof"
+ @param propFilters: the C{list} of L{ComponentFilter} elements.
+ @return: (filterProperyNames, expressions) tuple. expression==True means list all results, expression==False means no results
+ """
+
+ def combineExpressionLists(expressionList, allOf, addedExpressions):
+ """
+ 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
+ """
+ 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):
+ """
+ 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
+ """
+
+ 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""
+
+ # change types and flags
+ matchFlags &= ~MatchFlags.caseInsensitive
+ matchType = MatchType.equals
+ else:
+ matchValue = matchString.decode("utf-8")
+
+ return MatchExpression(fieldName, matchValue, matchType, matchFlags)
+
+
+ def definedExpression(defined, allOf):
+ if constant or propFilter.filter_name in ("N" , "FN", "UID", "SOURCE", "KIND",):
+ 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, "", MatchType.startsWith, matchFlags) for fieldName in searchableFields]
+ return andOrExpression(allOf, matchList)
+ '''
+
+
+ def andOrExpression(propFilterAllOf, matchList):
+ matchList = list(set(matchList))
+ if propFilterAllOf and len(matchList) > 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 ("REV" , "BDAY",):
+ rawString = matchString
+ matchString = ""
+ for c in rawString:
+ if not c in "TZ-:":
+ matchString += c
+ elif propFilter.filter_name == "GEO":
+ matchString = ",".join(matchString.split(";"))
+
+ if propFilter.filter_name in ("N" , "ADR", "ORG",):
+ # for structured properties, change into multiple strings for ds query
+ if propFilter.filter_name == "ADR":
+ #split by newline and comma
+ rawStrings = ",".join(matchString.split("\n")).split(",")
+ else:
+ #split by space
+ rawStrings = matchString.split(" ")
+
+ # 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 ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL",):
+ if textMatchElement.match_type == "equals":
+ matchType = MatchType.equals
+ elif textMatchElement.match_type == "starts-with":
+ matchType = MatchType.startsWith
+ elif textMatchElement.match_type == "ends-with":
+ matchType = MatchType.endsWith
+
+ matchList = []
+ for matchString in matchStrings:
+ matchFlags = None
+ if textMatchElement.collation == "i;unicode-casemap" and textMatchElement.negate:
+ matchFlags = MatchFlags.caseInsensitive | MatchFlags.NOT
+ elif textMatchElement.collation == "i;unicode-casemap":
+ 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(""))
+ 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 == "allof"
</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("expressionFromABFilter: expressions={q!r}", q=expressions,)
+ if isinstance(expressions, list):
+ expressions = list(set(expressions))
+ if len(expressions) > 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 == "allof"
+ if len(addressBookFilter.children):
+ properties, expression = propFilterListQuery(filterAllOf, addressBookFilter.children)
+ else:
+ expression = not filterAllOf
+
+ #log.debug("expressionFromABFilter: expression={q!r}, properties={pn}", q=expression, pn=properties)
+ return((properties, expression))
+
+
+
+class ABDirectoryQueryResult(DAVPropertyMixIn):
+ """
+ Result from ab query report or multiget on directory
+ """
+
+ def __init__(self, directoryBackedAddressBook,):
+
+ self._directoryBackedAddressBook = directoryBackedAddressBook
+ #self._vCard = None
+
+
+ def __repr__(self):
+ return "<{self.__class__.__name__}[{rn}({uid})]>".format(
+ self=self,
+ fn=self.vCard().propertyValue("FN"),
+ uid=self.vCard().propertyValue("UID")
+ )
+
+ '''
+ def __hash__(self):
+ s = "".join([
+ "{attr}:{values}".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("UID") + ".vcf"
+
+
+ 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 == "resourcetype":
+ result = davxml.ResourceType.empty #@UndefinedVariable
+ return result
+ elif name == "getetag":
+ result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
+ return result
+ elif name == "getcontenttype":
+ mimeType = MimeType('text', 'vcard', {})
+ result = davxml.GETContentType(generateContentType(mimeType))
+ return result
+ elif name == "getcontentlength":
+ result = davxml.GETContentLength.fromString(str(len(self.vCardText())))
+ return result
+ elif name == "getlastmodified":
+ if self.vCard().hasProperty("REV"):
+ modDatetime = parse_date(self.vCard().propertyValue("REV"))
+ 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 == "creationdate":
+ if self.vCard().hasProperty("REV"): # use modification date property if it exists
+ creationDatetime = parse_date(self.vCard().propertyValue("REV"))
+ else:
+ creationDatetime = datetime.datetime.utcnow()
+ result = davxml.CreationDate.fromDate(creationDatetime)
+ return result
+ elif name == "displayname":
+ # AddressBook.app uses N. Use FN or UID instead?
+ result = davxml.DisplayName.fromString(self.vCard().propertyValue("N"))
+ 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, "quota-available-bytes"),
+ (dav_namespace, "quota-used-bytes"),
+ )
+ 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 = "Bad XML: unknown value for test attribute: %s" % (testMode,)
</span><span class="cx"> log.warn(msg)
</span><span class="cx"> raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
</span><del>- operand = "and" if testMode == "allof" else "or"
</del><ins>+ operand = Operand.AND if testMode == "allof" 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("type", 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 = {
+ "starts-with": MatchType.startsWith,
+ "contains": MatchType.contains,
+ "equals": 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) >= 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, "limit"):
</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("Property %s was returned by listProperties() "
- "but does not exist for resource %s."
- % (name, self.resource))
</del><ins>+ log.error("Property {p} was returned by listProperties() "
+ "but does not exist for resource {r}.",
+ 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 == "record-type":
</span><span class="cx"> if hasattr(self, "record"):
</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, "search-token"):
</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, "limit"):
</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"> """
</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"> """
</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("urn:uuid:%s" % (guid,))
</del><ins>+ if isinstance(guid, uuid.UUID):
+ guid = unicode(guid).upper()
+ prop.setValue("urn:uuid:{guid}".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("urn:uuid:"):
</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() == "VPOLL":
</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 "urn:uuid:%s" % (guid,)
</del><ins>+ returnValue("urn:uuid:%s" % (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("urn:uuid:"):
</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"> """
</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("Error while handling REPORT body: %s" % (e,))
</del><ins>+ log.error("Error while handling REPORT body: {err}", 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("Unsupported REPORT %s for resource %s (no method %s)"
- % (encodeXMLName(namespace, name), self, method_name))
</del><ins>+ log.error("Unsupported REPORT {name} for resource {resource} (no method {method})",
+ 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__ = ["report_urn_ietf_params_xml_ns_carddav_addressbook_query"]
</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"> """
</span><span class="cx"> # Verify root element
</span><span class="cx"> if addressbook_query.qname() != (carddav_namespace, "addressbook-query"):
</span><del>- raise ValueError("{CardDAV:}addressbook-query expected as root element, not %s." % (addressbook_query.sname(),))
</del><ins>+ raise ValueError("{CardDAV:}addressbook-query expected as root element, not {elementName}.".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("addressbook-query report is not allowed on a resource outside of an address book collection %s" % (self,))
</del><ins>+ log.error("addressbook-query report is not allowed on a resource outside of an address book collection {parent}", parent=self)
</ins><span class="cx"> raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be address book collection or address book resource"))
</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("Missing resource during sync: %s" % (href,))
</del><ins>+ log.error("Missing resource during sync: {href}", 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"> """
</span><span class="cx"> """
</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="individual")
+ 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("Missing resource during sync: %s" % (vCardRecord.hRef(),))
</del><ins>+ log.error("Missing resource during sync: {href}", 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("/") + 1:])
+ if resource_name.endswith(".vcf") and len(resource_name) > 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 < 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("/") + 1:])
- if resource_name.endswith(".vcf") and len(resource_name) > 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="UID", # attributes
- ), ])
- vCardFilter = Filter(vCardFilter)
</del><ins>+ # Now determine which valid resources are readable and which are not
+ ok_resources = []
+ yield addrresource.findChildrenFaster(
+ "1",
+ 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("/") + 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, "vCard {name} is missing from address book collection {collection!r}".format(name=child_uri_name, collection=self)
+ else:
+ vcard = None
</ins><span class="cx">
</span><del>- elif directory.maxDSQueryRecords and directory.maxDSQueryRecords < 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("/") + 1:])
+ if resource_name.endswith(".vcf") and len(resource_name) > 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="UID", # 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(
- "1",
- 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("/") + 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, "vCard %s is missing from address book collection %r" % (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("/") + 1:])
- if resource_name.endswith(".vcf") and len(resource_name) > 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="UID", # 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("depth", "0")
</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("Too many matching components in addressbook-query report. Limited to %d items" % e.maxLimit())
</del><ins>+ self.log.info("Too many matching components in addressbook-query report. Limited to {limit} items", 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("Results limited by %s at %d" % resultsWereLimited),
- davxml.ResponseDescription("Results limited to %d items" % e.maxLimit()),
</del><ins>+ davxml.ResponseDescription("Results limited to {limit} items".format(limit=e.maxLimit())),
</ins><span class="cx"> ))
</span><span class="cx">
</span><span class="cx"> if not hasattr(request, "extendedLogItems"):
</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 ""
</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 ""
</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 ""
</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__ = ["multiget_common"]
</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("calendar-multiget report is not allowed on a resource outside of a calendar collection %s" % (self,))
</del><ins>+ log.error("calendar-multiget report is not allowed on a resource outside of a calendar collection {res}", res=self)
</ins><span class="cx"> raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar resource"))
</span><span class="cx"> elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><span class="cx"> if not parent.isAddressBookCollection():
</span><del>- log.error("addressbook-multiget report is not allowed on a resource outside of an address book collection %s" % (self,))
</del><ins>+ log.error("addressbook-multiget report is not allowed on a resource outside of an address book collection {res}", res=self)
</ins><span class="cx"> raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be address book resource"))
</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) > config.MaxMultigetWithDataHrefs:
</span><del>- log.error("Too many results in multiget report returning data: %d" % len(resources))
</del><ins>+ log.error("Too many resources in multiget report: {count}", 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("Invalid calendar resource during multiget: %s" %
- (href,))
</del><ins>+ log.error("Invalid calendar resource during multiget: {href}", 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("Missing resource during multiget: %s" % (href,))
</del><ins>+ log.error("Missing resource during multiget: {href}", 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("/") + 1:])
</span><span class="cx"> if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4:
</span><span class="cx"> valid_hrefs.append(href)
</span><ins>+ textMatchElement = carddavxml.TextMatch.fromString(resource_name[:-4])
+ textMatchElement.attributes["match-type"] = "equals" # do equals compare. Default is "contains"
</ins><span class="cx"> vCardFilters.append(carddavxml.PropertyFilter(
</span><del>- carddavxml.TextMatch.fromString(resource_name[:-4]),
</del><ins>+ textMatchElement,
</ins><span class="cx"> name="UID", # 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 "normal case" below
- limit = config.DirectoryAddressBook.MaxQueryResults
- directoryAddressBookLock, limited = (yield self.directory.cacheVCardsForAddressBookQuery(addressBookFilter, propertyreq, limit))
- if limited:
- log.error("Too many results in multiget report: %d" % len(resources))
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (dav_namespace, "number-of-matches-within-limits"),
- "Too many results",
- ))
- else:
- #get vCards and filter
- limit = config.DirectoryAddressBook.MaxQueryResults
- vCardRecords, limited = (yield self.directory.vCardRecordsForAddressBookQuery(addressBookFilter, propertyreq, limit))
- if limited:
- log.error("Too many results in multiget report: %d" % len(resources))
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- (dav_namespace, "number-of-matches-within-limits"),
- "Too many results",
- ))
</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("Too many results in multiget report: {count}", count=len(resources))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (dav_namespace, "number-of-matches-within-limits"),
+ "Too many results",
+ ))
</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"> """
</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 == "auto-schedule" and self.calendarsEnabled():
- autoSchedule = self.getAutoSchedule()
- returnValue(customxml.AutoSchedule("true" if autoSchedule else "false"))
</del><ins>+ # elif name == "auto-schedule" and self.calendarsEnabled():
+ # autoSchedule = self.getAutoSchedule()
+ # returnValue(customxml.AutoSchedule("true" if autoSchedule else "false"))
</ins><span class="cx">
</span><span class="cx"> elif name == "auto-schedule-mode" and self.calendarsEnabled():
</span><del>- autoScheduleMode = self.getAutoScheduleMode()
- returnValue(customxml.AutoScheduleMode(autoScheduleMode if autoScheduleMode else "default"))
</del><ins>+ autoScheduleMode = yield self.getAutoScheduleMode()
+ returnValue(customxml.AutoScheduleMode(autoScheduleMode.description if autoScheduleMode else "default"))
</ins><span class="cx">
</span><span class="cx"> elif namespace == carddav_namespace and self.addressBooksEnabled():
</span><span class="cx"> if name == "addressbook-home-set":
</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, "extendedLogItems"):
</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(), "calendar-proxy-write"))),
- 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(), "calendar-proxy-write"))),
+ 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"> """
</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 = "urn:uuid:" + 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"> """
</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 == "read":
</del><ins>+ access = (yield owner.record.accessForRecord(sharee.record))
+ if access == WikiAccessLevel.read:
</ins><span class="cx"> returnValue("read-only")
</span><del>- elif access in ("write", "admin"):
</del><ins>+ elif access == WikiAccessLevel.write:
</ins><span class="cx"> returnValue("read-write")
</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, "Only call this for a sharee resource"
</span><span class="cx"> assert self.isCalendarCollection() or self.isAddressBookCollection(), "Only call this for a address book or calendar resource"
</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 == "original" and not self._newStoreObject.ownerHome().external():
</span><span class="lines">@@ -411,7 +410,7 @@
</span><span class="cx"> """
</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"> """
</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"> "DirectoryService": {
</span><ins>+ "Enabled": True,
</ins><span class="cx"> "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
</span><span class="cx"> "params": DEFAULT_SERVICE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ "DirectoryRealmName": "",
+
</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"> "ResourceService": {
</span><del>- "Enabled" : True,
</del><ins>+ "Enabled": True,
</ins><span class="cx"> "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
</span><span class="cx"> "params": DEFAULT_RESOURCE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
</span><span class="cx"> },
</span><span class="lines">@@ -451,10 +454,6 @@
</span><span class="cx"> "Wiki": {
</span><span class="cx"> "Enabled": False,
</span><span class="cx"> "Cookie": "cc.collabd_session_guid",
</span><del>- "URL": "http://127.0.0.1:8089/RPC2",
- "UserMethod": "userForSession",
- "WikiMethod": "accessLevelForUserWikiCalendar",
- "LionCompatibility": False,
</del><span class="cx"> "CollabHost": "localhost",
</span><span class="cx"> "CollabPort": 4444,
</span><span class="cx"> },
</span><span class="lines">@@ -1016,8 +1015,6 @@
</span><span class="cx"> "Enabled": True,
</span><span class="cx"> "MemcachedPool" : "Default",
</span><span class="cx"> "UpdateSeconds" : 300,
</span><del>- "ExpireSeconds" : 86400,
- "LockSeconds" : 600,
</del><span class="cx"> "EnableUpdater" : True,
</span><span class="cx"> "UseExternalProxies" : False,
</span><span class="cx"> },
</span><span class="lines">@@ -1254,8 +1251,18 @@
</span><span class="cx"> hostname = "localhost"
</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"> """
</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"> """
</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"> """
</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"> """
</span><span class="cx"> Only read privileges allowed for managed attachments.
</span><span class="cx"> """
</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"> """
</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 == "read":
</span><span class="cx"> returnValue("read-only")
</span><span class="cx"> elif access in ("write", "admin"):
</span><span class="lines">@@ -2026,7 +2038,7 @@
</span><span class="cx"> if access in ("read-only", "read-write",):
</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, "This is a notification object, not a notification"
</span><span class="cx"> jsondata = (yield self._newStoreObject.notificationData())
</span><span class="cx"> if jsondata["notification-type"] == "invite-notification":
</span><del>- ownerPrincipal = self.principalForUID(jsondata["owner"])
</del><ins>+ ownerPrincipal = yield self.principalForUID(jsondata["owner"])
</ins><span class="cx"> ownerCN = ownerPrincipal.displayName()
</span><span class="cx"> ownerHomeURL = ownerPrincipal.calendarHomeURLs()[0] if jsondata["shared-type"] == "calendar" 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 = "urn:uuid:" + ownerPrincipal.principalUID()
</span><span class="cx">
</span><del>- shareePrincipal = self.principalForUID(jsondata["sharee"])
</del><ins>+ shareePrincipal = yield self.principalForUID(jsondata["sharee"])
</ins><span class="cx">
</span><span class="cx"> if "supported-components" in jsondata:
</span><span class="cx"> comps = jsondata["supported-components"]
</span><span class="lines">@@ -3942,10 +3954,10 @@
</span><span class="cx"> ),
</span><span class="cx"> )
</span><span class="cx"> elif jsondata["notification-type"] == "invite-reply":
</span><del>- ownerPrincipal = self.principalForUID(jsondata["owner"])
</del><ins>+ ownerPrincipal = yield self.principalForUID(jsondata["owner"])
</ins><span class="cx"> ownerHomeURL = ownerPrincipal.calendarHomeURLs()[0] if jsondata["shared-type"] == "calendar" else ownerPrincipal.addressBookHomeURLs()[0]
</span><span class="cx">
</span><del>- shareePrincipal = self.principalForUID(jsondata["sharee"])
</del><ins>+ shareePrincipal = yield self.principalForUID(jsondata["sharee"])
</ins><span class="cx">
</span><span class="cx"> # FIXME: use urn:uuid always?
</span><span class="cx"> if jsondata["shared-type"] == "calendar":
</span><span class="lines">@@ -3959,7 +3971,7 @@
</span><span class="cx"> cua = "urn:uuid:" + 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 = {"shared-type": jsondata["shared-type"]}
</span><span class="cx"> xmldata = customxml.Notification(
</span><span class="lines">@@ -3973,9 +3985,9 @@
</span><span class="cx"> customxml.InReplyTo.fromString(jsondata["in-reply-to"]),
</span><span class="cx"> customxml.InviteSummary.fromString(jsondata["summary"]) if jsondata["summary"] 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"> """
</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__), "data")
</span><span class="cx"> vcards_dir = os.path.join(data_dir, "vCards")
</span><span class="cx">
</span><ins>+
+ @inlineCallbacks
+ def setUp(self):
+ yield StoreTestCase.setUp(self)
+ self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+
+
</ins><span class="cx"> def test_multiget_some_vcards(self):
</span><span class="cx"> """
</span><span class="cx"> All vcards.
</span><span class="lines">@@ -207,7 +217,7 @@
</span><span class="cx"> </D:set>
</span><span class="cx"> </D:mkcol>
</span><span class="cx"> """
</span><del>- response = yield self.send(SimpleStoreRequest(self, "MKCOL", addressbook_uri, content=mkcol, authid="wsanchez"))
</del><ins>+ response = yield self.send(SimpleStoreRequest(self, "MKCOL", 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"> "PUT",
</span><span class="cx"> joinURL(addressbook_uri, filename + ".vcf"),
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/vcard")}),
</span><del>- authid="wsanchez"
</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"> "PUT",
</span><span class="cx"> joinURL(addressbook_uri, child.basename()),
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/vcard")}),
</span><del>- authid="wsanchez"
</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, "REPORT", addressbook_uri, authid="wsanchez")
</del><ins>+ request = SimpleStoreRequest(self, "REPORT", 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"> """
</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("MKCOL failed: %s" % (response.code,))
</span><span class="cx"> '''
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</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] != ".vcf":
</span><span class="cx"> continue
</span><del>- request = SimpleStoreRequest(self, "PUT", joinURL(addressbook_uri, child.basename()), authid="wsanchez")
</del><ins>+ request = SimpleStoreRequest(self, "PUT", 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, "REPORT", addressbook_uri, authid="wsanchez")
</del><ins>+ request = SimpleStoreRequest(self, "REPORT", 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"> """
</span><span class="cx"> Put the contents of the Holidays directory into the store.
</span><span class="cx"> """
</span><del>- record = self.directory.recordWithShortName(DirectoryService.recordType_users, "wsanchez")
</del><ins>+ record = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</ins><span class="cx"> yield self.transactionUnderTest().calendarHomeWithUID(record.uid, create=True)
</span><span class="cx"> calendar = yield self.calendarUnderTest(name="calendar", 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"> """
</span><span class="cx">
</span><span class="cx"> self.patch(config, "MaxQueryWithDataResults", 1)
</span><ins>+
</ins><span class="cx"> def _restoreValueOK(f):
</span><span class="cx"> self.fail("REPORT must fail with 403")
</span><span class="cx">
</span><span class="lines">@@ -268,6 +269,7 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> self.patch(config, "MaxQueryWithDataResults", 1)
</span><ins>+
</ins><span class="cx"> def _restoreValueError(f):
</span><span class="cx"> self.fail("REPORT must not fail with 403")
</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, "REPORT", "/calendars/users/wsanchez/calendar/", authid="wsanchez")
</del><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+ request = SimpleStoreRequest(self, "REPORT", "/calendars/users/wsanchez/calendar/", 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"> """
</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 , "doImplicitScheduling",
</del><ins>+ self.patch(CalendarObject, "doImplicitScheduling",
</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"> """
</span><span class="cx"> Make (regular) collection in calendar
</span><span class="cx"> """
</span><span class="cx"> calendar_uri = "/calendars/users/wsanchez/collection_in_calendar/"
</span><span class="cx">
</span><del>- def mkcalendar_cb(response):
- response = IResponse(response)
-
- if response.code != responsecode.CREATED:
- self.fail("MKCALENDAR failed: %s" % (response.code,))
-
- def mkcol_cb(response):
- response = IResponse(response)
-
- if response.code != responsecode.FORBIDDEN:
- self.fail("Incorrect response to nested MKCOL: %s" % (response.code,))
-
</del><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authRecord=authRecord)
+ response = yield self.send(request)
+ response = IResponse(response)
+ if response.code != responsecode.CREATED:
+ self.fail("MKCALENDAR failed: %s" % (response.code,))
</ins><span class="cx"> nested_uri = joinURL(calendar_uri, "nested")
</span><span class="cx">
</span><del>- request = SimpleStoreRequest(self, "MKCOL", nested_uri, authid="wsanchez")
- return self.send(request, mkcol_cb)
</del><ins>+ request = SimpleStoreRequest(self, "MKCOL", nested_uri, authRecord=authRecord)
+ response = yield self.send(request)
+ response = IResponse(response)
</ins><span class="cx">
</span><del>- request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
- return self.send(request, mkcalendar_cb)
</del><ins>+ if response.code != responsecode.FORBIDDEN:
+ self.fail("Incorrect response to nested MKCOL: %s" % (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"> """
</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"> """
</span><span class="cx"> calendar_uri = "/calendars/users/wsanchez/testing_calendar/"
</span><span class="cx">
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authRecord=authRecord)
+ response = yield self.send(request)
+ response = IResponse(response)
+ if response.code != responsecode.CREATED:
+ self.fail("MKCALENDAR failed: %s" % (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, "dst%d.ics" % (c,))
+ request = SimpleStoreRequest(self, "PUT", dst_uri, authRecord=authRecord)
+ request.headers.setHeader("if-none-match", "*")
+ request.headers.setHeader("content-type", MimeType("text", "calendar"))
+ 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("MKCALENDAR failed: %s" % (response.code,))
</del><ins>+ if response.code != response_code:
+ self.fail("Incorrect response to %s: %s (!= %s)" % (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, "dst%d.ics" % (c,))
- request = SimpleStoreRequest(self, "PUT", dst_uri, authid="wsanchez")
- request.headers.setHeader("if-none-match", "*")
- request.headers.setHeader("content-type", MimeType("text", "calendar"))
- request.stream = stream
- response = yield self.send(request)
- response = IResponse(response)
</del><span class="cx">
</span><del>- if response.code != response_code:
- self.fail("Incorrect response to %s: %s (!= %s)" % (what, response.code, response_code))
-
- c += 1
-
- request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
- 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"> """
</span><span class="cx"> Make (regular) collection in calendar
</span><span class="cx"> """
</span><span class="cx"> calendar_uri = "/calendars/users/wsanchez/dot_file_in_calendar/"
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+ request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authRecord=authRecord)
+ response = yield self.send(request)
+ response = IResponse(response)
+ if response.code != responsecode.CREATED:
+ self.fail("MKCALENDAR failed: %s" % (response.code,))
</ins><span class="cx">
</span><del>- def mkcalendar_cb(response):
- response = IResponse(response)
</del><ins>+ stream = self.dataPath.child(
+ "Holidays").child(
+ "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"
+ ).open()
+ try:
+ calendar = str(Component.fromStream(stream))
+ finally:
+ stream.close()
</ins><span class="cx">
</span><del>- if response.code != responsecode.CREATED:
- self.fail("MKCALENDAR failed: %s" % (response.code,))
</del><ins>+ event_uri = "/".join([calendar_uri, ".event.ics"])
</ins><span class="cx">
</span><del>- def put_cb(response):
- response = IResponse(response)
-
- if response.code != responsecode.FORBIDDEN:
- self.fail("Incorrect response to dot file PUT: %s" % (response.code,))
-
- stream = self.dataPath.child(
- "Holidays").child(
- "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"
- ).open()
- try:
- calendar = str(Component.fromStream(stream))
- finally:
- stream.close()
-
- event_uri = "/".join([calendar_uri, ".event.ics"])
-
- request = SimpleStoreRequest(self, "PUT", event_uri, authid="wsanchez")
- request.headers.setHeader("content-type", MimeType("text", "calendar"))
- request.stream = MemoryStream(calendar)
- return self.send(request, put_cb)
-
- request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authid="wsanchez")
- return self.send(request, mkcalendar_cb)
</del><ins>+ request = SimpleStoreRequest(self, "PUT", event_uri, authRecord=authRecord)
+ request.headers.setHeader("content-type", MimeType("text", "calendar"))
+ request.stream = MemoryStream(calendar)
+ response = yield self.send(request)
+ response = IResponse(response)
+ if response.code != responsecode.FORBIDDEN:
+ self.fail("Incorrect response to dot file PUT: %s" % (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 ("ServerHostName", "Notifications", "MultiProcess",
- "Postgres"):
- # Value is calculated and may vary
</del><ins>+ if key in (
+ "ServerHostName", "Notifications", "MultiProcess",
+ "Postgres", "DirectoryRealmName"
+ ): # 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"> """
</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"> """
</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 {
- "urn:uuid:foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo", "http://example.com/foo", "/foo")
- ),
- "urn:uuid:bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar", "mailto:bar@example.com", "http://example.com/bar", "/bar")
- ),
- "urn:uuid:baz" : (
- "Baz",
- "baz",
- ("urn:uuid:baz", "http://example.com/baz")
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "urn:uuid:foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo", "http://example.com/foo", "/foo")
+ ),
+ "urn:uuid:bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar", "mailto:bar@example.com", "http://example.com/bar", "/bar")
+ ),
+ "urn:uuid:baz" : (
+ "Baz",
+ "baz",
+ ("urn:uuid:baz", "http://example.com/baz")
+ ),
+ }[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("mailto:bar@example.com",
</span><span class="cx"> component.getAttendeeProperty(("mailto:bar@example.com",)).value())
</span><span class="lines">@@ -7548,6 +7554,7 @@
</span><span class="cx"> component.getAttendeeProperty(("http://example.com/baz",)).value())
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_normalizeCalendarUserAddressesAndLocationChange(self):
</span><span class="cx"> """
</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 {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[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("LOCATION")
</span><span class="lines">@@ -7601,6 +7610,7 @@
</span><span class="cx"> self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_normalizeCalendarUserAddressesAndLocationNoChange(self):
</span><span class="cx"> """
</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 {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[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("LOCATION")
</span><span class="lines">@@ -7654,6 +7666,7 @@
</span><span class="cx"> self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def test_normalizeCalendarUserAddressesAndLocationNoChangeOtherCUType(self):
</span><span class="cx"> """
</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 {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[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("LOCATION")
</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"> """
</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 {
- "urn:uuid:foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo", "http://example.com/foo", "/foo")
- ),
- "urn:uuid:bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar", "mailto:bar@example.com", "http://example.com/bar", "/bar")
- ),
- "urn:uuid:baz" : (
- "Baz",
- "baz",
- ("urn:uuid:baz", "http://example.com/baz")
- ),
- "urn:uuid:buz" : (
- "Buz",
- "buz",
- ("urn:uuid:buz",)
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "urn:uuid:foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo", "http://example.com/foo", "/foo")
+ ),
+ "urn:uuid:bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar", "mailto:bar@example.com", "http://example.com/bar", "/bar")
+ ),
+ "urn:uuid:baz" : (
+ "Baz",
+ "baz",
+ ("urn:uuid:baz", "http://example.com/baz")
+ ),
+ "urn:uuid:buz" : (
+ "Buz",
+ "buz",
+ ("urn:uuid:buz",)
+ ),
+ }[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"> """
</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 {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/buz" : (
- "Buz",
- "buz",
- ("urn:uuid:buz",)
- ),
- }[cuaddr]
</del><ins>+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/buz" : (
+ "Buz",
+ "buz",
+ ("urn:uuid:buz",)
+ ),
+ }[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"> """
</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"user01")
+
+
</ins><span class="cx"> def test_make_calendar(self):
</span><span class="cx"> """
</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, "MKCALENDAR", uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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, "MKCALENDAR", uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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, "MKCALENDAR", uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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, "nested")
</span><span class="cx">
</span><del>- request = SimpleStoreRequest(self, "MKCALENDAR", nested_uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", nested_uri, authRecord=self.authRecord)
</ins><span class="cx"> yield self.send(request, do_test)
</span><span class="cx">
</span><del>- request = SimpleStoreRequest(self, "MKCALENDAR", first_uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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__), "data")
</span><span class="cx"> holidays_dir = os.path.join(data_dir, "Holidays")
</span><span class="cx">
</span><ins>+ @inlineCallbacks
+ def setUp(self):
+ yield StoreTestCase.setUp(self)
+ self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
+
+
</ins><span class="cx"> def test_multiget_some_events(self):
</span><span class="cx"> """
</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, "MKCALENDAR", calendar_uri, authid="wsanchez"))
</del><ins>+ response = yield self.send(SimpleStoreRequest(self, "MKCALENDAR", 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("MKCALENDAR failed: %s" % (response.code,))
</span><span class="lines">@@ -274,7 +281,7 @@
</span><span class="cx"> "PUT",
</span><span class="cx"> joinURL(calendar_uri, filename + ".ics"),
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/calendar")}),
</span><del>- authid="wsanchez"
</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"> "PUT",
</span><span class="cx"> joinURL(calendar_uri, child.basename()),
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/calendar")}),
</span><del>- authid="wsanchez"
</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, "REPORT", calendar_uri, authid="wsanchez")
</del><ins>+ request = SimpleStoreRequest(self, "REPORT", 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"> """
</span><span class="cx"> CalDAV properties
</span><span class="cx"> """
</span><ins>+
+ @inlineCallbacks
+ def setUp(self):
+ yield StoreTestCase.setUp(self)
+ self.authRecord = yield self.directory.recordWithShortName(RecordType.user, u"user01")
+
+
</ins><span class="cx"> def test_live_props(self):
</span><span class="cx"> """
</span><span class="cx"> Live CalDAV properties
</span><span class="cx"> """
</span><span class="cx"> calendar_uri = "/calendars/users/user01/test/"
</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"> "PROPFIND",
</span><span class="cx"> calendar_uri,
</span><span class="cx"> headers=http_headers.Headers({"Depth": "0"}),
</span><del>- authid="user01",
</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, "MKCALENDAR", calendar_uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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"> "PROPFIND",
</span><span class="cx"> calendar_uri,
</span><span class="cx"> headers=http_headers.Headers({"Depth": "0"}),
</span><del>- authid="user01",
</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, "MKCALENDAR", calendar_uri, authid="user01")
</del><ins>+ request = SimpleStoreRequest(self, "MKCALENDAR", 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 "StubQnamespace", "StubQname"
</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"wsanchez")
+
+
+ @inlineCallbacks
</ins><span class="cx"> def test_pick_default_addressbook(self):
</span><span class="cx"> """
</span><span class="cx"> Get adbk
</span><span class="cx"> """
</span><span class="cx">
</span><del>- request = SimpleStoreRequest(self, "GET", "/addressbooks/users/wsanchez/", authid="wsanchez")
</del><ins>+ request = SimpleStoreRequest(self, "GET", "/addressbooks/users/wsanchez/", authRecord=self.authRecord)
</ins><span class="cx"> home = yield request.locateResource("/addressbooks/users/wsanchez")
</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"> """
</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("wiki-"):
- 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, "Enabled", True)
</span><span class="cx"> self.patch(config.Sharing.Calendars, "Enabled", True)
</span><ins>+ self.patch(config.Authentication.Wiki, "Enabled", 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):
- """
- The decorated method is patched on L{CalDAVResource} for the
- duration of the test.
- """
- 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 "bogus" in cuaddr:
- return None
- else:
- return FakePrincipal(cuaddr, self)
</del><ins>+ # def patched(c):
+ # """
+ # The decorated method is patched on L{CalDAVResource} for the
+ # duration of the test.
+ # """
+ # self.patch(CalDAVResource, c.__name__, c)
+ # return c
</ins><span class="cx">
</span><del>- @patched
- def validUserIDForShare(resourceSelf, userid, request):
- """
- Temporary replacement for L{CalDAVResource.validUserIDForShare}
- that marks any principal without 'bogus' in its name.
- """
- result = principalForCalendarUserAddress(resourceSelf, userid)
- if result is None:
- return result
- return result.principalURL()
</del><ins>+ # @patched
+ # def principalForCalendarUserAddress(resourceSelf, cuaddr):
+ # if "bogus" in cuaddr:
+ # return None
+ # else:
+ # return FakePrincipal(cuaddr, self)
</ins><span class="cx">
</span><del>- @patched
- def principalForUID(resourceSelf, principalUID):
- return FakePrincipal("urn:uuid:" + principalUID, self)
</del><ins>+ # @patched
+ # def validUserIDForShare(resourceSelf, userid, request):
+ # """
+ # Temporary replacement for L{CalDAVResource.validUserIDForShare}
+ # that marks any principal without 'bogus' in its name.
+ # """
+ # 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("urn:uuid:" + 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, "POST", "/calendars/__uids__/user01/calendar/", content=body, authid="user01")
</del><ins>+ authRecord = yield self.directory.recordWithUID(u"user01")
+ request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user01/calendar/", content=body, authRecord=authRecord)
</ins><span class="cx"> request.headers.setHeader("content-type", MimeType("text", "xml"))
</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, "POST", "/calendars/__uids__/user02/", content=body, authid="user02")
</del><ins>+ authRecord = yield self.directory.recordWithUID(u"user02")
+ request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user02/", content=body, authRecord=authRecord)
</ins><span class="cx"> request.headers.setHeader("content-type", MimeType("text", "xml"))
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user03"),
</span><del>- customxml.CommonName.fromString("USER03"),
</del><ins>+ customxml.CommonName.fromString("User 03"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user04"),
</span><del>- customxml.CommonName.fromString("USER04"),
</del><ins>+ customxml.CommonName.fromString("User 04"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user04"),
</span><del>- customxml.CommonName.fromString("USER04"),
</del><ins>+ customxml.CommonName.fromString("User 04"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user03"),
</span><del>- customxml.CommonName.fromString("USER03"),
</del><ins>+ customxml.CommonName.fromString("User 03"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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"> """
</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"> """
</span><span class="cx">
</span><del>- self._sqlCalendarStore._directoryService = buildDirectory(homes=("wiki-testing",))
</del><span class="cx"> wcreate = self._sqlCalendarStore.newTransaction("create wiki")
</span><del>- yield wcreate.calendarHomeWithUID("wiki-testing", create=True)
</del><ins>+ yield wcreate.calendarHomeWithUID(
+ u"{prefix}testing".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="user01")
- sharer = yield txn.calendarHomeWithUID("wiki-testing")
</del><ins>+ sharee = yield self.homeUnderTest(name="user01", create=True)
+ sharer = yield txn.calendarHomeWithUID(
+ u"{prefix}testing".format(prefix=WikiDirectoryService.uidPrefix),
+ )
</ins><span class="cx"> cal = yield sharer.calendarWithName("calendar")
</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"> """
</span><ins>+ sharedName = yield self.wikiSetup()
+ access = WikiAccessLevel.read
</ins><span class="cx">
</span><del>- access = "read"
- def stubWikiAccessMethod(userID, wikiID):
- return access
- self.patch(sharing, "getWikiAccess", stubWikiAccessMethod)
</del><ins>+ def stubAccessForRecord(*args):
+ return succeed(access)
</ins><span class="cx">
</span><del>- sharedName = yield self.wikiSetup()
</del><ins>+ self.patch(WikiDirectoryRecord, "accessForRecord", stubAccessForRecord)
+
</ins><span class="cx"> request = SimpleStoreRequest(self, "GET", "/calendars/__uids__/user01/")
</span><span class="cx"> collection = yield request.locateResource("/calendars/__uids__/user01/" + sharedName)
</span><span class="cx">
</span><span class="lines">@@ -779,7 +786,7 @@
</span><span class="cx"> self.assertFalse("<write/>" in acl.toxml())
</span><span class="cx">
</span><span class="cx"> # Simulate the wiki server granting Read-Write access
</span><del>- access = "write"
</del><ins>+ access = WikiAccessLevel.write
</ins><span class="cx"> acl = (yield collection.shareeAccessControlList(request))
</span><span class="cx"> self.assertTrue("<write/>" 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"> """
</span><span class="cx"> sharedName = yield self.wikiSetup()
</span><del>- access = "write"
- def stubWikiAccessMethod(userID, wikiID):
- return access
- self.patch(sharing, "getWikiAccess", stubWikiAccessMethod)
</del><ins>+ access = WikiAccessLevel.write
+
+ def stubAccessForRecord(*args):
+ return succeed(access)
+
+ self.patch(WikiDirectoryRecord, "accessForRecord", stubAccessForRecord)
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def listChildrenViaPropfind():
</span><del>- request = SimpleStoreRequest(self, "PROPFIND", "/calendars/__uids__/user01/", authid="user01")
</del><ins>+ authRecord = yield self.directory.recordWithUID(u"user01")
+ request = SimpleStoreRequest(self, "PROPFIND", "/calendars/__uids__/user01/", authRecord=authRecord)
</ins><span class="cx"> request.headers.setHeader("depth", "1")
</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("/") 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 = "no-access"
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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(""),
</span><span class="cx"> davxml.HRef.fromString("urn:uuid:user02"),
</span><del>- customxml.CommonName.fromString("USER02"),
</del><ins>+ customxml.CommonName.fromString("User 02"),
</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 = ".db.calendaruserproxy"
</span><span class="cx"> NEWPROXYFILE = "proxies.sqlite"
</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__)),
- "directory", "test", "accounts.xml")
- config.DirectoryService.params.xmlFile = xmlFile
</del><span class="cx">
</span><del>- xmlAugmentsFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
- "directory", "test", "augments.xml")
- config.AugmentService.type = "twistedcaldav.directory.augment.AugmentXMLDB"
- config.AugmentService.params.xmlFiles = (xmlAugmentsFile,)
-
- resourceFile = os.path.join(os.path.dirname(os.path.dirname(__file__)),
- "directory", "test", "resources.xml")
- config.ResourceService.params.xmlFile = resourceFile
-
-
</del><span class="cx"> def doUpgrade(self, config):
</span><span class="cx"> """
</span><span class="cx"> Perform the actual upgrade. (Hook for parallel tests.)
</span><span class="cx"> """
</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"> """
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/__uids__/BB05932F-DCE7-4195-9ED4-0896EAFF3B0B/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/users/wsanchez/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/users/wsanchez/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/users/wsanchez/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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 = "<?xml version='1.0' encoding='UTF-8'?>\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/>"
</span><span class="cx"> value = "<?xml version='1.0' encoding='UTF-8'?>\r\n<calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'>\r\n <href xmlns='DAV:'>/calendars/users/nonexistent/calendar</href>\r\n</calendar-free-busy-set>\r\n"
</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"> """
</span><del>- self.setUpXMLDirectory()
</del><span class="cx">
</span><span class="cx"> before = {
</span><span class="cx"> "calendars": {
</span><span class="lines">@@ -306,7 +291,7 @@
</span><span class="cx"> db_basename : {
</span><span class="cx"> "@contents": "",
</span><span class="cx"> },
</span><del>- },
</del><ins>+ },
</ins><span class="cx"> "notifications": {
</span><span class="cx"> "sample-notification.xml": {
</span><span class="cx"> "@contents": "<?xml version='1.0'>\n<should-be-ignored />"
</span><span class="lines">@@ -353,8 +338,6 @@
</span><span class="cx"> are upgraded to /calendars/__uids__/XX/YY/<guid> form
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "archived" :
</span><span class="cx"> {
</span><span class="lines">@@ -663,8 +642,6 @@
</span><span class="cx"> interrupt an upgrade.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> ignoredUIDContents = {
</span><span class="cx"> "64" : {
</span><span class="cx"> "23" : {
</span><span class="lines">@@ -757,8 +734,6 @@
</span><span class="cx"> interrupt an upgrade.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> beforeUIDContents = {
</span><span class="cx"> "64" : {
</span><span class="cx"> "23" : {
</span><span class="lines">@@ -867,8 +842,6 @@
</span><span class="cx"> are upgraded to /calendars/__uids__/XX/YY/<guid>/ form
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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"> """
</span><span class="cx">
</span><del>- self.setUpXMLDirectory()
-
</del><span class="cx"> before = {
</span><span class="cx"> "calendars" :
</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, "getResourceInfo", _getResourceInfo)
</del><ins>+ # self.patch(XMLDirectoryService, "getResourceInfo", _getResourceInfo)
</ins><span class="cx">
</span><span class="cx"> before = {
</span><span class="cx"> "trigger_resource_migration" : {
</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 = "Need to port to twext.who"
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> def test_removeIllegalCharacters(self):
</span><span class="cx"> """
</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"> """
</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"> """
</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>- "mailto:a@example.com" :
- StubRecord("User A", 123, ("mailto:a@example.com", "urn:uuid:123")),
- "mailto:b@example.com" :
- StubRecord("User B", 234, ("mailto:b@example.com", "urn:uuid:234")),
- "/principals/users/a" :
- StubRecord("User A", 123, ("mailto:a@example.com", "urn:uuid:123")),
- "/principals/users/b" :
- StubRecord("User B", 234, ("mailto:b@example.com", "urn:uuid:234")),
</del><ins>+ "mailto:a@example.com":
+ StubRecord(("User A",), u"123", ("mailto:a@example.com", "urn:uuid:123")),
+ "mailto:b@example.com":
+ StubRecord(("User B",), u"234", ("mailto:b@example.com", "urn:uuid:234")),
+ "/principals/users/a":
+ StubRecord(("User A",), u"123", ("mailto:a@example.com", "urn:uuid:123")),
+ "/principals/users/b":
+ StubRecord(("User B",), u"234", ("mailto:b@example.com", "urn:uuid:234")),
</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"mercury")
+ 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("proxies.sqlite")
+ proxyFile = os.path.join(config.DataRoot, "proxies.xml")
+ 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"left_coast"])
+ )
+
+ record = yield self.directory.recordWithUID(u"non_calendar_proxy")
+
+ readDelegates = yield delegatesOf(txn, record, False)
+ self.assertEquals(len(readDelegates), 1)
+ self.assertEquals(
+ set([d.uid for d in readDelegates]),
+ set([u"recursive2_coasts"])
+ )
+
+ yield txn.commit()
+
+
+
+
</ins><span class="cx"> normalizeEvent = """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, "Rewrite or remove")
</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"> """
</span><del>- record = self.directory.recordWithShortName("users", "wsanchez")
</del><ins>+ record = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</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"> """
</span><del>- record = self.directory.recordWithShortName("users", "wsanchez")
</del><ins>+ record = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</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"> "http://localhost:8008/" + path
</span><span class="cx"> )
</span><span class="cx"> if user is not None:
</span><del>- guid = XMLFileBase.users[user]["guid"]
</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"> """
</span><del>- self.assertEquals(resource._principalCollections,
- frozenset([self.principalsResource]))
</del><ins>+ self.assertEquals(
+ resource._principalCollections,
+ frozenset([self.actualRoot.getChild("principals")])
+ )
</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"> """
</span><span class="cx"> calDavFile = yield self.getResource("calendars/users/wsanchez/calendar")
</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>- "transaction mismatch on %s; %r is not %r " %
- (name, homeChild._associatedTransaction, homeTransaction))
</del><ins>+ "transaction mismatch on {n}; {at} is not {ht} ".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"> """
</span><del>- assertProvides(self, IDataStore, self.addressbookCollection._newStore)
</del><ins>+ assertProvides(self, IDataStore, self.actualRoot.getChild("addressbooks")._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, "ImplicitUIDLock:%s" % (hashlib.md5("uid1").hexdigest(),))
</span><span class="cx">
</span><span class="cx"> # PUT fails
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</ins><span class="cx"> request = SimpleStoreRequest(
</span><span class="cx"> self,
</span><span class="cx"> "PUT",
</span><span class="cx"> "/calendars/users/wsanchez/calendar/1.ics",
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/calendar")}),
</span><del>- authid="wsanchez"
</del><ins>+ authRecord=authRecord
</ins><span class="cx"> )
</span><span class="cx"> request.stream = MemoryStream("""BEGIN:VCALENDAR
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -606,12 +617,13 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> # PUT works
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</ins><span class="cx"> request = SimpleStoreRequest(
</span><span class="cx"> self,
</span><span class="cx"> "PUT",
</span><span class="cx"> "/calendars/users/wsanchez/calendar/1.ics",
</span><span class="cx"> headers=Headers({"content-type": MimeType.fromString("text/calendar")}),
</span><del>- authid="wsanchez"
</del><ins>+ authRecord=authRecord
</ins><span class="cx"> )
</span><span class="cx"> request.stream = MemoryStream("""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, "ImplicitUIDLock:%s" % (hashlib.md5("uid1").hexdigest(),))
</span><span class="cx">
</span><ins>+ authRecord = yield self.directory.recordWithShortName(RecordType.user, u"wsanchez")
</ins><span class="cx"> request = SimpleStoreRequest(
</span><span class="cx"> self,
</span><span class="cx"> "DELETE",
</span><span class="cx"> "/calendars/users/wsanchez/calendar/1.ics",
</span><del>- authid="wsanchez"
</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, "Feature unimplemented")
</span><span class="cx"> testUnimplemented = lambda f: _todo(f, "Test unimplemented")
</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("directory").child("test")
</span><span class="cx">
</span><span class="cx"> xmlFile = dirTest.child("accounts.xml")
</span><ins>+resourcesFile = dirTest.child("resources.xml")
</ins><span class="cx"> augmentsFile = dirTest.child("augments.xml")
</span><span class="cx"> proxiesFile = dirTest.child("proxies.xml")
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class DirectoryFixture(object):
- """
- Test fixture for creating various parts of the resource hierarchy related
- to directories.
- """
</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(
- "/principals/", ds
- )
- self._directoryChangeHooks = [_setUpPrincipals]
</del><span class="cx">
</span><del>- directoryService = None
- principalsResource = None
-
- def addDirectoryService(self, newService):
- """
- 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.
- """
-
- 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):
- """
- 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.
- """
- self._directoryChangeHooks.append(callback)
- if self.directoryService is not None:
- callback(self.directoryService)
-
-
-
</del><span class="cx"> class SimpleStoreRequest(SimpleRequest):
</span><span class="cx"> """
</span><span class="cx"> A SimpleRequest that automatically grabs the proper transaction for a test.
</span><span class="cx"> """
</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("/principals/__uids__/%s/" % (record.uid,)))
</del><ins>+ if authRecord is not None:
+ self.authzUser = self.authnUser = element.Principal(element.HRef("/principals/__uids__/%s/" % (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("/principals/", self.directory)
- self.site.resource.putChild("principals", self.principalsResource)
- self.calendarCollection = DirectoryCalendarHomeProvisioningResource(self.directory, "/calendars/", self._sqlCalendarStore)
- self.site.resource.putChild("calendars", self.calendarCollection)
- self.addressbookCollection = DirectoryAddressBookHomeProvisioningResource(self.directory, "/addressbooks/", self._sqlCalendarStore)
- self.site.resource.putChild("addressbooks", 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("accounts.xml")
</span><span class="cx"> accounts.setContent(xmlFile.getContent())
</span><span class="cx">
</span><ins>+ resources = FilePath(config.DataRoot).child("resources.xml")
+ resources.setContent(resourcesFile.getContent())
</ins><span class="cx">
</span><del>- @property
- def directoryService(self):
- """
- Read-only alias for L{DirectoryFixture.directoryService} for
- compatibility with older tests. TODO: remove this.
- """
- return self.directory
</del><ins>+ augments = FilePath(config.DataRoot).child("augments.xml")
+ augments.setContent(augmentsFile.getContent())
</ins><span class="cx">
</span><ins>+ proxies = FilePath(config.DataRoot).child("proxies.xml")
+ 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):
- """
- 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.
- """
- return CommonDataStore(FilePath(config.DocumentRoot), None, None, True, False,
- quota=deriveQuota(self))
-
-
- def createStockDirectoryService(self):
- """
- Create a stock C{directoryService} attribute and assign it.
- """
- self.xmlFile = FilePath(config.DataRoot).child("accounts.xml")
- self.xmlFile.setContent(xmlFile.getContent())
- self.directoryFixture.addDirectoryService(XMLDirectoryService({
- "xmlFile": "accounts.xml",
- "augmentService":
- augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
- }))
-
-
- def setupCalendars(self):
- """
- 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}.
- """
- newStore = self.createDataStore()
- @self.directoryFixture.whenDirectoryServiceChanges
- def putAllChildren(ds):
- self.calendarCollection = (
- DirectoryCalendarHomeProvisioningResource(
- ds, "/calendars/", newStore
- ))
- self.site.resource.putChild("calendars", self.calendarCollection)
- self.addressbookCollection = (
- DirectoryAddressBookHomeProvisioningResource(
- ds, "/addressbooks/", newStore
- ))
- self.site.resource.putChild("addressbooks",
- self.addressbookCollection)
-
-
- def configure(self):
- """
- Adjust the global configuration for this test.
- """
- config.reset()
-
- config.ServerRoot = os.path.abspath(self.serverRoot)
- config.ConfigRoot = "config"
- config.LogRoot = "logs"
- config.RunRoot = "logs"
-
- 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):
- """
- Read-only alias for L{DirectoryFixture.directoryService} for
- compatibility with older tests. TODO: remove this.
- """
- 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("Xattr mismatch:", childPath, attr)
</span><span class="cx"> print((xattr.getxattr(childPath, attr), " != ", 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):
+ """
+ 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.
+ """
+ return CommonDataStore(FilePath(config.DocumentRoot), None, None, True, False,
+ quota=deriveQuota(self))
+
+
+ def setupCalendars(self):
+ """
+ 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}.
+ """
+ newStore = self.createDataStore()
+
+
+ @self.directoryFixture.whenDirectoryServiceChanges
+ def putAllChildren(ds):
+ self.calendarCollection = (
+ DirectoryCalendarHomeProvisioningResource(
+ ds, "/calendars/", newStore
+ ))
+ self.site.resource.putChild("calendars", self.calendarCollection)
+ self.addressbookCollection = (
+ DirectoryAddressBookHomeProvisioningResource(
+ ds, "/addressbooks/", newStore
+ ))
+ self.site.resource.putChild("addressbooks",
+ self.addressbookCollection)
+
+
+ def configure(self):
+ """
+ Adjust the global configuration for this test.
+ """
+ config.reset()
+
+ config.ServerRoot = os.path.abspath(self.serverRoot)
+ config.ConfigRoot = "config"
+ config.LogRoot = "logs"
+ config.RunRoot = "logs"
+
+ 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"> "stub; ignore me"
</span><span class="lines">@@ -555,7 +458,8 @@
</span><span class="cx"> that stores the data for that L{CalendarHomeResource}.
</span><span class="cx"> """
</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"> """
</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"> "txdav.base.propertystore.xattr.PropertyStore.deadPropertyXattrPrefix"
</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("Fixing bad quotes in %s" % (resPath,))
</span><span class="cx"> needsRewrite = True
</span><span class="cx"> except Exception, e:
</span><del>- log.error("Error while fixing bad quotes in %s: %s" %
- (resPath, e))
</del><ins>+ log.error(
+ "Error while fixing bad quotes in %s: %s" %
+ (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("Removing illegal characters in %s" % (resPath,))
</span><span class="cx"> needsRewrite = True
</span><span class="cx"> except Exception, e:
</span><del>- log.error("Error while removing illegal characters in %s: %s" %
- (resPath, e))
</del><ins>+ log.error(
+ "Error while removing illegal characters in %s: %s" %
+ (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("Normalized CUAddrs in %s" % (resPath,))
</span><span class="cx"> needsRewrite = True
</span><span class="cx"> except Exception, e:
</span><del>- log.error("Error while normalizing %s: %s" %
- (resPath, e))
</del><ins>+ log.error(
+ "Error while normalizing %s: %s" %
+ (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("Upgrading calendar: %s" % (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("{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set"):
</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("Failed to upgrade calendar home %s: %s" % (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):
- """
- Upgrade driver which runs in the parent process.
- """
</del><ins>+# class To1Driver(AMP):
+# """
+# Upgrade driver which runs in the parent process.
+# """
</ins><span class="cx">
</span><del>- def upgradeHomeInHelper(self, path):
- return self.callRemote(UpgradeOneHome, path=path).addCallback(
- operator.itemgetter("succeeded")
- )
</del><ins>+# def upgradeHomeInHelper(self, path):
+# return self.callRemote(UpgradeOneHome, path=path).addCallback(
+# operator.itemgetter("succeeded")
+# )
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class To1Home(AMP):
- """
- Upgrade worker which runs in dedicated subprocesses.
- """
</del><ins>+# class To1Home(AMP):
+# """
+# Upgrade worker which runs in dedicated subprocesses.
+# """
</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"> """
</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 = ".db.calendaruserproxy"
</span><span class="cx"> newFilename = "proxies.sqlite"
</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("/")), uid=uid,
- gid=gid)
</del><ins>+ makeDirsUserGroup(
+ os.path.dirname(newHome.rstrip("/")), 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 "v1" that's where this info lived.
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+ print("FIXME, need to port migrateResourceInfo to twext.who")
+ returnValue(None)
+
</ins><span class="cx"> log.warn("Fetching delegate assignments and auto-schedule settings from directory")
</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("No resource info found in directory")
</span><del>- return
</del><ins>+ returnValue(None)
</ins><span class="cx">
</span><span class="cx"> log.warn("Found info for %d resources and locations in directory; applying settings" % (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, "users"),
- (DirectoryService.recordType_groups, "groups"),
- (DirectoryService.recordType_locations, "locations"),
- (DirectoryService.recordType_resources, "resources"),
</del><ins>+ (RecordType.user, u"users"),
+ (RecordType.group, u"groups"),
+ (CalRecordType.location, u"locations"),
+ (CalRecordType.resource, u"resources"),
</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("Processed calendar home %d of %d"
- % (count, total))
</del><ins>+ log.warn(
+ "Processed calendar home %d of %d"
+ % (count, total)
+ )
</ins><span class="cx"> log.warn("Done processing calendar homes")
</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"> """
</span><span class="cx"> Normalize calendar user addresses to urn:uuid: form.
</span><span class="lines">@@ -583,23 +604,26 @@
</span><span class="cx"> """
</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("Data upgrade failed, see error.log for details")
</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("Unable to migrate locations and resources from OD: %s" % (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("Cannot open %s; skipping migration" %
- (versionFilePath,))
</del><ins>+ log.error(
+ "Cannot open %s; skipping migration" %
+ (versionFilePath,)
+ )
</ins><span class="cx"> except ValueError:
</span><del>- log.error("Invalid version number in %s; skipping migration" %
- (versionFilePath,))
</del><ins>+ log.error(
+ "Invalid version number in %s; skipping migration" %
+ (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("/")
</span><span class="cx"> if pieces[2] == "__uids__":
</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("Can't update free-busy href; %s is not in the directory" % shortName)
</span><del>- return ""
</del><ins>+ returnValue("")
</ins><span class="cx">
</span><span class="cx"> uid = record.uid
</span><span class="cx"> newHref = "/calendars/__uids__/%s/%s/" % (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("Invalid free/busy property value")
</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("/")
</span><del>- if parts[0] == "": # absolute path
</del><ins>+ if parts[0] == "": # absolute path
</ins><span class="cx"> parts[0] = "/"
</span><span class="cx">
</span><span class="cx"> path = ""
</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"> """
</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("STILL NEED TO IMPLEMENT migrateFromOD")
+ 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("Migrating locations and resources")
</del><ins>+# log.warn("Migrating locations and resources")
</ins><span class="cx">
</span><del>- userService = directory.serviceForRecordType("users")
- resourceService = directory.serviceForRecordType("resources")
- if (
- not isinstance(userService, OpenDirectoryService) or
- not isinstance(resourceService, XMLDirectoryService)
- ):
- # Configuration requires no migration
- return succeed(None)
</del><ins>+# userService = directory.serviceForRecordType("users")
+# resourceService = directory.serviceForRecordType("resources")
+# 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("Could not start augment service")
+
</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"> "select GUID, AUTOSCHEDULE from RESOURCEINFO"
</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 = (
+ "automatic" if autoSchedule else "default"
+ )
</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("Migrated %d auto-schedule settings" % (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(
+ "select GROUPNAME, MEMBER from GROUPS"
+ )
+ ):
+ if "#" not in groupName:
+ continue
+
+ delegatorUID, groupType = groupName.split("#")
+ delegatorRecord = yield directory.recordWithUID(delegatorUID)
+ if delegatorRecord is None:
+ continue
+
+ delegateRecord = yield directory.recordWithUID(memberUID)
+ if delegateRecord is None:
+ continue
+
+ readWrite = (groupType == "calendar-proxy-write")
+ yield addDelegate(txn, delegatorRecord, delegateRecord, readWrite)
+
+ yield txn.commit()
+
+
+
</ins><span class="cx"> class UpgradeFileSystemFormatStep(object):
</span><span class="cx"> """
</span><span class="cx"> Upgrade filesystem from previous versions.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- def __init__(self, config):
</del><ins>+ def __init__(self, config, store):
</ins><span class="cx"> """
</span><span class="cx"> Initialize the service.
</span><span class="cx"> """
</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"> """
</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, "proxies.sqlite"
+ )
+ )
+ ):
+ sqliteProxyService = ProxySqliteDB("proxies.sqlite")
+ 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, "proxies.sqlite")
- 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, "proxies.sqlite")
+ 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("proxies.sqlite")
+ 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"> """
</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("Ignored inbox item - no record: %s" % (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("Ignored inbox item - no principal: %s" % (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, "PUT", 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("/principals/__uids__/%s/" % (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("Error processing inbox item: %s (%s)"
- % (inboxItem, e))
</del><ins>+ log.error(
+ "Error processing inbox item: %s (%s)"
+ % (inboxItem, e)
+ )
</ins><span class="cx"> else:
</span><span class="cx"> log.debug("Ignored inbox item - no resource: %s" % (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"> """
</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 = "urn:uuid:%s" % (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("Removing invalid inbox item: %s" % (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):
- """
- 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.
- """
- try:
- principal = principalFunction(cuaddr)
- except Exception, e:
- log.debug("Lookup of %s failed: %s" % (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('"', "'")
-
- cuas = principal.record.calendarUserAddresses
-
- return (fullName, rec.guid, cuas)
-
-
-
</del><span class="cx"> def bestAcceptType(accepts, allowedTypes):
</span><span class="cx"> """
</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"> """
</span><span class="cx"> Check the validity of the Originator header. Extract the corresponding principal.
</span><span class="cx"> """
</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("Could not find principal for originator: %s" % (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"> """
</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"> """
</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("ATTENDEE in calendar data does not match owner of Outbox: %s" % (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("FBCache", 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 ""
</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("utf-8"))
</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("utf-8"))
</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 ""
</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 ""
</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("urn:uuid:"):
</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("Don't have an email address for the organizer; "
</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 = "organizer"
</span><del>- elif self.isAttendeeScheduling():
</del><ins>+ elif (yield self.isAttendeeScheduling()):
</ins><span class="cx"> self.state = "attendee"
</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("utf-8"))
</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("utf-8"))
</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("Implicit - organizer '{organizer}' is re-inviting UID: '{uid}', attendees: {attendees}", organizer=self.organizer, uid=self.uid, attendees=", ".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("urn:uuid"):
</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("Cannot change ORGANIZER: UID:{uid}", 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("SCHEDULE-AGENT", "SERVER").upper() == "CLIENT":
</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"> """
</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"> """
</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"> """
</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("Originator", utf8String(originator))
</span><span class="cx"> self.sign_headers.append("Originator")
</span><span class="cx">
</span><span class="lines">@@ -399,6 +401,7 @@
</span><span class="cx"> self.sign_headers.append("Authorization")
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
</ins><span class="cx"> def _prepareData(self):
</span><span class="cx"> """
</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("METHOD")
</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("METHOD")
</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"> """
</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("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
</span><span class="lines">@@ -408,7 +408,7 @@
</span><span class="cx"> """
</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("Invalid ATTENDEE in calendar data: %s" % (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"> "iTipGenerator",
</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, "doing_attendee_refresh"):
</del><ins>+ if (yield self.recipient.principal.canAutoSchedule(organizer=organizer)) and not hasattr(self.txn, "doing_attendee_refresh"):
</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() == "INDIVIDUAL"
</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"> """
</span><del>-
</del><span class="cx"> # First ignore the none mode
</span><del>- if automode == "none":
</del><ins>+ if automode == AutoScheduleMode.none:
</ins><span class="cx"> returnValue((False, True, "",))
</span><del>- elif not automode or automode == "default":
- automode = config.Scheduling.Options.AutoSchedule.DefaultMode
</del><ins>+ elif not automode:
+ automode = {
+ "none": AutoScheduleMode.none,
+ "accept-always": AutoScheduleMode.accept,
+ "decline-always": AutoScheduleMode.decline,
+ "accept-if-free": AutoScheduleMode.acceptIfFree,
+ "decline-if-busy": AutoScheduleMode.declineIfBusy,
+ "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ }.get(
+ config.Scheduling.Options.AutoSchedule.DefaultMode,
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy
+ )
</ins><span class="cx">
</span><del>- log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode,))
</del><ins>+ log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (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 == "NEEDS-ACTION" and instance.active:
</span><del>- if automode == "accept-always":
</del><ins>+ if automode == AutoScheduleMode.accept:
</ins><span class="cx"> freePartstat = busyPartstat = "ACCEPTED"
</span><del>- elif automode == "decline-always":
</del><ins>+ elif automode == AutoScheduleMode.decline:
</ins><span class="cx"> freePartstat = busyPartstat = "DECLINED"
</span><span class="cx"> else:
</span><del>- freePartstat = "ACCEPTED" if automode in ("accept-if-free", "automatic",) else "NEEDS-ACTION"
- busyPartstat = "DECLINED" if automode in ("decline-if-busy", "automatic",) else "NEEDS-ACTION"
</del><ins>+ freePartstat = "ACCEPTED" if automode in (
+ AutoScheduleMode.acceptIfFree,
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ ) else "NEEDS-ACTION"
+ busyPartstat = "DECLINED" if automode in (
+ AutoScheduleMode.declineIfBusy,
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ ) else "NEEDS-ACTION"
</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"> """
</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["recipients"] = 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"> """
</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("utf-8"))
</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("utf-8"))
</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("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", 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("utf-8"))
</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("Attendee list size %d is larger than allowed limit %d" % (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"> """
</span><span class="cx"> If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
</span><span class="cx"> """
</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 "INDIVIDUAL"
</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("utf-8"))
</ins><span class="cx"> prop = Property("X-CALENDARSERVER-MODIFIED-BY", authz.canonicalCalendarUserAddress())
</span><del>- prop.setParameter("CN", authz.displayName())
</del><ins>+ prop.setParameter("CN", authz.displayName)
</ins><span class="cx"> for candidate in authz.calendarUserAddresses:
</span><span class="cx"> if candidate.startswith("mailto:"):
</span><span class="cx"> prop.setParameter("EMAIL", candidate[7:])
</span><span class="lines">@@ -2108,7 +2109,7 @@
</span><span class="cx"> log.debug("Organizer and attendee properties were entirely removed by the client. Restoring existing properties.")
</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("Sync COMPLETED property change.")
</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"> """
</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"> """
</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("CUTYPE") == "ROOM":
</span><span class="cx"> value = attendee.value()
</span><del>- if value.startswith("urn:uuid:"):
- guid = value[9:]
- loc = self.directoryService().recordWithGUID(guid)
- if loc is not None:
- guid = loc.extras.get("associatedAddress",
- None)
- if guid is not None:
- addr = self.directoryService().recordWithGUID(guid)
- if addr is not None:
- street = addr.extras.get("streetAddress", "")
- geo = addr.extras.get("geo", "")
- if street and geo:
- title = attendee.parameterValue("CN")
- params = {
- "X-ADDRESS" : street,
- "X-APPLE-RADIUS" : "71",
- "X-TITLE" : title,
- }
- structured = Property("X-APPLE-STRUCTURED-LOCATION",
- "geo:%s" % (geo,), params=params,
- valuetype=Value.VALUETYPE_URI)
- sub.replaceProperty(structured)
- sub.replaceProperty(Property("LOCATION",
- "%s\n%s" % (title, street)))
</del><ins>+ loc = yield dir.recordWithCalendarUserAddress(value)
+ if loc is not None:
+ uid = getattr(loc, "associatedAddress", "")
+ if uid:
+ addr = yield dir.recordWithUID(uid)
+ if addr is not None:
+ street = getattr(addr, "streetAddress", "")
+ geo = getattr(addr, "geographicLocation", "")
+ if street and geo:
+ title = attendee.parameterValue("CN")
+ params = {
+ "X-ADDRESS": street,
+ "X-APPLE-RADIUS": "71",
+ "X-TITLE": title,
+ }
+ structured = Property("X-APPLE-STRUCTURED-LOCATION",
+ "geo:%s" % (geo.encode("utf-8"),), params=params,
+ valuetype=Value.VALUETYPE_URI)
+ sub.replaceProperty(structured)
+ newLocProperty = Property("LOCATION",
+ "%s\n%s" % (title, street.encode("utf-8")))
+ 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"> <!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="/Search">
- <user>
</del><ins>+<directory realm="/Search">
+ <record type="user">
</ins><span class="cx"> <uid>home1</uid>
</span><del>- <guid>home1</guid>
</del><ins>+ <short-name>home1</short-name>
</ins><span class="cx"> <password>home1</password>
</span><del>- <name>Example User 1</name>
- <email-address>home1@example.com</email-address>
- </user>
- <user>
</del><ins>+ <full-name>Example User 1</full-name>
+ <email>home1@example.com</email>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>home2</uid>
</span><del>- <guid>home2</guid>
</del><ins>+ <short-name>home2</short-name>
</ins><span class="cx"> <password>home2</password>
</span><del>- <name>Example User 2</name>
- <email-address>home2@example.com</email-address>
- </user>
-</accounts>
</del><ins>+ <full-name>Example User 2</full-name>
+ <email>home2@example.com</email>
+ </record>
+</directory>
</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"> <?xml version="1.0" encoding="utf-8"?>
</span><span class="cx">
</span><del>-<accounts realm="/Search">
-</accounts>
</del><ins>+<directory realm="/Search">
+</directory>
</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"> """
</span><span class="cx">
</span><span class="cx"> storePath = FilePath(__file__).parent().child("calendar_store")
</span><del>-homeRoot = storePath.child("ho").child("me").child("home1")
</del><ins>+homeRoot = storePath.child("ho").child("me").child(u"home1")
</ins><span class="cx"> cal1Root = homeRoot.child("calendar_1")
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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("home1"))
</del><ins>+ home = (yield self.transactionUnderTest().calendarHomeWithUID(u"home1"))
</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>- "home1" : {
</del><ins>+ u"home1" : {
</ins><span class="cx"> "calendar1" : {
</span><span class="cx"> "1.1.ics" : (PLAIN_ICS % {"year": now, "uid": "1.1", }, metadata,),
</span><span class="cx"> "1.2.ics" : (ATTACHMENT_ICS % {"year": now, "uid": "1.2", "userid": "user01", "dropboxid": "1.2"}, metadata,),
</span><span class="lines">@@ -1372,7 +1372,7 @@
</span><span class="cx"> "1.5.ics" : (ATTACHMENT_ICS % {"year": now, "uid": "1.5", "userid": "user01", "dropboxid": "1.4"}, metadata,),
</span><span class="cx"> }
</span><span class="cx"> },
</span><del>- "home2" : {
</del><ins>+ u"home2" : {
</ins><span class="cx"> "calendar2" : {
</span><span class="cx"> "2-2.1.ics" : (PLAIN_ICS % {"year": now, "uid": "2-2.1", }, metadata,),
</span><span class="cx"> "2-2.2.ics" : (ATTACHMENT_ICS % {"year": now, "uid": "2-2.2", "userid": "user02", "dropboxid": "2.2"}, metadata,),
</span><span class="lines">@@ -1488,16 +1488,16 @@
</span><span class="cx"> """
</span><span class="cx"> Add the full set of attachments to be used for testing.
</span><span class="cx"> """
</span><del>- yield self._addAttachment("home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_1.txt")
- yield self._addAttachment("home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_2.txt")
- yield self._addAttachment("home1", "calendar1", "1.3.ics", "1.3", "attach_1_3.txt")
- yield self._addAttachment("home1", "calendar1", "1.4.ics", "1.4", "attach_1_4.txt")
- yield self._addAttachmentProperty("home1", "calendar1", "1.5.ics", "1.4", "home1", "attach_1_4.txt")
</del><ins>+ yield self._addAttachment(u"home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_1.txt")
+ yield self._addAttachment(u"home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_2.txt")
+ yield self._addAttachment(u"home1", "calendar1", "1.3.ics", "1.3", "attach_1_3.txt")
+ yield self._addAttachment(u"home1", "calendar1", "1.4.ics", "1.4", "attach_1_4.txt")
+ yield self._addAttachmentProperty(u"home1", "calendar1", "1.5.ics", "1.4", "home1", "attach_1_4.txt")
</ins><span class="cx">
</span><del>- yield self._addAttachment("home2", "calendar2", "2-2.2.ics", "2.2", "attach_2_2.txt")
- yield self._addAttachmentProperty("home2", "calendar2", "2-2.3.ics", "1.3", "home1", "attach_1_3.txt")
- yield self._addAttachmentProperty("home2", "calendar3", "2-3.2.ics", "1.4", "home1", "attach_1_4.txt")
- yield self._addAttachmentProperty("home2", "calendar3", "2-3.3.ics", "1.4", "home1", "attach_1_4.txt")
</del><ins>+ yield self._addAttachment(u"home2", "calendar2", "2-2.2.ics", "2.2", "attach_2_2.txt")
+ yield self._addAttachmentProperty(u"home2", "calendar2", "2-2.3.ics", "1.3", "home1", "attach_1_3.txt")
+ yield self._addAttachmentProperty(u"home2", "calendar3", "2-3.2.ics", "1.4", "home1", "attach_1_4.txt")
+ yield self._addAttachmentProperty(u"home2", "calendar3", "2-3.3.ics", "1.4", "home1", "attach_1_4.txt")
</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"> """
</span><span class="cx"> Test L{txdav.caldav.datastore.sql.DropboxAttachment.convertToManaged} converts properly to a ManagedAttachment.
</span><span class="cx"> """
</span><del>- yield self._addAttachment("home1", "calendar1", "1.2.ics", "1.2", "attach_1_2.txt")
</del><ins>+ yield self._addAttachment(u"home1", "calendar1", "1.2.ics", "1.2", "attach_1_2.txt")
</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"> """
</span><span class="cx"> Test L{txdav.caldav.datastore.sql.ManagedAttachment.newReference} creates a new managed attachment reference.
</span><span class="cx"> """
</span><del>- yield self._addAttachment("home1", "calendar1", "1.4.ics", "1.4", "attach_1_4.txt")
</del><ins>+ yield self._addAttachment(u"home1", "calendar1", "1.4.ics", "1.4", "attach_1_4.txt")
</ins><span class="cx">
</span><span class="cx"> txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">
</span><del>- home = (yield txn.calendarHomeWithUID("home1"))
</del><ins>+ home = (yield txn.calendarHomeWithUID(u"home1"))
</ins><span class="cx"> calendar = (yield home.calendarWithName("calendar1"))
</span><span class="cx"> event4 = (yield calendar.calendarObjectWithName("1.4.ics"))
</span><span class="cx"> event5 = (yield calendar.calendarObjectWithName("1.5.ics"))
</span><span class="lines">@@ -1664,12 +1664,12 @@
</span><span class="cx"> """
</span><span class="cx"> Test L{txdav.caldav.datastore.sql.CalendarObject.convertAttachments} re-writes calendar data.
</span><span class="cx"> """
</span><del>- yield self._addAttachment("home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_1.txt")
- yield self._addAttachment("home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_2.txt")
</del><ins>+ yield self._addAttachment(u"home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_1.txt")
+ yield self._addAttachment(u"home1", "calendar1", "1.2.ics", "1.2", "attach_1_2_2.txt")
</ins><span class="cx">
</span><span class="cx"> txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">
</span><del>- home = (yield txn.calendarHomeWithUID("home1"))
</del><ins>+ home = (yield txn.calendarHomeWithUID(u"home1"))
</ins><span class="cx"> calendar = (yield home.calendarWithName("calendar1"))
</span><span class="cx"> event = (yield calendar.calendarObjectWithName("1.2.ics"))
</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("home1"))
</del><ins>+ home = (yield txn.calendarHomeWithUID(u"home1"))
</ins><span class="cx"> calendar = (yield home.calendarWithName("calendar1"))
</span><span class="cx"> event = (yield calendar.calendarObjectWithName("1.2.ics"))
</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("home1"))
</del><ins>+ home = (yield txn.calendarHomeWithUID(u"home1"))
</ins><span class="cx"> calendar = (yield home.calendarWithName("calendar1"))
</span><span class="cx"> event = (yield calendar.calendarObjectWithName("1.2.ics"))
</span><span class="cx"> dattachment = (yield DropBoxAttachment.load(txn, "1.2.dropbox", "attach_1_2_2.txt"))
</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("home1"))
</del><ins>+ home = (yield txn.calendarHomeWithUID(u"home1"))
</ins><span class="cx"> calendar = (yield home.calendarWithName("calendar1"))
</span><span class="cx"> event = (yield calendar.calendarObjectWithName("1.2.ics"))
</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, "1.2.dropbox")
</span><span class="cx"> yield txn.commit()
</span><span class="cx">
</span><del>- yield self._verifyConversion("home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
- yield self._verifyNoConversion("home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
- yield self._verifyNoConversion("home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</del><ins>+ yield self._verifyConversion(u"home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</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, "1.3.dropbox")
</span><span class="cx"> yield txn.commit()
</span><span class="cx">
</span><del>- yield self._verifyNoConversion("home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
- yield self._verifyConversion("home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
- yield self._verifyNoConversion("home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</del><ins>+ yield self._verifyNoConversion(u"home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
+ yield self._verifyConversion(u"home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</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, "1.4.dropbox")
</span><span class="cx"> yield txn.commit()
</span><span class="cx">
</span><del>- yield self._verifyNoConversion("home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
- yield self._verifyNoConversion("home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
- yield self._verifyNoConversion("home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
- yield self._verifyNoConversion("home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
- yield self._verifyConversion("home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
- yield self._verifyConversion("home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</del><ins>+ yield self._verifyNoConversion(u"home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
+ yield self._verifyNoConversion(u"home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
+ yield self._verifyNoConversion(u"home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
+ yield self._verifyConversion(u"home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
+ yield self._verifyConversion(u"home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</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("home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
- yield self._verifyConversion("home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
- yield self._verifyConversion("home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
- yield self._verifyConversion("home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
- yield self._verifyConversion("home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
- yield self._verifyConversion("home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</del><ins>+ yield self._verifyConversion(u"home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.4.ics", ("attach_1_4.txt",))
+ yield self._verifyConversion(u"home1", "calendar1", "1.5.ics", ("attach_1_4.txt",))
+ yield self._verifyConversion(u"home2", "calendar2", "2-2.2.ics", ("attach_2_2.txt",))
+ yield self._verifyConversion(u"home2", "calendar2", "2-2.3.ics", ("attach_1_3.txt",))
+ yield self._verifyConversion(u"home2", "calendar3", "2-3.2.ics", ("attach_1_4.txt",))
+ yield self._verifyConversion(u"home2", "calendar3", "2-3.3.ics", ("attach_1_4.txt",))
</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"> """
</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="INDIVIDUAL",
</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 = ""
- 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("urn:uuid:"):
- cua = candidate
- break
- # Prefer mailto: if no urn:uuid:
- elif candidate.startswith("mailto:"):
- cua = candidate
- return cua
</del><ins>+ """
+ Return a CUA for this record, preferring in this order:
+ urn:uuid: form
+ mailto: form
+ /principals/__uids__/ form
+ first in calendarUserAddresses list (sorted)
+ """
</ins><span class="cx">
</span><ins>+ sortedCuas = sorted(self.calendarUserAddresses)
</ins><span class="cx">
</span><ins>+ for prefix in (
+ "urn:uuid:",
+ "mailto:",
+ "/principals/__uids__/"
+ ):
+ 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 "automatic"
</del><ins>+ return succeed("automatic")
</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"> "il1", ("il1",), "1 Infinite Loop", [],
</span><del>- extras={
- "geo" : "37.331741,-122.030333",
- "streetAddress" : "1 Infinite Loop, Cupertino, CA 95014",
- }
</del><ins>+ geographicLocation="37.331741,-122.030333",
+ streetAddress="1 Infinite Loop, Cupertino, CA 95014"
</ins><span class="cx"> ))
</span><span class="cx"> directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx"> "il2", ("il2",), "2 Infinite Loop", [],
</span><del>- extras={
- "geo" : "37.332633,-122.030502",
- "streetAddress" : "2 Infinite Loop, Cupertino, CA 95014",
- }
</del><ins>+ geographicLocation="37.332633,-122.030502",
+ streetAddress="2 Infinite Loop, Cupertino, CA 95014"
</ins><span class="cx"> ))
</span><span class="cx"> directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx"> "room1", ("room1",), "Conference Room One",
</span><span class="cx"> frozenset(("urn:uuid:room1",)),
</span><del>- extras={
- "associatedAddress" : "il1",
- }
</del><ins>+ associatedAddress="il1",
</ins><span class="cx"> ))
</span><span class="cx"> directory.addRecord(TestCalendarStoreDirectoryRecord(
</span><span class="cx"> "room2", ("room2",), "Conference Room Two",
</span><span class="cx"> frozenset(("urn:uuid:room2",)),
</span><del>- extras={
- "associatedAddress" : "il2",
- }
</del><ins>+ associatedAddress="il2",
</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"> """
</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"> """
</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("Lookup of %s failed: %s" % (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('"', "'"),
- record.uid,
- record.calendarUserAddresses,
</del><ins>+ fullName = record.displayName.replace('"', "'").encode("utf-8")
+ cuas = set(
+ [cua.encode("utf-8") 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"> """
</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"> """
</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"> """
</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("Unknown child element: %s" % (qname,))
</span><span class="cx">
</span><del>- if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
- raise ValueError("No other tests allowed when CardDAV:is-not-defined is present")
-
</del><span class="cx"> if xml_element.qname() == (carddav_namespace, "prop-filter"):
</span><span class="cx"> propfilter_test = xml_element.attributes.get("test", "anyof")
</span><span class="cx"> if propfilter_test not in ("anyof", "allof"):
</span><span class="lines">@@ -200,13 +197,16 @@
</span><span class="cx"> else:
</span><span class="cx"> propfilter_test = "anyof"
</span><span class="cx">
</span><ins>+ if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0) and propfilter_test == "allof":
+ raise ValueError("When test is allof, no other tests allowed when CardDAV:is-not-defined is present")
+
</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["name"]
</span><span class="cx"> if isinstance(self.filter_name, unicode):
</span><span class="cx"> self.filter_name = self.filter_name.encode("utf-8")
</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"> """
</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 == "allof"
+ 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) > 0:
</span><del>- allof = self.propfilter_test == "allof"
</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"> """
</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"> """
</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("utf-8"))
</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"> """
</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"> """
</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("Cross-pod source: {}".format(source_guid))
</del><ins>+ raise DirectoryRecordNotFoundError("Cross-pod source: {}".format(source_uid))
</ins><span class="cx"> if not source.thisServer():
</span><del>- raise FailedCrossPodRequestError("Cross-pod source not on this server: {}".format(source_guid))
</del><ins>+ raise FailedCrossPodRequestError("Cross-pod source not on this server: {}".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("Cross-pod destination: {}".format(destination_guid))
</del><ins>+ raise DirectoryRecordNotFoundError("Cross-pod destination: {}".format(destination_uid))
</ins><span class="cx"> if destination.thisServer():
</span><del>- raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(destination_guid))
</del><ins>+ raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".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"> """
</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"> "action": "shareinvite",
</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"> """
</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"> "action": "shareuninvite",
</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"> """
</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"> "action": "sharereply",
</span><span class="lines">@@ -398,7 +399,7 @@
</span><span class="cx">
</span><span class="cx"> actionName = "add-attachment"
</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["rids"] = rids
</span><span class="cx"> action["filename"] = 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 = "update-attachment"
</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["managedID"] = managed_id
</span><span class="cx"> action["filename"] = 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 = "remove-attachment"
</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["rids"] = rids
</span><span class="cx"> action["managedID"] = 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"> """
</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"> "action": 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["resource_id"] = 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"> """
</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["arguments"] = 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("freebusy", calresource)
</del><ins>+ action, recipient = yield self._send("freebusy", calresource)
</ins><span class="cx"> action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
</span><span class="cx"> action["matchtotal"] = matchtotal
</span><span class="cx"> action["excludeuid"] = 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"> """
</span><span class="cx"> Cross-pod request fails when there is no shared secret header present.
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> conduit = PoddingConduit(self.storeUnderTest())
</span><del>- r1, r2 = conduit.validRequst("user01", "puser02")
</del><ins>+ r1, r2 = yield conduit.validRequest("user01", "puser02")
</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, "bogus01", "user02")
- self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "user01", "bogus02")
- self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, "user01", "user02")
</del><ins>+ self.assertFailure(
+ conduit.validRequest("bogus01", "user02"),
+ DirectoryRecordNotFoundError
+ )
</ins><span class="cx">
</span><ins>+ self.assertFailure(
+ conduit.validRequest("user01", "bogus02"),
+ DirectoryRecordNotFoundError
+ )
</ins><span class="cx">
</span><ins>+ self.assertFailure(
+ conduit.validRequest("user01", "user02"),
+ 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"> "action": "fake",
</span><span class="cx"> "echo": "bravo"
</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"> """
</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("name"),
- gr.GROUP_GUID: Parameter("groupGUID"),
- gr.MEMBERSHIP_HASH: Parameter("membershipHash")},
- Return=gr.GROUP_ID)
</del><ins>+ return Insert(
+ {
+ gr.NAME: Parameter("name"),
+ gr.GROUP_GUID: Parameter("groupUID"),
+ gr.MEMBERSHIP_HASH: Parameter("membershipHash")
+ },
+ 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("membershipHash"),
- gr.NAME: Parameter("name"), gr.MODIFIED: Parameter("timestamp")},
- Where=(gr.GROUP_GUID == Parameter("groupGUID")))
</del><ins>+ return Update(
+ {
+ gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
+ gr.NAME: Parameter("name"),
+ gr.MODIFIED:
+ Parameter("timestamp")
+ },
+ Where=(gr.GROUP_GUID == Parameter("groupUID"))
+ )
</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("groupGUID")
- )
- )
</del><ins>+ return Select(
+ [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH],
+ From=gr,
+ Where=(gr.GROUP_GUID == Parameter("groupUID"))
+ )
</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("groupID")
- )
- )
</del><ins>+ return Select(
+ [gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH],
+ From=gr,
+ Where=(gr.GROUP_ID == Parameter("groupID"))
+ )
</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("groupID")))
</del><ins>+ return Delete(
+ From=gr,
+ Where=(gr.GROUP_ID == Parameter("groupID"))
+ )
</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"> """
</span><del>- @type groupGUID: C{UUID}
</del><ins>+ @type groupUID: C{unicode}
+ @type name: C{unicode}
+ @type membershipHash: C{str}
</ins><span class="cx"> """
</span><del>- return self._addGroupQuery.on(self, name=name,
- groupGUID=str(groupGUID), membershipHash=membershipHash)
</del><ins>+ return self._addGroupQuery.on(
+ self,
+ name=name.encode("utf-8"),
+ groupUID=groupUID.encode("utf-8"),
+ 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"> """
</span><del>- @type groupGUID: C{UUID}
</del><ins>+ @type groupUID: C{unicode}
+ @type name: C{unicode}
+ @type membershipHash: C{str}
</ins><span class="cx"> """
</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("utf-8"),
+ groupUID=groupUID.encode("utf-8"),
+ 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"> """
</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"> """
</span><del>- results = (yield self._groupByGUID.on(self, groupGUID=str(groupGUID)))
</del><ins>+ results = (
+ yield self._groupByUID.on(
+ self, groupUID=groupUID.encode("utf-8")
+ )
+ )
</ins><span class="cx"> if results:
</span><del>- returnValue(results[0])
</del><ins>+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # name
+ results[0][2], # membership hash
+ ))
</ins><span class="cx"> else:
</span><del>- savepoint = SavepointAction("groupByGUID")
</del><ins>+ savepoint = SavepointAction("groupByUID")
</ins><span class="cx"> yield savepoint.acquire(self)
</span><span class="cx"> try:
</span><del>- yield self.addGroup(groupGUID, "", "")
</del><ins>+ yield self.addGroup(groupUID, u"", "")
</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("utf-8")
+ )
+ )
</ins><span class="cx"> if results:
</span><del>- returnValue(results[0])
</del><ins>+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # 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("utf-8")
+ )
+ )
</ins><span class="cx"> if results:
</span><del>- returnValue(results[0])
</del><ins>+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # 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>+ """
+ 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}
+ """
</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("urn:uuid:" + results[0])] + results[1:]
</del><ins>+ results = (
+ results[0].decode("utf-8"),
+ results[1].decode("utf-8"),
+ 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("groupID"),
</span><del>- gm.MEMBER_GUID: Parameter("memberGUID")
</del><ins>+ gm.MEMBER_GUID: Parameter("memberUID")
</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("groupID")
</span><span class="cx"> ).And(
</span><del>- gm.MEMBER_GUID == Parameter("memberGUID")
</del><ins>+ gm.MEMBER_GUID == Parameter("memberUID")
</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("guid")
</del><ins>+ gr.GROUP_ID.In(
+ Select(
+ [gm.GROUP_ID],
+ From=gm,
+ Where=(
+ gm.MEMBER_GUID == Parameter("uid")
+ )
+ )
+ )
</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("utf-8")
</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("utf-8")
</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("urn:uuid:" + row[0]))
</del><ins>+ members.add(row[0].decode("utf-8"))
</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"> """
</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"> """
</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("utf-8")
+ )
+ )
</ins><span class="cx"> for row in results:
</span><del>- groups.add(row[0])
</del><ins>+ groups.add(row[0].decode("utf-8"))
</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("delegator")
+ ).And(
+ de.READ_WRITE == Parameter("readWrite")
+ )
+ )
+
+
+ @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("delegator")
+ ).And(
+ ds.READ_WRITE == Parameter("readWrite")
+ )
+ )
+
+
+ @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("delegator")
- ).And(
- ds.READ_WRITE == Parameter("readWrite")
</del><ins>+ gr.GROUP_ID.In(
+ Select(
+ [ds.GROUP_ID],
+ From=ds,
+ Where=(
+ ds.DELEGATOR == Parameter("delegator")
+ ).And(
+ ds.READ_WRITE == Parameter("readWrite")
+ )
+ )
+ )
</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"> """
</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("utf-8"),
+ delegate=delegate.encode("utf-8"),
</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"> """
</span><span class="cx"> return self._addDelegateGroupQuery.on(
</span><span class="cx"> self,
</span><del>- delegator=str(delegator),
</del><ins>+ delegator=delegator.encode("utf-8"),
</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"> """
</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("utf-8"),
+ delegate=delegate.encode("utf-8"),
</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):
+ """
+ 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}
+ """
+ return self._removeDelegatesQuery.on(
+ self,
+ delegator=delegator.encode("utf-8"),
+ readWrite=1 if readWrite else 0
+ )
+
+
</ins><span class="cx"> def removeDelegateGroup(self, delegator, delegateGroupID, readWrite):
</span><span class="cx"> """
</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"> """
</span><span class="cx"> return self._removeDelegateGroupQuery.on(
</span><span class="cx"> self,
</span><del>- delegator=str(delegator),
</del><ins>+ delegator=delegator.encode("utf-8"),
</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):
+ """
+ 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}
+ """
+ return self._removeDelegateGroupsQuery.on(
+ self,
+ delegator=delegator.encode("utf-8"),
+ 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"> """
</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"> """
</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("utf-8"),
</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("urn:uuid:" + row[0]))
</del><ins>+ delegates.add(row[0].decode("utf-8"))
</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("utf-8"),
+ readWrite=1 if readWrite else 0
+ )
</ins><span class="cx"> )
</span><del>- )
- for row in results:
- delegates.add(UUID("urn:uuid:" + row[0]))
</del><ins>+ for row in results:
+ delegates.add(row[0].decode("utf-8"))
</ins><span class="cx">
</span><ins>+ else:
+ # Get the directly-delegated-to groups
+ results = (
+ yield self._selectDelegateGroupsQuery.on(
+ self,
+ delegator=delegator.encode("utf-8"),
+ readWrite=1 if readWrite else 0
+ )
+ )
+ for row in results:
+ delegates.add(row[0].decode("utf-8"))
+
</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"> """
</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("utf-8"),
</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("urn:uuid:" + row[0]))
</del><ins>+ delegators.add(row[0].decode("utf-8"))
</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("utf-8"),
</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("urn:uuid:" + row[0]))
</del><ins>+ delegators.add(row[0].decode("utf-8"))
</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"> """
</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"> """
</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("urn:uuid:" + row[0]))
</del><ins>+ delegates.add(row[0].decode("utf-8"))
</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"> """
</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"> """
</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("utf-8")] = (
+ readDelegateUID.encode("utf-8") if readDelegateUID else None,
+ writeDelegateUID.encode("utf-8") 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"> """
</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 ""
</del><ins>+ readDelegateUID.encode("utf-8") if readDelegateUID else ""
</ins><span class="cx"> )
</span><span class="cx"> writeDelegateForDB = (
</span><del>- str(writeDelegateGUID) if writeDelegateGUID else ""
</del><ins>+ writeDelegateUID.encode("utf-8") if writeDelegateUID else ""
</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>+ """
+ @param uid: I'm going to assume uid is utf-8 encoded bytes
+ """
</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("utf-8"))
</ins><span class="cx"> if record is None:
</span><span class="cx"> raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".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("utf-8"))
</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>+ """
+ @param uid: I'm going to assume uid is utf-8 encoded bytes
+ """
</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("utf-8"))
</ins><span class="cx"> if record is None:
</span><span class="cx"> raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".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"> """
</span><span class="cx"> Client side of directory proxy
</span><span class="cx"> """
</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"> """
</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"> """
</span><span class="cx"> if not serializedFields:
</span><span class="cx"> return None
</span><span class="cx">
</span><ins>+ # print("FIELDS", 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 == "recordType": # Is there a better way?
+ fields[field] = self.recordType.lookupByName(value)
+
+ # print("AFTER:", 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 = "data/Logs/state/directory-proxy.sock"
</del><ins>+ # FIXME:
+ from twistedcaldav.config import config
+ path = config.DirectoryProxy.SocketPath
+ # path = "data/Logs/state/directory-proxy.sock"
</ins><span class="cx"> if getattr(self, "_connection", None) is None:
</span><span class="cx"> log.debug("Creating connection")
</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("Already have connection")
</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("Need to change shortName to unicode")
+ shortName = shortName.decode("utf-8")
+
+
</ins><span class="cx"> return self._call(
</span><span class="cx"> RecordWithShortNameCommand,
</span><span class="cx"> self._processSingleRecord,
</span><del>- recordType=recordType.description.encode("utf-8"),
</del><ins>+ recordType=recordType.name.encode("utf-8"),
</ins><span class="cx"> shortName=shortName.encode("utf-8")
</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("Need to change uid to unicode")
+ uid = uid.decode("utf-8")
+
</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("utf-8")
</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("utf-8")
</del><ins>+ recordType=recordType.name.encode("utf-8")
</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("utf-8")
</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("utf-8") 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("utf-8"),
+ searchTerm.encode("utf-8"),
+ matchFlags.name.encode("utf-8"),
+ matchType.name.encode("utf-8")
+ )
+ )
+ if recordType is not None:
+ recordType = recordType.name.encode("utf-8")
+
+ return self._call(
+ RecordsMatchingFieldsCommand,
+ self._processMultipleRecords,
+ fields=newFields,
+ operand=operand.name.encode("utf-8"),
+ recordType=recordType
+ )
+
+
+ def recordsFromExpression(self, expression):
+ raise NotImplementedError(
+ "This won't work until expressions are serializable to send "
+ "across AMP"
+ )
+
+
+
+@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("utf-8")
+ )
+
+
+ def groups(self):
+ return self.service._call(
+ GroupsCommand,
+ self.service._processMultipleRecords,
+ uid=self.uid.encode("utf-8")
+ )
+
+
+ def setMembers(self, members):
+ log.debug("DPS Client setMembers")
+ memberUIDs = [m.uid.encode("utf-8") for m in members]
+ return self.service._call(
+ SetMembersCommand,
+ lambda x: x['success'],
+ uid=self.uid.encode("utf-8"),
+ 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("negas"))
</span><span class="cx"> print("plain auth: {a}".format(a=authenticated))
</span><del>- """
- record = (yield ds.recordWithUID("__dre__"))
- print("uid: {r}".format(r=record))
- if record:
- authenticated = (yield record.verifyPlaintextPassword("erd"))
- print("plain auth: {a}".format(a=authenticated))
- record = (yield ds.recordWithGUID("A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"))
- print("guid: {r}".format(r=record))
- records = (yield ds.recordsWithRecordType(RecordType.user))
- print("recordType: {r}".format(r=records))
- records = (yield ds.recordsWithEmailAddress("cdaboo@bitbucket.calendarserver.org"))
- print("emailAddress: {r}".format(r=records))
- """
</del><span class="cx">
</span><ins>+ # record = (yield ds.recordWithUID("__dre__"))
+ # print("uid: {r}".format(r=record))
+ # if record:
+ # authenticated = (yield record.verifyPlaintextPassword("erd"))
+ # print("plain auth: {a}".format(a=authenticated))
</ins><span class="cx">
</span><ins>+ # record = yield ds.recordWithGUID(
+ # "A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"
+ # )
+ # print("guid: {r}".format(r=record))
</ins><span class="cx">
</span><ins>+ # records = yield ds.recordsWithRecordType(RecordType.user)
+ # print("recordType: {r}".format(r=records))
+
+ # records = yield ds.recordsWithEmailAddress(
+ # "cdaboo@bitbucket.calendarserver.org"
+ # )
+ # print("emailAddress: {r}".format(r=records))
+
+
+
</ins><span class="cx"> def succeeded(result):
</span><span class="cx"> print("yay")
</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 "License");
+# 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 "AS IS" 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
+
+"""
+JSON serialization utilities.
+"""
+
+__all__ = [
+ "expressionAsJSONText",
+ "expressionFromJSONText",
+]
+
+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(
+ "Unknown expression type: {!r}".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("JSON expression must be a dict.")
+
+ try:
+ json_type = json["type"]
+ except KeyError as e:
+ raise ValueError("JSON expression must have {!r} key.".format(e[0]))
+
+ if json_type == "CompoundExpression":
+ return compoundExpressionFromJSON(json)
+
+ if json_type == "MatchExpression":
+ return matchExpressionFromJSON(json)
+
+ raise NotImplementedError(
+ "Unknown expression type: {}".format(json_type)
+ )
+
+
+def compoundExpressionFromJSON(json):
+ try:
+ expressions_json = json["expressions"]
+ operand_json = json["operand"]
+ except KeyError as e:
+ raise ValueError(
+ "JSON compound expression must have {!r} key.".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["field"]
+ value_json = json["value"]
+ match_json = json["match"]
+ flags_json = json["flags"]
+ except KeyError as e:
+ raise ValueError(
+ "JSON match expression must have {!r} key.".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):
+ """
+ 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}
+ """
+ return dumps(obj, separators=(',', ':')).decode("UTF-8")
</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"> """
</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"> """
</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("%s: %s" % (field.name, value))
- valueType = self._directory.fieldName.valueType(field)
- if valueType is unicode:
</del><ins>+ valueType = record.service.fieldName.valueType(field)
+ # print("%s: %s (%s)" % (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("Server side fields", 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("utf-8")
</span><span class="cx"> log.debug("RecordWithShortName: {r} {n}", 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("RecordsWithRecordType: {r}", 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("utf-8") for t in tokens]
+ log.debug("RecordsMatchingTokens: {t}", t=(", ".join(tokens)))
+ records = yield self._directory.recordsMatchingTokens(
+ tokens, context=context
+ )
+ fieldsList = []
+ for record in records:
+ fieldsList.append(self.recordToDict(record))
+ response = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
+ @RecordsMatchingFieldsCommand.responder
+ @inlineCallbacks
+ def recordsMatchingFields(self, fields, operand="OR", recordType=None):
+ log.debug("RecordsMatchingFields")
+ newFields = []
+ for fieldName, searchTerm, matchFlags, matchType in fields:
+ fieldName = fieldName.decode("utf-8")
+ searchTerm = searchTerm.decode("utf-8")
+ 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("utf-8"))
+ matchType = MatchType.lookupByName(matchType.decode("utf-8"))
+ 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 = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
+ @MembersCommand.responder
+ @inlineCallbacks
+ def members(self, uid):
+ uid = uid.decode("utf-8")
+ log.debug("Members: {u}", u=uid)
+ try:
+ record = (yield self._directory.recordWithUID(uid))
+ except Exception as e:
+ log.error("Failed in members", error=e)
+ record = None
+
+ fieldsList = []
+ if record is not None:
+ for member in (yield record.members()):
+ fieldsList.append(self.recordToDict(member))
+ response = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
+ @SetMembersCommand.responder
+ @inlineCallbacks
+ def setMembers(self, uid, memberUIDs):
+ uid = uid.decode("utf-8")
+ memberUIDs = [m.decode("utf-8") for m in memberUIDs]
+ log.debug("Set Members: {u} -> {m}", u=uid, m=memberUIDs)
+ try:
+ record = (yield self._directory.recordWithUID(uid))
+ except Exception as e:
+ log.error("Failed in setMembers", 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 = {
+ "success": success,
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
+ @GroupsCommand.responder
+ @inlineCallbacks
+ def groups(self, uid):
+ uid = uid.decode("utf-8")
+ log.debug("Groups: {u}", u=uid)
+ try:
+ record = (yield self._directory.recordWithUID(uid))
+ except Exception as e:
+ log.error("Failed in groups", error=e)
+ record = None
+
+ fieldsList = []
+ for group in (yield record.groups()):
+ fieldsList.append(self.recordToDict(group))
+ response = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", 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("CalendarServer Directory Proxy Service")
</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("Failed to create directory service", error=e)
+ raise
</ins><span class="cx">
</span><del>- if directoryType == "OD":
- from twext.who.opendirectory import DirectoryService as ODDirectoryService
- directory = ODDirectoryService(*args, **kwds)
</del><ins>+ log.info("Created directory service")
</ins><span class="cx">
</span><del>- elif directoryType == "LDAP":
- authDN = kwds.pop("authDN", "")
- password = kwds.pop("password", "")
- if authDN and password:
- creds = UsernamePassword(authDN, password)
- else:
- creds = None
- kwds["credentials"] = creds
- debug = kwds.pop("debug", "")
- directory = LDAPDirectoryService(*args, _debug=debug, **kwds)
-
- elif directoryType == "XML":
- path = kwds.pop("path", "")
- if not path or not os.path.exists(path):
- log.error("Path not found for XML directory: {p}", p=path)
- fp = FilePath(path)
- directory = XMLDirectoryService(fp, *args, **kwds)
-
- else:
- log.error("Invalid DirectoryType: {dt}", dt=directoryType)
-
- desc = "unix:{path}:mode=660".format(
- path=config.DirectoryProxy.SocketPath
</del><ins>+ return strPortsService(
+ "unix:{path}:mode=660".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"> <record type="user">
</span><span class="cx"> <uid>__sagen__</uid>
</span><ins>+ <guid>B3B1158F-0564-4F5B-81E4-A89EA5FF81B0</guid>
</ins><span class="cx"> <short-name>sagen</short-name>
</span><span class="cx"> <full-name>Morgen Sagen</full-name>
</span><span class="cx"> <password>negas</password>
</span><span class="lines">@@ -113,4 +114,10 @@
</span><span class="cx"> <member-uid>__alyssa__</member-uid>
</span><span class="cx"> </record>
</span><span class="cx">
</span><ins>+ <record type="location">
+ <uid>__sanchezoffice__</uid>
+ <short-name>sanchezoffice</short-name>
+ <full-name>Sanchez Office</full-name>
+ </record>
+
</ins><span class="cx"> </directory>
</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 = "xml" # "xml" or "od"
</span><span class="cx"> if testMode == "xml":
</span><ins>+ testShortName = u"wsanchez"
+ testUID = u"__wsanchez__"
+ testPassword = u"zehcnasw"
</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 == "od":
</span><del>- odpw = "secret"
</del><ins>+ testShortName = u"becausedigest"
+ testUID = u"381D56CA-3B89-4AA1-942A-D4BFBC4F6F69"
+ testPassword = u"password"
</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):
+ """
+ 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)
+ """
+
</ins><span class="cx"> def setUp(self):
</span><span class="cx">
</span><span class="cx"> # The "local" directory service
</span><span class="lines">@@ -46,9 +79,9 @@
</span><span class="cx"> # The "remote" directory service
</span><span class="cx"> if testMode == "xml":
</span><span class="cx"> path = os.path.join(os.path.dirname(__file__), "test.xml")
</span><del>- remoteDirectory = XMLDirectoryService(FilePath(path))
</del><ins>+ remoteDirectory = CalendarXMLDirectoryService(FilePath(path))
</ins><span class="cx"> elif testMode == "od":
</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("__dre__"))
- self.assertEquals(record.shortNames, [u"dre"])
</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>- "wsanchez"
</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 == "od":
+ 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(
- "A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"
</del><ins>+ def test_recordType(self):
+ if testMode != "od":
+ records = (yield self.directory.recordsWithRecordType(
+ RecordType.user
+ ))
+ self.assertEquals(len(records), 9)
+
+
+ @inlineCallbacks
+ def test_emailAddress(self):
+ if testMode == "xml":
+ records = (yield self.directory.recordsWithEmailAddress(
+ u"cdaboo@bitbucket.calendarserver.org"
+ ))
+ self.assertEquals(len(records), 1)
+ self.assertEquals(records[0].shortNames, [u"cdaboo"])
+
+
+ @inlineCallbacks
+ def test_recordsMatchingTokens(self):
+ records = (yield self.directory.recordsMatchingTokens(
+ [u"anche"]
</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("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><ins>+ def test_recordsMatchingFields_anyType(self):
+ fields = (
+ (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
+ (u"fullNames", "morgen", 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("sagen" in matchingShortNames)
+ self.assertTrue("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ self.assertTrue("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_oneType(self):
+ fields = (
+ (u"fullNames", "anche", 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("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ # This location should *not* appear in the results
+ self.assertFalse("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_unsupportedField(self):
+ fields = (
+ (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
+ # This should be ignored:
+ (u"foo", "bar", 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("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ self.assertTrue("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_nonUnicode(self):
+ fields = (
+ (u"guid", uuid.UUID("A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"),
+ 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("dre" in matchingShortNames)
+ self.assertFalse("wsanchez" 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 = "Won't work until we can serialize expressions"
+
+
+ @inlineCallbacks
+ def test_verifyPlaintextPassword(self):
+ expectations = (
+ (testPassword, True), # Correct
+ ("wrong", 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
+ ("wrong", False) # Incorrect
+ )
+ record = (
+ yield self.directory.recordWithShortName(
+ RecordType.user,
+ testShortName
+ )
+ )
+
+ realm = "host.example.com"
+ nonce = "128446648710842461101646794502"
+ algorithm = "md5"
+ uri = "http://host.example.com"
+ method = "GET"
+
+ for password, answer in expectations:
+ for qop, nc, cnonce in (
+ ("", "", ""),
+ ("auth", "00000001", "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
+ ):
+ 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):
+ """
+ Similar to the above tests, but in the context of the directory structure
+ that directoryFromConfig() returns
+ """
+
+ wsanchezUID = u"6423F94A-6B76-4A3A-815B-D52CFD77935D"
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()
+
+ # The "local" directory service
+ self.client = DirectoryService(None)
+
+ # The "remote" 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, "_getConnection", 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, "_call", newCall)
+
+
+ def configure(self):
+ """
+ Override configuration hook to turn on wiki.
+ """
+ super(DPSClientAugmentedAggregateDirectoryTest, self).configure()
+ self.patch(config.Authentication.Wiki, "Enabled", True)
+
+
+ @inlineCallbacks
+ def test_uid(self):
+ record = (yield self.client.recordWithUID(self.wsanchezUID))
+ self.assertTrue(u"wsanchez" in record.shortNames)
+
+
+ @inlineCallbacks
+ def test_shortName(self):
+ record = (yield self.client.recordWithShortName(
+ RecordType.user,
+ u"wsanchez"
+ ))
+ self.assertEquals(record.uid, self.wsanchezUID)
+
+
+ def test_guid(self):
+ record = yield self.client.recordWithGUID(self.wsanchezUID)
+ self.assertTrue(u"wsanchez" 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(
- "cdaboo@bitbucket.calendarserver.org"
</del><ins>+ records = (yield self.client.recordsWithEmailAddress(
+ u"wsanchez@example.com"
</ins><span class="cx"> ))
</span><span class="cx"> self.assertEquals(len(records), 1)
</span><del>- self.assertEquals(records[0].shortNames, [u"cdaboo"])
</del><ins>+ self.assertEquals(records[0].shortNames, [u"wsanchez"])
</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"anche"]
+ ))
+ matchingShortNames = set()
+ for r in records:
+ for shortName in r.shortNames:
+ matchingShortNames.add(shortName)
+ self.assertTrue("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_anyType(self):
+ fields = (
+ (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
+ (u"fullNames", "morgen", 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("sagen" in matchingShortNames)
+ self.assertTrue("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ self.assertTrue("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_oneType(self):
+ fields = (
+ (u"fullNames", "anche", 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("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ # This location should *not* appear in the results
+ self.assertFalse("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsMatchingFields_unsupportedField(self):
+ fields = (
+ (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
+ # This should be ignored:
+ (u"foo", "bar", 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("dre" in matchingShortNames)
+ self.assertTrue("wsanchez" in matchingShortNames)
+ self.assertTrue("sanchezoffice" in matchingShortNames)
+
+
+ @inlineCallbacks
+ def test_recordsFromMatchExpression(self):
+ expression = MatchExpression(
+ FieldName.uid,
+ u"wsanchez",
+ MatchType.equals,
+ MatchFlags.none
+ )
+ records = yield self.client.recordsFromExpression(expression)
+ self.assertEquals(len(records), 1)
+
+ test_recordsFromMatchExpression.todo = "Won't work until we can serialize expressions"
+
+
+ @inlineCallbacks
</ins><span class="cx"> def test_verifyPlaintextPassword(self):
</span><del>- if testMode == "xml":
- expectations = (
- ("erd", True), # Correct
- ("wrong", False) # Incorrect
</del><ins>+ expectations = (
+ (u"zehcnasw", True), # Correct
+ ("wrong", False) # Incorrect
+ )
+ record = (
+ yield self.client.recordWithShortName(
+ RecordType.user,
+ u"wsanchez"
</ins><span class="cx"> )
</span><del>- record = (
- yield self.directory.recordWithShortName(RecordType.user, "dre")
- )
- elif testMode == "od":
- expectations = (
- (odpw, True), # Correct
- ("wrong", False) # Incorrect
- )
- record = (
- yield self.directory.recordWithGUID(
- "D0B38B00-4166-11DD-B22C-A07C87F02F6A"
- )
- )
</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 == "xml":
- username = "dre"
- expectations = (
- ("erd", True), # Correct
- ("wrong", False) # Incorrect
</del><ins>+ expectations = (
+ (u"zehcnasw", True), # Correct
+ ("wrong", False) # Incorrect
+ )
+ record = (
+ yield self.client.recordWithShortName(
+ RecordType.user,
+ u"wsanchez"
</ins><span class="cx"> )
</span><del>- record = (
- yield self.directory.recordWithShortName(RecordType.user, "dre")
- )
- elif testMode == "od":
- username = "sagen"
- expectations = (
- (odpw, True), # Correct
- ("wrong", False) # Incorrect
- )
- record = (
- yield self.directory.recordWithGUID(
- "D0B38B00-4166-11DD-B22C-A07C87F02F6A"
- )
- )
</del><ins>+ )
</ins><span class="cx">
</span><span class="cx"> realm = "host.example.com"
</span><span class="cx"> nonce = "128446648710842461101646794502"
</span><span class="lines">@@ -172,13 +513,13 @@
</span><span class="cx"> ("auth", "00000001", "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
</span><span class="cx"> ):
</span><span class="cx"> response = calcResponse(
</span><del>- calcHA1(algorithm, username, realm, password, nonce, cnonce),
</del><ins>+ calcHA1(algorithm, u"wsanchez", 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"wsanchez", 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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+Augmenting Directory Service
+"""
+
+__all__ = [
+ "AugmentedDirectoryService",
+]
+
+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
+):
+ """
+ Augmented directory service.
+
+ This is a directory service that wraps an L{IDirectoryService} and augments
+ directory records with additional or modified fields.
+ """
+
+ 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("Need to change uid to unicode")
+ uid = uid.decode("utf-8")
+
+ 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("Need to change shortName to unicode")
+ shortName = shortName.decode("utf-8")
+
+ 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("Need to change emailAddress to unicode")
+ emailAddress = emailAddress.decode("utf-8")
+
+ 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):
+ """
+ Pull out the augmented fields from each record, apply those to the
+ augments database, then update the base records.
+ """
+
+ 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: "none",
+ AutoScheduleMode.accept: "accept-always",
+ AutoScheduleMode.decline: "decline-always",
+ AutoScheduleMode.acceptIfFree: "accept-if-free",
+ AutoScheduleMode.declineIfBusy: "decline-if-busy",
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy: "automatic",
+ }.get(augmentFields.get(FieldName.autoScheduleMode, None), None)
+
+ kwargs = {
+ "uid": record.uid,
+ "autoScheduleMode": autoScheduleMode,
+ }
+ if FieldName.hasCalendars in augmentFields:
+ kwargs["enabledForCalendaring"] = augmentFields[FieldName.hasCalendars]
+ if FieldName.hasContacts in augmentFields:
+ kwargs["enabledForAddressBooks"] = augmentFields[FieldName.hasContacts]
+ if FieldName.loginAllowed in augmentFields:
+ kwargs["enabledForLogin"] = augmentFields[FieldName.loginAllowed]
+ if FieldName.autoAcceptGroup in augmentFields:
+ kwargs["autoAcceptGroup"] = augmentFields[FieldName.autoAcceptGroup]
+ if FieldName.serviceNodeUID in augmentFields:
+ kwargs["serverID"] = 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):
+ """
+ Returns a tuple of two dictionaries; the first contains all the non
+ augment fields, and the second contains all the augment fields.
+ """
+ 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("Got augment record", augmentRecord)
+
+ if augmentRecord:
+
+ self._assignToField(
+ fields, "hasCalendars",
+ augmentRecord.enabledForCalendaring
+ )
+
+ self._assignToField(
+ fields, "hasContacts",
+ augmentRecord.enabledForAddressBooks
+ )
+
+ autoScheduleMode = {
+ "none": AutoScheduleMode.none,
+ "accept-always": AutoScheduleMode.accept,
+ "decline-always": AutoScheduleMode.decline,
+ "accept-if-free": AutoScheduleMode.acceptIfFree,
+ "decline-if-busy": AutoScheduleMode.declineIfBusy,
+ "automatic": 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, "autoScheduleMode",
+ autoScheduleMode
+ )
+
+ if augmentRecord.autoAcceptGroup is not None:
+ self._assignToField(
+ fields, "autoAcceptGroup",
+ augmentRecord.autoAcceptGroup.decode("utf-8")
+ )
+
+ self._assignToField(
+ fields, "loginAllowed",
+ augmentRecord.enabledForLogin
+ )
+
+ self._assignToField(
+ fields, "serviceNodeUID",
+ augmentRecord.serverID.decode("utf-8")
+ )
+
+ if (
+ (
+ fields.get(
+ self.fieldName.lookupByName("hasCalendars"), False
+ ) or
+ fields.get(
+ self.fieldName.lookupByName("hasContacts"), False
+ )
+ ) and
+ record.recordType == RecordType.group
+ ):
+ self._assignToField(fields, "hasCalendars", False)
+ self._assignToField(fields, "hasContacts", False)
+
+ # For augment records cloned from the Default augment record,
+ # don't emit this message:
+ if not augmentRecord.clonedFromDefault:
+ log.error(
+ "Group {record} cannot be enabled for "
+ "calendaring or address books",
+ record=record
+ )
+
+ else:
+ self._assignToField(fields, "hasCalendars", False)
+ self._assignToField(fields, "hasContacts", False)
+ self._assignToField(fields, "loginAllowed", False)
+
+ # print("Augmented fields", fields)
+
+ # Clone to a new record with the augmented fields
+ returnValue(AugmentedDirectoryRecord(self, record, fields))
+
+
+
+class AugmentedDirectoryRecord(DirectoryRecord, CalendarDirectoryRecordMixin):
+ """
+ Augmented directory record.
+ """
+
+ 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"> """
</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):
+ """
+ Constants for read-only delegates and read-write delegate groups
+ """
+
+ readDelegateGroup = NamedConstant()
+ readDelegateGroup.description = u"read-delegate-group"
+
+ writeDelegateGroup = NamedConstant()
+ writeDelegateGroup.description = u"write-delegate-group"
+
+ readDelegatorGroup = NamedConstant()
+ readDelegatorGroup.description = u"read-delegator-group"
+
+ writeDelegatorGroup = NamedConstant()
+ writeDelegatorGroup.description = u"write-delegator-group"
+
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+
+ @inlineCallbacks
+ def members(self, expanded=False):
+ """
+ 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.
+ """
+ parentUID, proxyType = self.uid.split(u"#")
+
+ 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):
+ """
+ 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
+ """
+ if self.recordType not in (
+ RecordType.readDelegateGroup, RecordType.writeDelegateGroup
+ ):
+ raise NotAllowedError("Setting members not supported")
+
+ parentUID, proxyType = self.uid.split(u"#")
+ readWrite = (self.recordType is RecordType.writeDelegateGroup)
+
+ log.debug(
+ "Setting delegate assignments for {u} ({rw}) to {m}",
+ u=parentUID, rw=("write" if readWrite else "read"),
+ 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: "calendar-proxy-read",
+ RecordType.writeDelegateGroup: "calendar-proxy-write",
+ RecordType.readDelegatorGroup: "calendar-proxy-read-for",
+ RecordType.writeDelegatorGroup: "calendar-proxy-write-for",
+ }.get(recordType, None)
+
+
+def proxyTypeToRecordType(proxyType):
+ return {
+ "calendar-proxy-read": RecordType.readDelegateGroup,
+ "calendar-proxy-write": RecordType.writeDelegateGroup,
+ "calendar-proxy-read-for": RecordType.readDelegatorGroup,
+ "calendar-proxy-write-for": RecordType.writeDelegatorGroup,
+ }.get(proxyType, None)
+
+
+
+class DirectoryService(BaseDirectoryService):
+ """
+ Delegate directory service
+ """
+
+ 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 + "#" + recordTypeToProxyType(recordType)
+
+ record = DirectoryRecord(self, {
+ FieldName.uid: uid,
+ FieldName.recordType: recordType,
+ FieldName.shortNames: (uid,),
+ })
+ return succeed(record)
+
+
+ def recordWithUID(self, uid):
+ if "#" not in uid: # Not a delegate group uid
+ return succeed(None)
+ uid, proxyType = uid.split("#")
+ recordType = proxyTypeToRecordType(proxyType)
+ if recordType is None:
+ return succeed(None)
+ return self.recordWithShortName(recordType, uid)
+
+
+ @inlineCallbacks
+ def recordsFromExpression(self, expression, records=None):
+ """
+ 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.
+ """
+ if isinstance(expression, MatchExpression):
+ if(
+ (expression.fieldName is FieldName.uid) and
+ (expression.matchType is MatchType.equals) and
+ ("#" 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"> """
</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"> """
</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"> """
</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"> """
</span><span class="cx"> Return the records of the delegates of "delegator". The type of access
</span><span class="cx"> is specified by the "readWrite" parameter.
</span><span class="lines">@@ -83,10 +272,12 @@
</span><span class="cx"> """
</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"> """
</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):
- """
- @return: the GUIDs of all groups which are currently delegated to
- @rtype: a Deferred which fires with a set() of GUID strings
- """
- 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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+Calendar/Contacts specific methods for DirectoryRecord
+"""
+
+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__ = [
+ "CalendarDirectoryRecordMixin",
+ "CalendarDirectoryServiceMixin",
+]
+
+
+
+class CalendarDirectoryServiceMixin(object):
+
+ guid = "1332A615-4D3A-41FE-B636-FBE25BFB982E"
+
+ # Must maintain the hack for a bit longer:
+ def setPrincipalCollection(self, principalCollection):
+ """
+ Set the principal service that the directory relies on for doing proxy tests.
+
+ @param principalService: the principal service.
+ @type principalService: L{DirectoryProvisioningResource}
+ """
+ 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("urn:uuid:"):
+ try:
+ guid = uuid.UUID(address[9:])
+ except ValueError:
+ log.info("Invalid GUID: {guid}", guid=address[9:])
+ returnValue(None)
+ record = yield self.recordWithGUID(guid)
+ elif address.startswith("mailto:"):
+ records = yield self.recordsWithEmailAddress(address[7:])
+ if records:
+ returnValue(records[0])
+ else:
+ returnValue(None)
+ elif address.startswith("/principals/"):
+ parts = address.split("/")
+ if len(parts) == 4:
+ if parts[2] == "__uids__":
+ 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 = [
+ ("fullNames", MatchType.contains),
+ ("emailAddresses", 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):
+ """
+ @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})
+ """
+ subExpressions = []
+ for fieldName, searchTerm, matchFlags, matchType in fields:
+ try:
+ field = self.fieldName.lookupByName(fieldName)
+ except ValueError:
+ log.debug(
+ "Unsupported field name: {fieldName}",
+ 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 = {
+ "address": "addresses",
+ "group": "groups",
+ "location": "locations",
+ "resource": "resources",
+ "user": "users",
+ "macOSXServerWiki": "wikis",
+ "readDelegateGroup": "readDelegateGroups",
+ "writeDelegateGroup": "writeDelegateGroups",
+ "readDelegatorGroup": "readDelegatorGroups",
+ "writeDelegatorGroup": "writeDelegatorGroups",
+ }
+
+
+ # Maps record types <--> url path segments, i.e. the segment after
+ # /principals/ e.g. "users" or "groups"
+
+ 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):
+ """
+ Calendar (and Contacts) specific logic for directory records lives in this
+ class
+ """
+
+
+ @inlineCallbacks
+ def verifyCredentials(self, credentials):
+
+ if isinstance(credentials, UsernamePassword):
+ log.debug("UsernamePassword")
+ returnValue(
+ (yield self.verifyPlaintextPassword(credentials.password))
+ )
+
+ elif isinstance(credentials, DigestedCredentials):
+ log.debug("DigestedCredentials")
+ returnValue(
+ (yield self.verifyHTTPDigest(
+ self.shortNames[0],
+ self.service.realmName,
+ credentials.fields["uri"],
+ credentials.fields["nonce"],
+ credentials.fields.get("cnonce", ""),
+ credentials.fields["algorithm"],
+ credentials.fields.get("nc", ""),
+ credentials.fields.get("qop", ""),
+ credentials.fields["response"],
+ credentials.method
+ ))
+ )
+
+
+ @property
+ def calendarUserAddresses(self):
+ try:
+ if not self.hasCalendars:
+ return frozenset()
+ except AttributeError:
+ pass
+
+ try:
+ cuas = set(
+ ["mailto:%s" % (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("urn:uuid:{guid}".format(guid=guid))
+ except AttributeError:
+ # No guid
+ pass
+ cuas.add(u"/principals/__uids__/{uid}/".format(uid=self.uid))
+ for shortName in self.shortNames:
+ cuas.add(u"/principals/{rt}/{sn}/".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, "UNKNOWN")
+
+
+ @classmethod
+ def fromCUType(cls, cuType):
+ for key, val in cls._cuTypes.iteritems():
+ if val == cuType:
+ return key
+ return None
+
+
+ def applySACLs(self):
+ """
+ Disable calendaring and addressbooks as dictated by SACLs
+ """
+
+ # FIXME: need to re-implement SACLs
+ # if config.EnableSACLs and self.CheckSACL:
+ # username = self.shortNames[0]
+ # if self.CheckSACL(username, "calendar") != 0:
+ # self.log.debug("%s is not enabled for calendaring due to SACL"
+ # % (username,))
+ # self.enabledForCalendaring = False
+ # if self.CheckSACL(username, "addressbook") != 0:
+ # self.log.debug("%s is not enabled for addressbooks due to SACL"
+ # % (username,))
+ # self.enabledForAddressBooks = False
+
+
+ @property
+ def displayName(self):
+ return self.fullNames[0]
+
+
+ def cacheToken(self):
+ """
+ Generate a token that can be uniquely used to identify the state of this record for use
+ in a cache.
+ """
+ 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):
+ """
+ Return a CUA for this record, preferring in this order:
+ urn:uuid: form
+ mailto: form
+ /principals/__uids__/ form
+ first in calendarUserAddresses list (sorted)
+ """
+
+ sortedCuas = sorted(self.calendarUserAddresses)
+
+ for prefix in (
+ "urn:uuid:",
+ "mailto:",
+ "/principals/__uids__/"
+ ):
+ 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):
+ """
+ URL of the server hosting this record. Return None if hosted on this server.
+ """
+ # FIXME:
+ from twistedcaldav.config import config
+
+ if config.Servers.Enabled and getattr(self, "serviceNodeUID", None):
+ return Servers.getServerURIById(self.serviceNodeUID)
+ else:
+ return None
+
+
+ def server(self):
+ """
+ Server hosting this record. Return None if hosted on this server.
+ """
+ # FIXME:
+ from twistedcaldav.config import config
+
+ if config.Servers.Enabled and getattr(self, "serviceNodeUID", 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() != "INDIVIDUAL" 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, "_groupCacher", None)
- newGroupCacher = getattr(self.transaction, "_newGroupCacher", None)
- if oldGroupCacher is not None or newGroupCacher is not None:
</del><ins>+ groupCacher = getattr(self.transaction, "_groupCacher", 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"> "Scheduling next group cacher update: {when}", 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"> "Failed to update new group membership cache ({error})",
</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(
- "Failed to update old group membership cache ({error})",
- 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 "groupGuid", 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, "_groupCacher", 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("utf-8")
+ )
</ins><span class="cx"> except Exception, e:
</span><span class="cx"> log.error(
</span><span class="cx"> "Failed to refresh group {group} {err}",
</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"> "Rescheduling group refresh for {group}: {when}",
</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):
- """
- Return the expanded set of member records. Intermediate groups are not
- returned in the results, but their members are.
- """
- 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) != "groups"
- ):
- members.add(member)
- yield expandedMembers(member, members, records)
-
- returnValue(members)
-
-
-
</del><span class="cx"> def diffAssignments(old, new):
</span><span class="cx"> """
</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"> """
</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>- "Number of groups to refresh: {num}", num=len(groupGUIDs)
</del><ins>+ "Number of groups to refresh: {num}", 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("Enqueuing group refresh for {u}", 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("Enqueued group refresh for {u}", 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(
- "Group '{group}' changed", group=record.fullNames[0]
- )
</del><ins>+ self.log.debug("Faulting in group: {g}", 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("Group has disappeared: {g}", g=groupUID)
</ins><span class="cx"> else:
</span><del>- membershipChanged = False
</del><ins>+ self.log.debug("Got group record: {u}", 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(
+ "Group '{group}' changed", 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"> """
</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"> """
</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"> """
</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"> """
</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"> "scheduling group reconciliation for "
</span><del>- "({eventID}, {groupID}, {groupGUID}): {when}",
</del><ins>+ "({eventID}, {groupID}, {groupUID}): {when}",
</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>- "There are {count} group delegates", count=len(delegatedGUIDs)
</del><ins>+ "There are {count} group delegates", 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"> """
</span><span class="cx">
</span><span class="cx"> __all__ = [
</span><ins>+ "AutoScheduleMode",
+ "RecordType",
+ "FieldName",
</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"auto-accept group"
</span><span class="cx"> autoAcceptGroup.valueType = BaseFieldName.valueType(BaseFieldName.uid)
</span><ins>+
+ # For "locations", i.e., scheduled spaces:
+
+ associatedAddress = NamedConstant()
+ associatedAddress.description = u"associated address UID"
+
+ capacity = NamedConstant()
+ capacity.description = u"room capacity"
+ capacity.valueType = int
+
+ floor = NamedConstant()
+ floor.description = u"building floor"
+
+ # For "addresses", i.e., non-scheduled areas containing locations:
+
+ abbreviatedName = NamedConstant()
+ abbreviatedName.description = u"abbreviated name"
+
+ geographicLocation = NamedConstant()
+ geographicLocation.description = u"geographic location URI"
+
+ streetAddress = NamedConstant()
+ streetAddress.description = u"street address"
</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"> <!DOCTYPE accounts SYSTEM "accounts.dtd">
</span><span class="cx">
</span><del>-<accounts realm="Test Realm">
- <user>
</del><ins>+<directory realm="Test Realm">
+ <record type="user">
</ins><span class="cx"> <uid>admin</uid>
</span><del>- <guid>admin</guid>
</del><ins>+ <short-name>admin</short-name>
</ins><span class="cx"> <password>admin</password>
</span><del>- <name>Super User</name>
- <first-name>Super</first-name>
- <last-name>User</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Super User</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>apprentice</uid>
</span><del>- <guid>apprentice</guid>
</del><ins>+ <short-name>apprentice</short-name>
</ins><span class="cx"> <password>apprentice</password>
</span><del>- <name>Apprentice Super User</name>
- <first-name>Apprentice</first-name>
- <last-name>Super User</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Apprentice Super User</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>wsanchez</uid>
</span><del>- <guid>wsanchez</guid>
- <email-address>wsanchez@example.com</email-address>
</del><ins>+ <short-name>wsanchez</short-name>
+ <email>wsanchez@example.com</email>
</ins><span class="cx"> <password>test</password>
</span><del>- <name>Wilfredo Sanchez Vega</name>
- <first-name>Wilfredo</first-name>
- <last-name>Sanchez Vega</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Wilfredo Sanchez Vega</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>cdaboo</uid>
</span><del>- <guid>cdaboo</guid>
- <email-address>cdaboo@example.com</email-address>
</del><ins>+ <short-name>cdaboo</short-name>
+ <email>cdaboo@example.com</email>
</ins><span class="cx"> <password>test</password>
</span><del>- <name>Cyrus Daboo</name>
- <first-name>Cyrus</first-name>
- <last-name>Daboo</last-name>
- </user>
- <user>
</del><ins>+ <full-name>cyrus Daboo</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>sagen</uid>
</span><del>- <guid>sagen</guid>
- <email-address>sagen@example.com</email-address>
</del><ins>+ <short-name>sagen</short-name>
+ <email>sagen@example.com</email>
</ins><span class="cx"> <password>test</password>
</span><del>- <name>Morgen Sagen</name>
- <first-name>Morgen</first-name>
- <last-name>Sagen</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Morgen Sagen</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>dre</uid>
</span><del>- <guid>andre</guid>
- <email-address>dre@example.com</email-address>
</del><ins>+ <short-name>andre</short-name>
+ <email>dre@example.com</email>
</ins><span class="cx"> <password>test</password>
</span><del>- <name>Andre LaBranche</name>
- <first-name>Andre</first-name>
- <last-name>LaBranche</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Andre LaBranche</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>glyph</uid>
</span><del>- <guid>glyph</guid>
- <email-address>glyph@example.com</email-address>
</del><ins>+ <short-name>glyph</short-name>
+ <email>glyph@example.com</email>
</ins><span class="cx"> <password>test</password>
</span><del>- <name>Glyph Lefkowitz</name>
- <first-name>Glyph</first-name>
- <last-name>Lefkowitz</last-name>
- </user>
- <user>
</del><ins>+ <full-name>Glyph Lefkowitz</full-name>
+ </record>
+ <record type="user">
</ins><span class="cx"> <uid>i18nuser</uid>
</span><del>- <guid>i18nuser</guid>
- <email-address>i18nuser@example.com</email-address>
</del><ins>+ <short-name>i18nuser</short-name>
+ <email>i18nuser@example.com</email>
</ins><span class="cx"> <password>i18nuser</password>
</span><del>- <name>まだ</name>
- <first-name>ま</first-name>
- <last-name>だ</last-name>
- </user>
</del><ins>+ <full-name>まだ</full-name>
+ </record>
+
+ <!-- twext.who xml doesn't (yet) support repeat
</ins><span class="cx"> <user repeat="101">
</span><span class="cx"> <uid>user%02d</uid>
</span><span class="cx"> <uid>User %02d</uid>
</span><del>- <guid>user%02d</guid>
</del><ins>+ <short-name>user%02d</short-name>
</ins><span class="cx"> <password>user%02d</password>
</span><del>- <name>User %02d</name>
- <first-name>User</first-name>
- <last-name>%02d</last-name>
- <email-address>user%02d@example.com</email-address>
- </user>
</del><ins>+ <full-name>User %02d</full-name>
+ <email>user%02d@example.com</email>
+ </record>
</ins><span class="cx"> <user repeat="10">
</span><span class="cx"> <uid>public%02d</uid>
</span><del>- <guid>public%02d</guid>
</del><ins>+ <short-name>public%02d</short-name>
</ins><span class="cx"> <password>public%02d</password>
</span><del>- <name>Public %02d</name>
- <first-name>Public</first-name>
- <last-name>%02d</last-name>
- </user>
- <group>
</del><ins>+ <full-name>Public %02d</full-name>
+ </record>
+ -->
+ <record type="user">
+ <short-name>user01</short-name>
+ <uid>user01</uid>
+ <password>user01</password>
+ <full-name>User 01</full-name>
+ <email>user01@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user02</short-name>
+ <uid>user02</uid>
+ <password>user02</password>
+ <full-name>User 02</full-name>
+ <email>user02@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user03</short-name>
+ <uid>user03</uid>
+ <password>user03</password>
+ <full-name>User 03</full-name>
+ <email>user03@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user04</short-name>
+ <uid>user04</uid>
+ <password>user04</password>
+ <full-name>User 04</full-name>
+ <email>user04@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user05</short-name>
+ <uid>user05</uid>
+ <password>user05</password>
+ <full-name>User 05</full-name>
+ <email>user05@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user06</short-name>
+ <uid>user06</uid>
+ <password>user06</password>
+ <full-name>User 06</full-name>
+ <email>user06@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user07</short-name>
+ <uid>user07</uid>
+ <password>user07</password>
+ <full-name>User 07</full-name>
+ <email>user07@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user08</short-name>
+ <uid>user08</uid>
+ <password>user08</password>
+ <full-name>User 08</full-name>
+ <email>user08@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user09</short-name>
+ <uid>user09</uid>
+ <password>user09</password>
+ <full-name>User 09</full-name>
+ <email>user09@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user10</short-name>
+ <uid>user10</uid>
+ <password>user10</password>
+ <full-name>User 10</full-name>
+ <email>user10@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user11</short-name>
+ <uid>user11</uid>
+ <password>user11</password>
+ <full-name>User 11</full-name>
+ <email>user11@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user12</short-name>
+ <uid>user12</uid>
+ <password>user12</password>
+ <full-name>User 12</full-name>
+ <email>user12@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user13</short-name>
+ <uid>user13</uid>
+ <password>user13</password>
+ <full-name>User 13</full-name>
+ <email>user13@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user14</short-name>
+ <uid>user14</uid>
+ <password>user14</password>
+ <full-name>User 14</full-name>
+ <email>user14@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user15</short-name>
+ <uid>user15</uid>
+ <password>user15</password>
+ <full-name>User 15</full-name>
+ <email>user15@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user16</short-name>
+ <uid>user16</uid>
+ <password>user16</password>
+ <full-name>User 16</full-name>
+ <email>user16@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user17</short-name>
+ <uid>user17</uid>
+ <password>user17</password>
+ <full-name>User 17</full-name>
+ <email>user17@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user18</short-name>
+ <uid>user18</uid>
+ <password>user18</password>
+ <full-name>User 18</full-name>
+ <email>user18@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user19</short-name>
+ <uid>user19</uid>
+ <password>user19</password>
+ <full-name>User 19</full-name>
+ <email>user19@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user20</short-name>
+ <uid>user20</uid>
+ <password>user20</password>
+ <full-name>User 20</full-name>
+ <email>user20@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user21</short-name>
+ <uid>user21</uid>
+ <password>user21</password>
+ <full-name>User 21</full-name>
+ <email>user21@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user22</short-name>
+ <uid>user22</uid>
+ <password>user22</password>
+ <full-name>User 22</full-name>
+ <email>user22@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user23</short-name>
+ <uid>user23</uid>
+ <password>user23</password>
+ <full-name>User 23</full-name>
+ <email>user23@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user24</short-name>
+ <uid>user24</uid>
+ <password>user24</password>
+ <full-name>User 24</full-name>
+ <email>user24@example.com</email>
+ </record>
+
+ <record type="user">
+ <short-name>user25</short-name>
+ <uid>user25</uid>
+ <password>user25</password>
+ <full-name>User 25</full-name>
+ <email>user25@example.com</email>
+ </record>
+
+ <record type="group">
</ins><span class="cx"> <uid>group01</uid>
</span><del>- <guid>group01</guid>
</del><ins>+ <short-name>group01</short-name>
</ins><span class="cx"> <password>group01</password>
</span><del>- <name>Group 01</name>
- <email-address>group01@example.com</email-address>
- <members>
- <member type="users">user01</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Group 01</full-name>
+ <member-uid type="users">user01</member-uid>
+ </record>
+ <record type="group">
</ins><span class="cx"> <uid>group02</uid>
</span><del>- <guid>group02</guid>
</del><ins>+ <short-name>group02</short-name>
</ins><span class="cx"> <password>group02</password>
</span><del>- <name>Group 02</name>
- <email-address>group02@example.com</email-address>
- <members>
- <member type="users">user06</member>
- <member type="users">user07</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Group 02</full-name>
+ <member-uid type="users">user06</member-uid>
+ <member-uid type="users">user07</member-uid>
+ </record>
+ <record type="group">
</ins><span class="cx"> <uid>group03</uid>
</span><del>- <guid>group03</guid>
</del><ins>+ <short-name>group03</short-name>
</ins><span class="cx"> <password>group03</password>
</span><del>- <name>Group 03</name>
- <members>
- <member type="users">user08</member>
- <member type="users">user09</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Group 03</full-name>
+ <member-uid type="users">user08</member-uid>
+ <member-uid type="users">user09</member-uid>
+ </record>
+ <record type="group">
</ins><span class="cx"> <uid>group04</uid>
</span><del>- <guid>group04</guid>
</del><ins>+ <short-name>group04</short-name>
</ins><span class="cx"> <password>group04</password>
</span><del>- <name>Group 04</name>
- <members>
- <member type="groups">group02</member>
- <member type="groups">group03</member>
- <member type="users">user10</member>
- </members>
- </group>
- <group> <!-- delegategroup -->
</del><ins>+ <full-name>Group 04</full-name>
+ <member-uid type="groups">group02</member-uid>
+ <member-uid type="groups">group03</member-uid>
+ <member-uid type="users">user10</member-uid>
+ </record>
+ <record type="group"> <!-- delegategroup -->
</ins><span class="cx"> <uid>group05</uid>
</span><del>- <guid>group05</guid>
</del><ins>+ <short-name>group05</short-name>
</ins><span class="cx"> <password>group05</password>
</span><del>- <name>Group 05</name>
- <members>
- <member type="groups">group06</member>
- <member type="users">user20</member>
- </members>
- </group>
- <group> <!-- delegatesubgroup -->
</del><ins>+ <full-name>Group 05</full-name>
+ <member-uid type="groups">group06</member-uid>
+ <member-uid type="users">user20</member-uid>
+ </record>
+ <record type="group"> <!-- delegatesubgroup -->
</ins><span class="cx"> <uid>group06</uid>
</span><del>- <guid>group06</guid>
</del><ins>+ <short-name>group06</short-name>
</ins><span class="cx"> <password>group06</password>
</span><del>- <name>Group 06</name>
- <members>
- <member type="users">user21</member>
- </members>
- </group>
- <group> <!-- readonlydelegategroup -->
</del><ins>+ <full-name>Group 06</full-name>
+ <member-uid type="users">user21</member-uid>
+ </record>
+ <record type="group"> <!-- readonlydelegategroup -->
</ins><span class="cx"> <uid>group07</uid>
</span><del>- <guid>group07</guid>
</del><ins>+ <short-name>group07</short-name>
</ins><span class="cx"> <password>group07</password>
</span><del>- <name>Group 07</name>
- <members>
- <member type="users">user22</member>
- <member type="users">user23</member>
- <member type="users">user24</member>
- </members>
- </group>
- <group>
</del><ins>+ <full-name>Group 07</full-name>
+ <member-uid type="users">user22</member-uid>
+ <member-uid type="users">user23</member-uid>
+ <member-uid type="users">user24</member-uid>
+ </record>
+ <record type="group">
</ins><span class="cx"> <uid>disabledgroup</uid>
</span><del>- <guid>disabledgroup</guid>
</del><ins>+ <short-name>disabledgroup</short-name>
</ins><span class="cx"> <password>disabledgroup</password>
</span><del>- <name>Disabled Group</name>
- <members>
- <member type="users">user01</member>
- </members>
- </group>
-</accounts>
</del><ins>+ <full-name>Disabled Group</full-name>
+ <member-uid type="users">user01</member-uid>
+ </record>
+</directory>
</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>+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE augments SYSTEM "augments.dtd">
+
+<augments>
+ <record>
+ <uid>Default</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ </record>
+ <record repeat="10">
+ <uid>location%02d</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record repeat="4">
+ <uid>resource%02d</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record>
+ <uid>resource05</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>none</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource06</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-always</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource07</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>decline-always</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource08</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>accept-if-free</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource09</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>decline-if-busy</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource10</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>automatic</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>resource11</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>decline-always</auto-schedule-mode>
+ <auto-accept-group>group01</auto-accept-group>
+ </record>
+ <record repeat="10">
+ <uid>group%02d</uid>
+ <enable>true</enable>
+ </record>
+ <record>
+ <uid>disabledgroup</uid>
+ <enable>false</enable>
+ </record>
+ <record>
+ <uid>delegatedroom</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>false</enable-addressbook>
+ <auto-schedule>false</auto-schedule>
+ </record>
+ <record>
+ <uid>03DFF660-8BCC-4198-8588-DD77F776F518</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record>
+ <uid>80689D41-DAF8-4189-909C-DB017B271892</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ <auto-accept-group>group01</auto-accept-group>
+ </record>
+ <record>
+ <uid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record>
+ <uid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ </record>
+ <record>
+ <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>false</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+ <record>
+ <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ <enable-login>true</enable-login>
+ <auto-schedule>true</auto-schedule>
+ <auto-schedule-mode>default</auto-schedule-mode>
+ </record>
+</augments>
</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>-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-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 "AS IS" 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.
- -->
-
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-
-<accounts realm="Test Realm">
- <location repeat="10">
- <uid>location%02d</uid>
- <guid>location%02d</guid>
- <password>location%02d</password>
- <name>Room %02d</name>
- </location>
- <resource repeat="10">
- <uid>resource%02d</uid>
- <guid>resource%02d</guid>
- <password>resource%02d</password>
- <name>Resource %02d</name>
- </resource>
-</accounts>
</del><ins>+<directory realm="Test Realm">
+ <record type="location">
+ <short-name>fantastic</short-name>
+ <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
+ <full-name>Fantastic Conference Room</full-name>
+ <extras>
+ <associatedAddress>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</associatedAddress>
+ </extras>
+ </record>
+ <record type="location">
+ <short-name>jupiter</short-name>
+ <uid>jupiter</uid>
+ <full-name>Jupiter Conference Room, Building 2, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>uranus</short-name>
+ <uid>uranus</uid>
+ <full-name>Uranus Conference Room, Building 3, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>morgensroom</short-name>
+ <uid>03DFF660-8BCC-4198-8588-DD77F776F518</uid>
+ <full-name>Morgen's Room</full-name>
+ </record>
+ <record type="location">
+ <short-name>mercury</short-name>
+ <uid>mercury</uid>
+ <full-name>Mercury Conference Room, Building 1, 2nd Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>location09</short-name>
+ <uid>location09</uid>
+ <full-name>Room 09</full-name>
+ </record>
+ <record type="location">
+ <short-name>location08</short-name>
+ <uid>location08</uid>
+ <full-name>Room 08</full-name>
+ </record>
+ <record type="location">
+ <short-name>location07</short-name>
+ <uid>location07</uid>
+ <full-name>Room 07</full-name>
+ </record>
+ <record type="location">
+ <short-name>location06</short-name>
+ <uid>location06</uid>
+ <full-name>Room 06</full-name>
+ </record>
+ <record type="location">
+ <short-name>location05</short-name>
+ <uid>location05</uid>
+ <full-name>Room 05</full-name>
+ </record>
+ <record type="location">
+ <short-name>location04</short-name>
+ <uid>location04</uid>
+ <full-name>Room 04</full-name>
+ </record>
+ <record type="location">
+ <short-name>location03</short-name>
+ <uid>location03</uid>
+ <full-name>Room 03</full-name>
+ </record>
+ <record type="location">
+ <short-name>location02</short-name>
+ <uid>location02</uid>
+ <full-name>Room 02</full-name>
+ </record>
+ <record type="location">
+ <short-name>location01</short-name>
+ <uid>location01</uid>
+ <full-name>Room 01</full-name>
+ </record>
+ <record type="location">
+ <short-name>delegatedroom</short-name>
+ <uid>delegatedroom</uid>
+ <full-name>Delegated Conference Room</full-name>
+ </record>
+ <record type="location">
+ <short-name>mars</short-name>
+ <uid>redplanet</uid>
+ <full-name>Mars Conference Room, Building 1, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>sharissroom</short-name>
+ <uid>80689D41-DAF8-4189-909C-DB017B271892</uid>
+ <full-name>Shari's Room</full-name>
+ <extras>
+ <associatedAddress>6F9EE33B-78F6-481B-9289-3D0812FF0D64</associatedAddress>
+ </extras>
+ </record>
+ <record type="location">
+ <short-name>pluto</short-name>
+ <uid>pluto</uid>
+ <full-name>Pluto Conference Room, Building 2, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>saturn</short-name>
+ <uid>saturn</uid>
+ <full-name>Saturn Conference Room, Building 2, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>location10</short-name>
+ <uid>location10</uid>
+ <full-name>Room 10</full-name>
+ </record>
+ <record type="location">
+ <short-name>pretend</short-name>
+ <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
+ <full-name>Pretend Conference Room</full-name>
+ <extras>
+ <associatedAddress>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</associatedAddress>
+ </extras>
+ </record>
+ <record type="location">
+ <short-name>neptune</short-name>
+ <uid>neptune</uid>
+ <full-name>Neptune Conference Room, Building 2, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>Earth</short-name>
+ <uid>Earth</uid>
+ <full-name>Earth Conference Room, Building 1, 1st Floor</full-name>
+ </record>
+ <record type="location">
+ <short-name>venus</short-name>
+ <uid>venus</uid>
+ <full-name>Venus Conference Room, Building 1, 2nd Floor</full-name>
+ </record>
+ <record type="resource">
+ <short-name>sharisotherresource</short-name>
+ <uid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</uid>
+ <full-name>Shari's Other Resource</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource15</short-name>
+ <uid>resource15</uid>
+ <full-name>Resource 15</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource14</short-name>
+ <uid>resource14</uid>
+ <full-name>Resource 14</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource17</short-name>
+ <uid>resource17</uid>
+ <full-name>Resource 17</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource16</short-name>
+ <uid>resource16</uid>
+ <full-name>Resource 16</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource11</short-name>
+ <uid>resource11</uid>
+ <full-name>Resource 11</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource10</short-name>
+ <uid>resource10</uid>
+ <full-name>Resource 10</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource13</short-name>
+ <uid>resource13</uid>
+ <full-name>Resource 13</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource12</short-name>
+ <uid>resource12</uid>
+ <full-name>Resource 12</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource19</short-name>
+ <uid>resource19</uid>
+ <full-name>Resource 19</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource18</short-name>
+ <uid>resource18</uid>
+ <full-name>Resource 18</full-name>
+ </record>
+ <record type="resource">
+ <short-name>sharisresource</short-name>
+ <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
+ <full-name>Shari's Resource</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource20</short-name>
+ <uid>resource20</uid>
+ <full-name>Resource 20</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource06</short-name>
+ <uid>resource06</uid>
+ <full-name>Resource 06</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource07</short-name>
+ <uid>resource07</uid>
+ <full-name>Resource 07</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource04</short-name>
+ <uid>resource04</uid>
+ <full-name>Resource 04</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource05</short-name>
+ <uid>resource05</uid>
+ <full-name>Resource 05</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource02</short-name>
+ <uid>resource02</uid>
+ <full-name>Resource 02</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource03</short-name>
+ <uid>resource03</uid>
+ <full-name>Resource 03</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource01</short-name>
+ <uid>resource01</uid>
+ <full-name>Resource 01</full-name>
+ </record>
+ <record type="resource">
+ <short-name>sharisotherresource1</short-name>
+ <uid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</uid>
+ <full-name>Shari's Other Resource1</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource08</short-name>
+ <uid>resource08</uid>
+ <full-name>Resource 08</full-name>
+ </record>
+ <record type="resource">
+ <short-name>resource09</short-name>
+ <uid>resource09</uid>
+ <full-name>Resource 09</full-name>
+ </record>
+ <record type="address">
+ <short-name>testaddress1</short-name>
+ <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
+ <full-name>Test Address One</full-name>
+ <extras>
+ <streetAddress>20300 Stevens Creek Blvd, Cupertino, CA 95014</streetAddress>
+ <geo>37.322281,-122.028345</geo>
+ </extras>
+ </record>
+ <record type="address">
+ <short-name>il2</short-name>
+ <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
+ <full-name>IL2</full-name>
+ <extras>
+ <streetAddress>2 Infinite Loop, Cupertino, CA 95014</streetAddress>
+ <geo>37.332633,-122.030502</geo>
+ </extras>
+ </record>
+ <record type="address">
+ <short-name>il1</short-name>
+ <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
+ <full-name>IL1</full-name>
+ <extras>
+ <streetAddress>1 Infinite Loop, Cupertino, CA 95014</streetAddress>
+ <geo>37.331741,-122.030333</geo>
+ </extras>
+ </record>
+</directory>
</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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+Augment service tests
+"""
+
+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):
+ """
+ Make sure augmented record groups( ) returns only the groups that have
+ been refreshed.
+ """
+
+ store = self.storeUnderTest()
+
+ txn = store.newTransaction()
+ yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")
+ yield txn.commit()
+ record = yield self.directory.recordWithUID(u"__sagen1__")
+ groups = yield record.groups()
+ self.assertEquals(
+ set(["__top_group_1__"]),
+ set([g.uid for g in groups])
+ )
+
+ txn = store.newTransaction()
+ yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")
+ yield txn.commit()
+
+ record = yield self.directory.recordWithUID(u"__sagen1__")
+ groups = yield record.groups()
+ self.assertEquals(
+ set(["__top_group_1__", "__sub_group_1__"]),
+ 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"> """
</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"__wsanchez__")
- delegate1 = yield self.xmlService.recordWithUID(u"__sagen__")
- delegate2 = yield self.xmlService.recordWithUID(u"__cdaboo__")
</del><ins>+ delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
+ delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
+ delegate2 = yield self.directory.recordWithUID(u"__cdaboo1__")
</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(["sagen"], [d.shortNames[0] for d in delegates])
</del><ins>+ self.assertEquals([u"__sagen1__"], [d.uid for d in delegates])
</ins><span class="cx"> delegators = (yield delegatedTo(txn, delegate1, True))
</span><del>- self.assertEquals(["wsanchez"], [d.shortNames[0] for d in delegators])
</del><ins>+ self.assertEquals([u"__wsanchez1__"], [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 "proxy-write" pseudoGroup will have one member
+ pseudoGroup = yield self.directory.recordWithShortName(
+ DelegateRecordType.writeDelegateGroup,
+ u"__wsanchez1__"
+ )
+ self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-write")
+ self.assertEquals(
+ [r.uid for r in (yield pseudoGroup.members())],
+ [u"__sagen1__"]
+ )
+ # The "proxy-read" pseudoGroup will have no members
+ pseudoGroup = yield self.directory.recordWithShortName(
+ DelegateRecordType.readDelegateGroup,
+ u"__wsanchez1__"
+ )
+ self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-read")
+ self.assertEquals(
+ [r.uid for r in (yield pseudoGroup.members())],
+ []
+ )
+ # The "proxy-write-for" pseudoGroup will have one member
+ pseudoGroup = yield self.directory.recordWithShortName(
+ DelegateRecordType.writeDelegatorGroup,
+ u"__sagen1__"
+ )
+ self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-write-for")
+ self.assertEquals(
+ [r.uid for r in (yield pseudoGroup.members())],
+ [u"__wsanchez1__"]
+ )
+ # The "proxy-read-for" pseudoGroup will have no members
+ pseudoGroup = yield self.directory.recordWithShortName(
+ DelegateRecordType.readDelegatorGroup,
+ u"__sagen1__"
+ )
+ self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-read-for")
+ 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(["sagen", "cdaboo"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__", u"__cdaboo1__"]),
+ 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(["wsanchez"], [d.shortNames[0] for d in delegators])
</del><ins>+ self.assertEquals([u"__wsanchez1__"], [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(["cdaboo"], [d.shortNames[0] for d in delegates])
</del><ins>+ self.assertEquals([u"__cdaboo1__"], [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"__wsanchez1__"
+ )
+ yield pseudoGroup.setMembers([delegate1, delegate2])
+
+ # Verify the assignments were made
+ txn = self.store.newTransaction()
+ delegates = (yield delegatesOf(txn, delegator, True))
+ self.assertEquals(
+ set([u"__sagen1__", u"__cdaboo1__"]),
+ 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"__cdaboo1__"]),
+ 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"__wsanchez__")
- delegate1 = yield self.xmlService.recordWithUID(u"__sagen__")
- group1 = yield self.xmlService.recordWithUID(u"__top_group_1__")
- group2 = yield self.xmlService.recordWithUID(u"__sub_group_1__")
</del><ins>+ delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
+ delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
+ group1 = yield self.directory.recordWithUID(u"__top_group_1__")
+ group2 = yield self.directory.recordWithUID(u"__sub_group_1__")
</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"__top_group_1__")
+ # 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 = "49b350c69611477b94d95516b13856ab"
</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(["sagen", "cdaboo", "glyph"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
+ 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(["wsanchez"], [d.shortNames[0] for d in delegators])
</del><ins>+ self.assertEquals([u"__wsanchez1__"], [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("49b350c69611477b94d95516b13856ab"),
- UUID("86144f73345a409782f1b782672087c7")
- ]), 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(["sagen", "cdaboo", "glyph"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
+ 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"wsanchez", u"cdaboo", u"sagen", u"glyph", u"dre"):
</del><ins>+ for name in (u"wsanchez1", u"cdaboo1", u"sagen1", u"glyph1", u"dre1"):
</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(["sagen", "cdaboo", "glyph", "dre"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__", u"__dre1__"]),
+ 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(["sagen", "cdaboo"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__", u"__cdaboo1__"]),
+ 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(["sagen"]),
- set([d.shortNames[0] for d in delegates])
</del><ins>+ set([u"__sagen1__"]),
+ set([d.uid for d in delegates])
</ins><span class="cx"> )
</span><del>-
-
-
-testXMLConfig = """<?xml version="1.0" encoding="utf-8"?>
-
-<directory realm="xyzzy">
-
- <record type="user">
- <uid>__wsanchez__</uid>
- <guid>3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F</guid>
- <short-name>wsanchez</short-name>
- <short-name>wilfredo_sanchez</short-name>
- <full-name>Wilfredo Sanchez</full-name>
- <password>zehcnasw</password>
- <email>wsanchez@bitbucket.calendarserver.org</email>
- <email>wsanchez@devnull.twistedmatrix.com</email>
- </record>
-
- <record type="user">
- <uid>__glyph__</uid>
- <guid>9064DF91-1DBC-4E07-9C2B-6839B0953876</guid>
- <short-name>glyph</short-name>
- <full-name>Glyph Lefkowitz</full-name>
- <password>hpylg</password>
- <email>glyph@bitbucket.calendarserver.org</email>
- <email>glyph@devnull.twistedmatrix.com</email>
- </record>
-
- <record type="user">
- <uid>__sagen__</uid>
- <guid>4AD155CB-AE9B-475F-986C-E08A7537893E</guid>
- <short-name>sagen</short-name>
- <full-name>Morgen Sagen</full-name>
- <password>negas</password>
- <email>sagen@bitbucket.calendarserver.org</email>
- <email>shared@example.com</email>
- </record>
-
- <record type="user">
- <uid>__cdaboo__</uid>
- <guid>7D45CB10-479E-456B-B54D-528958C5734B</guid>
- <short-name>cdaboo</short-name>
- <full-name>Cyrus Daboo</full-name>
- <password>suryc</password>
- <email>cdaboo@bitbucket.calendarserver.org</email>
- </record>
-
- <record type="user">
- <uid>__dre__</uid>
- <guid>CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B</guid>
- <short-name>dre</short-name>
- <full-name>Andre LaBranche</full-name>
- <password>erd</password>
- <email>dre@bitbucket.calendarserver.org</email>
- <email>shared@example.com</email>
- </record>
-
- <record type="group">
- <uid>__top_group_1__</uid>
- <guid>49B350C6-9611-477B-94D9-5516B13856AB</guid>
- <short-name>top-group-1</short-name>
- <full-name>Top Group 1</full-name>
- <email>topgroup1@example.com</email>
- <member-uid>__wsanchez__</member-uid>
- <member-uid>__glyph__</member-uid>
- <member-uid>__sub_group_1__</member-uid>
- </record>
-
- <record type="group">
- <uid>__sub_group_1__</uid>
- <guid>86144F73-345A-4097-82F1-B782672087C7</guid>
- <short-name>sub-group-1</short-name>
- <full-name>Sub Group 1</full-name>
- <email>subgroup1@example.com</email>
- <member-uid>__sagen__</member-uid>
- <member-uid>__cdaboo__</member-uid>
- </record>
-
-</directory>
-"""
</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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+Directory tests
+"""
+
+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"both_coasts")
+
+ direct = yield record.members()
+ self.assertEquals(
+ set([u"left_coast", u"right_coast"]),
+ set([r.uid for r in direct])
+ )
+
+ expanded = yield record.expandedMembers()
+ self.assertEquals(
+ set([u"Chris Lecroy", u"Cyrus Daboo", u"David Reid", u"Wilfredo Sanchez"]),
+ set([r.displayName for r in expanded])
+ )
+
+
+ def test_canonicalCalendarUserAddress(self):
+
+ record = TestDirectoryRecord(
+ self.directory,
+ {
+ FieldName.uid: u"uid",
+ FieldName.shortNames: [u"name"],
+ FieldName.recordType: RecordType.user,
+ }
+ )
+ self.assertEquals(
+ record.canonicalCalendarUserAddress(),
+ u"/principals/__uids__/uid/"
+ )
+
+
+ record = TestDirectoryRecord(
+ self.directory,
+ {
+ FieldName.uid: u"uid",
+ FieldName.shortNames: [u"name"],
+ FieldName.emailAddresses: [u"test@example.com"],
+ FieldName.recordType: RecordType.user,
+ }
+ )
+ self.assertEquals(
+ record.canonicalCalendarUserAddress(),
+ u"mailto:test@example.com"
+ )
+
+
+ record = TestDirectoryRecord(
+ self.directory,
+ {
+ FieldName.uid: u"uid",
+ FieldName.guid: UUID("E2F6C57F-BB15-4EF9-B0AC-47A7578386F1"),
+ FieldName.shortNames: [u"name"],
+ FieldName.emailAddresses: [u"test@example.com"],
+ FieldName.recordType: RecordType.user,
+ }
+ )
+ self.assertEquals(
+ record.canonicalCalendarUserAddress(),
+ u"urn:uuid:E2F6C57F-BB15-4EF9-B0AC-47A7578386F1"
+ )
+
+
+ @inlineCallbacks
+ def test_recordsFromMatchExpression(self):
+ expression = MatchExpression(
+ FieldName.uid,
+ u"6423F94A-6B76-4A3A-815B-D52CFD77935D",
+ 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("6423F94A-6B76-4A3A-815B-D52CFD77935D"),
+ 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"> """
</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):
- """
- Verify expandedMembers() returns a "flattened" set of records
- belonging to a group (and does not return sub-groups themselves,
- only their members)
- """
- record = yield self.xmlService.recordWithUID(u"__top_group_1__")
- memberUIDs = set()
- for member in (yield expandedMembers(record)):
- memberUIDs.add(member.uid)
- self.assertEquals(
- memberUIDs,
- set(["__cdaboo__", "__glyph__", "__sagen__", "__wsanchez__"])
- )
-
- # Non group records return an empty set() of members
- record = yield self.xmlService.recordWithUID(u"__sagen__")
- members = yield expandedMembers(record)
- self.assertEquals(0, len(list(members)))
-
-
- @inlineCallbacks
</del><span class="cx"> def test_refreshGroup(self):
</span><span class="cx"> """
</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"__top_group_1__")
- yield self.groupCacher.refreshGroup(txn, record.guid)
</del><ins>+ record = yield self.directory.recordWithUID(u"__top_group_1__")
+ yield self.groupCacher.refreshGroup(txn, record.uid)
</ins><span class="cx">
</span><del>- groupID, name, membershipHash = (yield txn.groupByGUID(record.guid))
- self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
</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, "Top Group 1")
- self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
</del><ins>+ self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
</ins><span class="cx">
</span><ins>+ groupUID, name, membershipHash = (yield txn.groupByID(groupID))
+ self.assertEquals(groupUID, record.uid)
+ self.assertEquals(name, u"Top Group 1")
+ self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
+
</ins><span class="cx"> members = (yield txn.membersOfGroup(groupID))
</span><span class="cx"> self.assertEquals(
</span><del>- set([UUID("9064df911dbc4e079c2b6839b0953876"),
- UUID("4ad155cbae9b475f986ce08a7537893e"),
- UUID("3bdcb95484d54f6d8035eac19a6d6e1f"),
- UUID("7d45cb10479e456bb54d528958c5734b")]),
</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(["wsanchez", "cdaboo", "glyph", "sagen"])
</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"__sagen__")
- groups = (yield self.groupCacher.cachedGroupsFor(txn, record.guid))
- self.assertEquals(set([groupID]), groups)
</del><ins>+ record = yield self.directory.recordWithUID(u"__sagen1__")
+ groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
+ self.assertEquals(set([u"__top_group_1__"]), 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("49b350c69611477b94d95516b13856ab")
- yield self.groupCacher.refreshGroup(txn, guid)
- groupID, name, membershipHash = (yield txn.groupByGUID(guid))
</del><ins>+ uid = u"__top_group_1__"
+ 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"wsanchez", u"cdaboo", u"dre"):
</del><ins>+ for name in (u"wsanchez1", u"cdaboo1", u"dre1"):
</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(["wsanchez", "cdaboo", "dre"])
</del><ins>+ set(["wsanchez1", "cdaboo1", "dre1"])
</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("49b350c69611477b94d95516b13856ab")
- hash = "4b0e162f2937f0f3daa6d10e5a6a6c33"
- yield self.groupCacher.refreshGroup(txn, guid)
- groupID, name, membershipHash = (yield txn.groupByGUID(guid))
</del><ins>+ uid = u"__top_group_1__"
+ hash = "553eb54e3bbb26582198ee04541dbee4"
+ 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, "Top Group 1", hash], results)
</del><ins>+ self.assertEquals((uid, u"Top Group 1", 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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
- (None, UUID("49B350C6-9611-477B-94D9-5516B13856AB"))
</del><ins>+ u"__wsanchez1__": (None, u"__top_group_1__")
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
</del><ins>+ u"__wsanchez1__":
</ins><span class="cx"> (
</span><span class="cx"> None,
</span><del>- UUID("49B350C6-9611-477B-94D9-5516B13856AB")
</del><ins>+ u"__top_group_1__"
</ins><span class="cx"> )
</span><span class="cx"> }
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> newAssignments = {
</span><del>- UUID("7D45CB10-479E-456B-B54D-528958C5734B"):
</del><ins>+ u"__cdaboo1__":
</ins><span class="cx"> (
</span><del>- UUID("86144F73-345A-4097-82F1-B782672087C7"),
</del><ins>+ u"__sub_group_1__",
</ins><span class="cx"> None
</span><span class="cx"> ),
</span><del>- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
</del><ins>+ u"__wsanchez1__":
</ins><span class="cx"> (
</span><del>- UUID("86144F73-345A-4097-82F1-B782672087C7"),
- UUID("49B350C6-9611-477B-94D9-5516B13856AB")
</del><ins>+ u"__sub_group_1__",
+ u"__top_group_1__"
</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"__wsanchez1__":
</ins><span class="cx"> (
</span><del>- UUID('86144f73-345a-4097-82f1-b782672087c7'),
- UUID('49b350c6-9611-477b-94d9-5516b13856ab')
</del><ins>+ u"__sub_group_1__",
+ u"__top_group_1__"
</ins><span class="cx"> ),
</span><del>- UUID('7d45cb10-479e-456b-b54d-528958c5734b'):
</del><ins>+ u"__cdaboo1__":
</ins><span class="cx"> (
</span><del>- UUID('86144f73-345a-4097-82f1-b782672087c7'),
</del><ins>+ u"__sub_group_1__",
</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"__top_group_1__",
+ u"__sub_group_1__"
</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"__sub_group_1__")
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
</del><ins>+ u"__wsanchez1__", 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"__sagen1__",
+ u"__cdaboo1__"
</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"__top_group_1__")
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
</del><ins>+ u"__wsanchez1__", 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"__wsanchez1__",
+ u"__sagen1__",
+ u"__cdaboo1__",
+ u"__glyph1__"
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
</del><ins>+ u"__wsanchez1__":
</ins><span class="cx"> (
</span><del>- UUID("86144F73-345A-4097-82F1-B782672087C7"),
</del><ins>+ u"__sub_group_1__",
</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"__wsanchez1__":
</ins><span class="cx"> (
</span><del>- UUID('86144f73-345a-4097-82f1-b782672087c7'),
</del><ins>+ u"__sub_group_1__",
</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"__sub_group_1__"
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
</del><ins>+ u"__wsanchez1__", 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"__sagen1__",
+ u"__cdaboo1__"
</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("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
</del><ins>+ u"__wsanchez1__", 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"__sub_group_1__"
</ins><span class="cx"> ]
</span><span class="cx"> )
</span><span class="cx"> )
</span><span class="lines">@@ -417,81 +389,3 @@
</span><span class="cx"> {"D": ("7", "8"), "C": ("4", "5"), "A": ("1", "2")},
</span><span class="cx"> )
</span><span class="cx"> )
</span><del>-
-testXMLConfig = """<?xml version="1.0" encoding="utf-8"?>
-
-<directory realm="xyzzy">
-
- <record type="user">
- <uid>__wsanchez__</uid>
- <guid>3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F</guid>
- <short-name>wsanchez</short-name>
- <short-name>wilfredo_sanchez</short-name>
- <full-name>Wilfredo Sanchez</full-name>
- <password>zehcnasw</password>
- <email>wsanchez@bitbucket.calendarserver.org</email>
- <email>wsanchez@devnull.twistedmatrix.com</email>
- </record>
-
- <record type="user">
- <uid>__glyph__</uid>
- <guid>9064DF91-1DBC-4E07-9C2B-6839B0953876</guid>
- <short-name>glyph</short-name>
- <full-name>Glyph Lefkowitz</full-name>
- <password>hpylg</password>
- <email>glyph@bitbucket.calendarserver.org</email>
- <email>glyph@devnull.twistedmatrix.com</email>
- </record>
-
- <record type="user">
- <uid>__sagen__</uid>
- <guid>4AD155CB-AE9B-475F-986C-E08A7537893E</guid>
- <short-name>sagen</short-name>
- <full-name>Morgen Sagen</full-name>
- <password>negas</password>
- <email>sagen@bitbucket.calendarserver.org</email>
- <email>shared@example.com</email>
- </record>
-
- <record type="user">
- <uid>__cdaboo__</uid>
- <guid>7D45CB10-479E-456B-B54D-528958C5734B</guid>
- <short-name>cdaboo</short-name>
- <full-name>Cyrus Daboo</full-name>
- <password>suryc</password>
- <email>cdaboo@bitbucket.calendarserver.org</email>
- </record>
-
- <record type="user">
- <uid>__dre__</uid>
- <guid>CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B</guid>
- <short-name>dre</short-name>
- <full-name>Andre LaBranche</full-name>
- <password>erd</password>
- <email>dre@bitbucket.calendarserver.org</email>
- <email>shared@example.com</email>
- </record>
-
- <record type="group">
- <uid>__top_group_1__</uid>
- <guid>49B350C6-9611-477B-94D9-5516B13856AB</guid>
- <short-name>top-group-1</short-name>
- <full-name>Top Group 1</full-name>
- <email>topgroup1@example.com</email>
- <member-uid>__wsanchez__</member-uid>
- <member-uid>__glyph__</member-uid>
- <member-uid>__sub_group_1__</member-uid>
- </record>
-
- <record type="group">
- <uid>__sub_group_1__</uid>
- <guid>86144F73-345A-4097-82F1-B782672087C7</guid>
- <short-name>sub-group-1</short-name>
- <full-name>Sub Group 1</full-name>
- <email>subgroup1@example.com</email>
- <member-uid>__sagen__</member-uid>
- <member-uid>__cdaboo__</member-uid>
- </record>
-
-</directory>
-"""
</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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+txdav.who.util tests
+"""
+
+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("accounts")
+ self.serverRoot = os.path.abspath(self.mktemp())
+ os.mkdir(self.serverRoot)
+ self.dataRoot = os.path.join(self.serverRoot, "data")
+ if not os.path.exists(self.dataRoot):
+ os.makedirs(self.dataRoot)
+ destDir = FilePath(self.dataRoot)
+
+ accounts = destDir.child("accounts.xml")
+ sourceAccounts = sourceDir.child("accounts.xml")
+ accounts.setContent(sourceAccounts.getContent())
+
+ resources = destDir.child("resources.xml")
+ sourceResources = sourceDir.child("resources.xml")
+ resources.setContent(sourceResources.getContent())
+
+ augments = destDir.child("augments.xml")
+ sourceAugments = sourceDir.child("augments.xml")
+ augments.setContent(sourceAugments.getContent())
+
+
+ @inlineCallbacks
+ def test_directoryFromConfig(self):
+
+ config = ConfigDict(
+ {
+ "DataRoot": self.dataRoot,
+ "Authentication": {
+ "Wiki": {
+ "Enabled": True,
+ "CollabHost": "localhost",
+ "CollabPort": 4444,
+ },
+ },
+ "DirectoryService": {
+ "Enabled": True,
+ "type": "XML",
+ "params": {
+ "xmlFile": "accounts.xml",
+ "recordTypes": ["users", "groups"],
+ },
+ },
+ "ResourceService": {
+ "Enabled": True,
+ "type": "XML",
+ "params": {
+ "xmlFile": "resources.xml",
+ "recordTypes": ["locations", "resources", "addresses"],
+ },
+ },
+ "AugmentService": {
+ "Enabled": True,
+ # FIXME: This still uses an actual class name:
+ "type": "twistedcaldav.directory.augment.AugmentXMLDB",
+ "params": {
+ "xmlFiles": ["augments.xml"],
+ },
+ },
+ }
+ )
+
+ 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("group07")
+ 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 "License");
+# 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 "AS IS" 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
+
+"""
+Tests for L{txdav.who.wiki}.
+"""
+
+
+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):
+ """
+ Instantiate a wiki service directly
+ """
+
+ @inlineCallbacks
+ def test_service(self):
+ service = DirectoryService("realm", "localhost", 4444)
+ record = yield service.recordWithUID(u"[wiki]test")
+ self.assertEquals(
+ record.shortNames[0],
+ u"test"
+ )
+
+
+
+class WikiAggregateServiceTestCase(StoreTestCase):
+ """
+ Get a wiki service as part of directoryFromConfig
+ """
+
+ def configure(self):
+ """
+ Override configuration hook to turn on wiki service.
+ """
+ from twistedcaldav.config import config
+
+ super(WikiAggregateServiceTestCase, self).configure()
+ self.patch(config.Authentication.Wiki, "Enabled", True)
+
+
+ @inlineCallbacks
+ def test_service(self):
+ record = yield self.directory.recordWithUID(u"[wiki]test")
+ self.assertEquals(
+ record.shortNames[0],
+ u"test"
+ )
+
+
+
+class AccessForRecordTestCase(StoreTestCase):
+ """
+ Exercise accessForRecord
+ """
+
+ def configure(self):
+ """
+ Override configuration hook to turn on wiki service.
+ """
+ from twistedcaldav.config import config
+
+ super(AccessForRecordTestCase, self).configure()
+ self.patch(config.Authentication.Wiki, "Enabled", True)
+ self.patch(
+ txdav.who.wiki,
+ "accessForUserToWiki",
+ self.stubAccessForUserToWiki
+ )
+
+
+ def stubAccessForUserToWiki(self, *args, **kwds):
+ return succeed(self.access)
+
+
+ @inlineCallbacks
+ def test_accessForRecord(self):
+ record = yield self.directory.recordWithUID(u"[wiki]test")
+
+ self.access = "no-access"
+ access = yield record.accessForRecord(None)
+ self.assertEquals(access, WikiAccessLevel.none)
+
+ self.access = "read"
+ access = yield record.accessForRecord(None)
+ self.assertEquals(access, WikiAccessLevel.read)
+
+ self.access = "write"
+ access = yield record.accessForRecord(None)
+ self.assertEquals(access, WikiAccessLevel.write)
+
+ self.access = "admin"
+ 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 "License");
+# 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 "AS IS" 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):
+ """
+ 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
+ """
+
+ # 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 ("DirectoryService", "ResourceService"):
+ serviceValue = config.get(serviceKey, None)
+
+ if not serviceValue.Enabled:
+ continue
+
+ directoryType = serviceValue.type.lower()
+ params = serviceValue.params
+
+ # TODO: add a "test" directory service that produces test records
+ # from code -- no files needed.
+
+ if "xml" in directoryType:
+ xmlFile = params.xmlFile
+ xmlFile = fullServerPath(config.DataRoot, xmlFile)
+ if not xmlFile or not os.path.exists(xmlFile):
+ log.error("Path not found for XML directory: {p}", p=xmlFile)
+ fp = FilePath(xmlFile)
+ directory = XMLDirectoryService(fp)
+
+ elif "opendirectory" in directoryType:
+ from twext.who.opendirectory import (
+ DirectoryService as ODDirectoryService
+ )
+ directory = ODDirectoryService()
+
+ elif "ldap" 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("Invalid DirectoryType: {dt}", dt=directoryType)
+ raise DirectoryConfigurationError
+
+ # Set the appropriate record types on each service
+ types = []
+ fieldNames = []
+ for recordTypeName in params.recordTypes:
+ recordType = {
+ "users": RecordType.user,
+ "groups": RecordType.group,
+ "locations": CalRecordType.location,
+ "resources": CalRecordType.resource,
+ "addresses": CalRecordType.address,
+ }.get(recordTypeName, None)
+
+ if recordType is None:
+ log.error("Invalid Record Type: {rt}", rt=recordTypeName)
+ raise DirectoryConfigurationError
+
+ if recordType in types:
+ log.error("Duplicate Record Type: {rt}", 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(
+ "Configuring augment service of type: {augmentClass}",
+ augmentClass=augmentClass
+ )
+ try:
+ augmentService = augmentClass(**config.AugmentService.params)
+ except IOError:
+ log.error("Could not start augment service")
+ raise
+ else:
+ augmentService = None
+
+ userDirectory = None
+ for directory in aggregatedServices:
+ if RecordType.user in directory.recordTypes():
+ userDirectory = directory
+ break
+ else:
+ log.error("No directory service set up for users")
+ 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("Could not create directory service", 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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+ Utilities to converting a Record to a vCard
+"""
+
+__all__ = [
+ "vCardFromRecord"
+]
+
+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: "individual",
+ RecordType.group: "group",
+ CalRecordType.location: "location",
+ CalRecordType.resource: "device",
+}
+
+vCardKindToRecordTypeMap = {
+ "individual" : RecordType.user,
+ "group": RecordType.group,
+ "org": RecordType.group,
+ "location": CalRecordType.location,
+ "device": CalRecordType.resource,
+}
+
+
+# all possible generated parameters.
+vCardPropToParamMap = {
+ #"PHOTO": {"ENCODING": ("B",), "TYPE": ("JPEG",), },
+ "ADR": {"TYPE": ("WORK", "PREF", "POSTAL", "PARCEL",), },
+ #"LABEL": {"TYPE": ("POSTAL", "PARCEL",)},
+ #"TEL": {"TYPE": None, }, # None means param can contain can be anything
+ "EMAIL": {"TYPE": None, },
+ #"KEY": {"ENCODING": ("B",), "TYPE": ("PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE",)},
+ #"URL": {"TYPE": ("WEBLOG", "HOMEPAGE",)},
+ #"IMPP": {"TYPE": ("PREF",), "X-SERVICE-TYPE": None, },
+ #"X-ABRELATEDNAMES": {"TYPE": None, },
+ #"X-AIM": {"TYPE": ("PREF",), },
+ #"X-JABBER": {"TYPE": ("PREF",), },
+ #"X-MSN": {"TYPE": ("PREF",), },
+ #"X-ICQ": {"TYPE": ("PREF",), },
+}
+
+
+vCardConstantProperties = {
+ #===================================================================
+ # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
+ #===================================================================
+ # 3.6.3 PRODID
+ "PRODID": vCardProductID,
+ # 3.6.9 VERSION
+ "VERSION": "3.0",
+}
+
+
+@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(
+ "Ignoring property {prop!r} it is a duplicate",
+ prop=newProperty
+ )
+
+ #=======================================================================
+ # start
+ #=======================================================================
+
+ log.debug("vCardFromRecord: record={record}, forceKind={forceKind}, addProps={addProps}, parentURI={parentURI}",
+ record=record, forceKind=forceKind, addProps=addProps, parentURI=parentURI)
+
+ if forceKind is None:
+ kind = recordTypeToVCardKindMap.get(record.recordType, "individual")
+ 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("VCARD")
+
+ # 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("utf-8") + ".vcf")
+
+ # seems like this should be in some standard place.
+ if config.EnableSSL and config.SSLPort:
+ if config.SSLPort == 443:
+ source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
+ else:
+ source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.SSLPort, uri=uri)
+ else:
+ if config.HTTPPort == 80:
+ source = "https://{server}{uri}".format(server=config.ServerHostName, uri=uri)
+ else:
+ source = "https://{server}:{port}{uri}".format(server=config.ServerHostName, port=config.HTTPPort, uri=uri)
+ vcard.addProperty(Property("SOURCE", source))
+
+ #===================================================================
+ # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
+ #===================================================================
+ # 3.1.1 FN
+ vcard.addProperty(Property("FN", record.fields[FieldName.fullNames][0].encode("utf-8")))
+
+ # 3.1.2 N
+ # TODO: Better parsing
+ fullNameParts = record.fields[FieldName.fullNames][0].split()
+ first = fullNameParts[0] if len(fullNameParts) >= 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("utf-8") if first else None,
+ last=last.encode("utf-8") if last else None,
+ middle=middle.encode("utf-8") if middle else None,
+ prefix=prefix.encode("utf-8") if prefix else None,
+ suffix=suffix.encode("utf-8") if suffix else None,
+ )
+ vcard.addProperty(Property("N", nameObject))
+
+ # 3.1.3 NICKNAME
+ nickname = record.fields.get(CalFieldName.abbreviatedName)
+ if nickname:
+ vcard.addProperty(Property("NICKNAME", nickname.encode("utf-8")))
+
+ # 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(
+ "ADR", Adr(
+ #pobox = box,
+ extended=extended.encode("utf-8") if extended else None,
+ street=street.encode("utf-8") if street else None,
+ locality=city.encode("utf-8") if city else None,
+ region=region.encode("utf-8") if region else None,
+ postalcode=postalcode.encode("utf-8") if postalcode else None,
+ country=country.encode("utf-8") if country else None,
+ ),
+ params={"TYPE": ("WORK", "PREF", "POSTAL", "PARCEL",), }
+ )
+ )
+
+ # 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 = {"TYPE": ("WORK", "PREF", "INTERNET",), }
+ workParams = {"TYPE": ("WORK", "INTERNET",), }
+ params = preferredWorkParams
+ for emailAddress in record.fields.get(FieldName.emailAddresses, []):
+ addUniqueProperty(Property("EMAIL", emailAddress.encode("utf-8"), params=params), ignoredParameters={"TYPE": ("PREF",)})
+ 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("GEO", geographicLocation.encode("utf-8")))
+
+ #===================================================================
+ # 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("UID", record.fields[FieldName.uid].encode("utf-8")))
+
+ # 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-<instant messaging type> such as:
+ # "AIM", "FACEBOOK", "GAGU-GAGU", "GOOGLE TALK", "ICQ", "JABBER", "MSN", "QQ", "SKYPE", "YAHOO",
+ # X-MAIDENNAME
+ # X-PHONETIC-FIRST-NAME
+ # X-PHONETIC-MIDDLE-NAME
+ # X-PHONETIC-LAST-NAME
+ # X-ABRELATEDNAMES
+
+ # X-ADDRESSBOOKSERVER-KIND
+ if kind == "group":
+ vcard.addProperty(Property("X-ADDRESSBOOKSERVER-KIND", kind))
+
+ # add members
+ # FIXME: members() is a deferred, so all of vCardFromRecord is deferred.
+ for memberRecord in (yield record.members()):
+ vcard.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", "urn:uuid:" + memberRecord.fields[FieldName.uid].encode("utf-8")))
+
+ #===================================================================
+ # 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("KIND", kind))
+
+ # one more X- related to kind
+ if kind == "org":
+ vcard.addProperty(Property("X-ABShowAs", "COMPANY"))
+
+ log.debug("vCardFromRecord: vcard=\n{vcard}", 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 "License");
+# 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 "AS IS" 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.
+##
+
+"""
+Mac OS X Server Wiki directory service.
+"""
+
+__all__ = [
+ "WikiAccessLevel",
+ "DirectoryService",
+]
+
+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"Mac OS X Server Wiki"
+
+
+
+class DirectoryService(BaseDirectoryService):
+ """
+ Mac OS X Server Wiki directory service.
+ """
+
+ uidPrefix = u"[wiki]"
+
+ 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"{}{}".format(self.uidPrefix, name),
+ self.fieldName.recordType: RecordType.macOSXServerWiki,
+ self.fieldName.shortNames: [name],
+ self.fieldName.fullNames: [u"Wiki: {}".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):
+ """
+ Mac OS X Server Wiki directory record.
+ """
+
+ log = Logger()
+
+
+ @property
+ def name(self):
+ return self.shortNames[0]
+
+
+ @inlineCallbacks
+ def accessForRecord(self, record):
+ """
+ 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
+ """
+ if record is None:
+ uid = u"unauthenticated"
+ 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(
+ "Unable to look up access for record {record} "
+ "in wiki {log_source}: {error}",
+ record=record, error=e
+ )
+
+ except WebError as e:
+ status = int(e.status)
+
+ if status == responsecode.FORBIDDEN: # Unknown user
+ self.log.debug(
+ "No such record (according to wiki): {record}",
+ record=record, error=e
+ )
+ returnValue(WikiAccessLevel.none)
+
+ if status == responsecode.NOT_FOUND: # Unknown wiki
+ self.log.error(
+ "No such wiki: {log_source.name}",
+ record=record, error=e
+ )
+ returnValue(WikiAccessLevel.none)
+
+ self.log.error(
+ "Unable to look up wiki access: {error}",
+ record=record, error=e
+ )
+
+ try:
+ returnValue({
+ "no-access": WikiAccessLevel.none,
+ "read": WikiAccessLevel.read,
+ "write": WikiAccessLevel.write,
+ "admin": WikiAccessLevel.write,
+ }[access])
+
+ except KeyError:
+ self.log.error("Unknown wiki access level: {level}", level=access)
+ returnValue(WikiAccessLevel.none)
+
+
+@inlineCallbacks
+def getWikiACL(resource, request):
+ """
+ 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.
+ """
+ from twistedcaldav.directory.principal import DirectoryPrincipalResource
+
+ if (
+ not hasattr(resource, "record") 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(
+ "/principals/wikis/{}/".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(
+ "/principals/wikis/{}/".format(wikiID)
+ )
+ ),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+ davxml.Privilege(davxml.Write()),
+ ),
+ TwistedACLInheritable(),
+ )
+ )
+ returnValue(request.wikiACL)
+
+ else: # "no-access":
+
+ 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,
+ "You are not allowed to access this wiki"
+ )
+ )
+
+ except HTTPError:
+ # pass through the HTTPError we might have raised above
+ raise
+
+ except Exception as e:
+ log.error("Wiki ACL lookup failed: {error}", error=e)
+ raise HTTPError(StatusResponse(
+ responsecode.SERVICE_UNAVAILABLE, "Wiki ACL lookup failed"
+ ))
+
+
+
+class WebAuthError(RuntimeError):
+ """
+ Error in web auth
+ """
+
+
+
+@inlineCallbacks
+def uidForAuthToken(token, host="localhost", port=80):
+ """
+ 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.
+ """
+ url = "http://%s:%d/auth/verify?auth_token=%s" % (host, port, token,)
+ jsonResponse = (yield _getPage(url, host, port))
+ try:
+ response = json.loads(jsonResponse)
+ except Exception, e:
+ log.error(
+ "Error parsing JSON response from webauth: {resp} {error}",
+ resp=jsonResponse, error=str(e)
+ )
+ raise WebAuthError("Could not look up token: %s" % (token,))
+ if response["succeeded"]:
+ returnValue(response["generated_uid"])
+ else:
+ raise WebAuthError("Could not look up token: %s" % (token,))
+
+
+
+def accessForUserToWiki(user, wiki, host="localhost", port=4444):
+ """
+ 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
+ """
+ url = "http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s" % (
+ host, port, user, wiki
+ )
+ return _getPage(url, host, port)
+
+
+
+# FIXME: Why don't we use twisted.web.
+def _getPage(url, host, port):
+ """
+ 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.
+ """
+ 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 "locations", i.e., scheduled spaces:
</ins><span class="cx">
</span><ins>+ capacity = ValueConstant(u"capacity")
+ capacity.fieldName = FieldName.capacity
</ins><span class="cx">
</span><ins>+ floor = ValueConstant(u"floor")
+ floor.fieldName = FieldName.floor
+
+ associatedAddress = ValueConstant(u"associated-address")
+ associatedAddress.fieldName = FieldName.associatedAddress
+
+ # For "addresses", i.e., non-scheduled areas containing locations:
+
+ abbreviatedName = ValueConstant(u"abbreviated-name")
+ abbreviatedName.fieldName = FieldName.abbreviatedName
+
+ streetAddress = ValueConstant(u"street-address")
+ streetAddress.fieldName = FieldName.streetAddress
+
+ geographicLocation = ValueConstant(u"geographic-location")
+ geographicLocation.fieldName = FieldName.geographicLocation
+
+
+
</ins><span class="cx"> class Attribute(Values):
</span><span class="cx"> """
</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"> """
</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 "0" 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 > 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"> """
</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->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, "Transfer-Encoding received without chunked in last position.")
</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."""
</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 = "HTTP/1.1"
</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, "Unknown protocol: %s" % 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] > 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 >= (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, "Unknown Status")
</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("%s: %s\r\n" % ('Connection', 'close'))
</span><span class="cx"> elif self.version < (1,1):
</span><span class="cx"> l.append("%s: %s\r\n" % ('Connection', 'Keep-Alive'))
</span><del>-
</del><ins>+
</ins><span class="cx"> l.append("\r\n")
</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(("%X\r\n" % len(data), data, "\r\n"))
</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"> """We are finished writing data."""
</span><span class="cx"> if self.finished:
</span><span class="cx"> warnings.warn("Warning! request.finish called twice.", 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("0\r\n\r\n")
</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"> """
</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"> """Handle low level protocol errors."""
</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("Aborted request (%d) %s" % (errorcode, text))
</span><span class="cx"> raise AbortedException
</span><del>-
</del><ins>+
</ins><span class="cx"> def _cleanup(self):
</span><span class="cx"> """Called when have finished responding and are no longer queued."""
</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"> """Register a producer.
</span><span class="cx"> """
</span><del>-
</del><ins>+
</ins><span class="cx"> if self.producer:
</span><span class="cx"> raise ValueError, "registering producer %s before previous one (%s) was unregistered" % (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"> """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"> """
</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("GET fake HTTP/1.0")
</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) >= 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) > 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"> """Called by first request in queue when it is done."""
</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) > 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"> """
</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 >= 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("\r\n\r\n>>>> Request complete at: %.3f (elapsed: %.1f ms)" % (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 "AS IS", 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"> """
</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"> """
</span><span class="cx"> # Verify root element
</span><span class="lines">@@ -61,7 +61,7 @@
</span><span class="cx"> if depth != "0":
</span><span class="cx"> log.error("Non-zero depth is not allowed: %s" % (depth,))
</span><span class="cx"> raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (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("namespace", dav_namespace)
</span><span class="cx"> name = property.attributes.get("name", "")
</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("Error reading property %r for resource %s: %s" % (qname, request.uri, f.value))
</del><ins>+ log.error(
+ "Error reading property {qname} for resource {req}: {failure}",
+ qname=qname, req=request.uri, failure=f.value
+ )
</ins><span class="cx">
</span><span class="cx"> status = statusForFailure(f, "getting property: %s" % (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"> """
</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, "authnUser") 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"> """
</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"> """
</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"> """
</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"> """
</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"> """
</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"> "bindMethods",
</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("utf-8")
</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 = [("t", 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"> """
</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>