<!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>[13015] CalendarServer/branches/users/sagen/move2who-4</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/13015">13015</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-03-27 19:06:05 -0700 (Thu, 27 Mar 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Port wiki and sharing to twext.who</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenmove2who4calendarserverplatformdarwinwikipy">CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4calendarserverprovisionrootpy">CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4calendarservertaputilpy">CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectoryaugmentpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorycalendarpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectoryprincipalpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorywikipy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavsharingpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavstdconfigpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavstorebridgepy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4twistedcaldavtesttest_sharingpy">CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4txdavwhoaugmentpy">CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4txdavwhodirectorypy">CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4txdavwhoutilpy">CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenmove2who4txdavwhowikipy">CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagenmove2who4calendarserverplatformdarwinwikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -27,16 +27,17 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def guidForAuthToken(token, host=&quot;localhost&quot;, port=80):
</del><ins>+def uidForAuthToken(token, host=&quot;localhost&quot;, port=80):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Send a GET request to the web auth service to retrieve the user record
</span><del>-    guid associated with the provided auth token.
</del><ins>+    uid associated with the provided auth token.
</ins><span class="cx"> 
</span><span class="cx">     @param token: An auth token, usually passed in via cookie when webcal
</span><span class="cx">         makes a request.
</span><span class="cx">     @type token: C{str}
</span><del>-    @return: deferred returning a guid (C{str}) if successful, or
</del><ins>+    @return: deferred returning a uid (C{str}) if successful, or
</ins><span class="cx">         will raise WebAuthError otherwise.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     url = &quot;http://%s:%d/auth/verify?auth_token=%s&quot; % (host, port, token,)
</span><span class="lines">@@ -44,8 +45,10 @@
</span><span class="cx">     try:
</span><span class="cx">         response = json.loads(jsonResponse)
</span><span class="cx">     except Exception, e:
</span><del>-        log.error(&quot;Error parsing JSON response from webauth: %s (%s)&quot; %
-            (jsonResponse, str(e)))
</del><ins>+        log.error(
+            &quot;Error parsing JSON response from webauth: {resp} {error}&quot;,
+            resp=jsonResponse, error=str(e)
+        )
</ins><span class="cx">         raise WebAuthError(&quot;Could not look up token: %s&quot; % (token,))
</span><span class="cx">     if response[&quot;succeeded&quot;]:
</span><span class="cx">         returnValue(response[&quot;generated_uid&quot;])
</span><span class="lines">@@ -57,10 +60,10 @@
</span><span class="cx"> def accessForUserToWiki(user, wiki, host=&quot;localhost&quot;, port=4444):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Send a GET request to the wiki collabd service to retrieve the access level
</span><del>-    the given user (in GUID form) has to the given wiki (in wiki short-name
</del><ins>+    the given user (uid) has to the given wiki (in wiki short-name
</ins><span class="cx">     form).
</span><span class="cx"> 
</span><del>-    @param user: The GUID of the user
</del><ins>+    @param user: The UID of the user
</ins><span class="cx">     @type user: C{str}
</span><span class="cx">     @param wiki: The short name of the wiki
</span><span class="cx">     @type wiki: C{str}
</span><span class="lines">@@ -69,8 +72,9 @@
</span><span class="cx">         status FORBIDDEN will errBack; an unknown wiki will have a status
</span><span class="cx">         of NOT_FOUND
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    url = &quot;http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s&quot; % (host, port,
-        user, wiki)
</del><ins>+    url = &quot;http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s&quot; % (
+        host, port, user, wiki
+    )
</ins><span class="cx">     return _getPage(url, host, port)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4calendarserverprovisionrootpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -19,29 +19,29 @@
</span><span class="cx">     &quot;RootResource&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><ins>+from calendarserver.platform.darwin.wiki import uidForAuthToken
</ins><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><ins>+from twext.who.idirectory import RecordType
</ins><span class="cx"> from twisted.cred.error import LoginFailed, UnauthorizedLogin
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> from twisted.python.reflect import namedClass
</span><ins>+from twisted.web.error import Error as WebError
</ins><span class="cx"> from twisted.web.xmlrpc import Proxy
</span><del>-from twisted.web.error import Error as WebError
-
</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.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="lines">@@ -234,20 +234,20 @@
</span><span class="cx">                     record = None
</span><span class="cx">                     try:
</span><span class="cx">                         if wikiConfig.LionCompatibility:
</span><del>-                            guid = None
</del><ins>+                            uid = None
</ins><span class="cx">                             proxy = Proxy(wikiConfig[&quot;URL&quot;])
</span><span class="cx">                             username = (yield proxy.callRemote(wikiConfig[&quot;UserMethod&quot;], token))
</span><span class="cx">                             directory = request.site.resource.getDirectory()
</span><del>-                            record = directory.recordWithShortName(&quot;users&quot;, username)
</del><ins>+                            record = yield directory.recordWithShortName(RecordType.user, username)
</ins><span class="cx">                             if record is not None:
</span><del>-                                guid = record.guid
</del><ins>+                                uid = record.uid
</ins><span class="cx">                         else:
</span><del>-                            guid = (yield guidForAuthToken(token))
-                            if guid == &quot;unauthenticated&quot;:
-                                guid = None
</del><ins>+                            uid = (yield uidForAuthToken(token))
+                            if uid == &quot;unauthenticated&quot;:
+                                uid = None
</ins><span class="cx"> 
</span><span class="cx">                     except WebError, w:
</span><del>-                        guid = None
</del><ins>+                        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><span class="cx">                             log.debug(&quot;Unknown wiki token: %s&quot; % (token,))
</span><span class="lines">@@ -257,18 +257,18 @@
</span><span class="cx"> 
</span><span class="cx">                     except Exception, e:
</span><span class="cx">                         log.error(&quot;Failed to look up wiki token (%s)&quot; % (e,))
</span><del>-                        guid = None
</del><ins>+                        uid = None
</ins><span class="cx"> 
</span><del>-                    if guid is not None:
-                        log.debug(&quot;Wiki lookup returned guid: %s&quot; % (guid,))
</del><ins>+                    if uid is not None:
+                        log.debug(&quot;Wiki lookup returned uid: %s&quot; % (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><span class="cx">                             log.debug(&quot;Wiki user record for user %s : %s&quot; % (username, record))
</span><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="lines">@@ -317,7 +317,7 @@
</span><span class="cx">         elif (len(segments) &gt; 2 and segments[0] in (&quot;calendars&quot;, &quot;principals&quot;) and
</span><span class="cx">             (
</span><span class="cx">                 segments[1] == &quot;wikis&quot; or
</span><del>-                (segments[1] == &quot;__uids__&quot; and segments[2].startswith(&quot;wiki-&quot;))
</del><ins>+                (segments[1] == &quot;__uids__&quot; and segments[2].startswith(WikiDirectoryService.uidPrefix))
</ins><span class="cx">             )
</span><span class="cx">         ):
</span><span class="cx">             # This is a wiki-related calendar resource. SACLs are not checked.
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4calendarservertaputilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py        2014-03-28 02:06:05 UTC (rev 13015)
</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">@@ -46,17 +45,13 @@
</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><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><span class="cx"> from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
</span><span class="cx"> from twistedcaldav.directory.digest import QopDigestCredentialFactory
</span><span class="cx"> from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
</span><del>-from twistedcaldav.directory.wiki import WikiDirectoryService
</del><span class="cx"> from calendarserver.push.notifier import NotifierFactory
</span><span class="cx"> from calendarserver.push.applepush import APNSubscriptionResource
</span><span class="cx"> from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
</span><span class="lines">@@ -98,8 +93,6 @@
</span><span class="cx"> from urllib import quote
</span><span class="cx"> from twisted.python.usage import UsageError
</span><span class="cx"> 
</span><del>-from txdav.dps.client import DirectoryService as DirectoryProxyClientService
-
</del><span class="cx"> from twext.who.checker import UsernamePasswordCredentialChecker
</span><span class="cx"> from twext.who.checker import HTTPDigestCredentialChecker
</span><span class="cx"> from twisted.cred.error import UnauthorizedLogin
</span><span class="lines">@@ -281,96 +274,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def REMOVEMEdirectoryFromConfig(config):
-    &quot;&quot;&quot;
-    Create an L{AggregateDirectoryService} from the given configuration.
-    &quot;&quot;&quot;
-
-    #
-    # Setup the Augment Service
-    #
-    if config.AugmentService.type:
-        augmentClass = namedClass(config.AugmentService.type)
-        log.info(&quot;Configuring augment service of type: {augmentClass}&quot;,
-            augmentClass=augmentClass)
-        try:
-            augmentService = augmentClass(**config.AugmentService.params)
-        except IOError:
-            log.error(&quot;Could not start augment service&quot;)
-            raise
-    else:
-        augmentService = None
-
-    #
-    # Setup the group membership cacher
-    #
-    if config.GroupCaching.Enabled:
-        groupMembershipCache = GroupMembershipCache(
-            config.GroupCaching.MemcachedPool,
-            expireSeconds=config.GroupCaching.ExpireSeconds)
-    else:
-        groupMembershipCache = None
-
-    #
-    # Setup the Directory
-    #
-    directories = []
-
-    directoryClass = namedClass(config.DirectoryService.type)
-    principalResourceClass = DirectoryPrincipalProvisioningResource
-
-    log.info(&quot;Configuring directory service of type: {directoryType}&quot;,
-        directoryType=config.DirectoryService.type)
-
-    config.DirectoryService.params.augmentService = augmentService
-    config.DirectoryService.params.groupMembershipCache = groupMembershipCache
-    baseDirectory = directoryClass(config.DirectoryService.params)
-
-    # Wait for the directory to become available
-    while not baseDirectory.isAvailable():
-        sleep(5)
-
-    directories.append(baseDirectory)
-
-    #
-    # Setup the Locations and Resources Service
-    #
-    if config.ResourceService.Enabled:
-        resourceClass = namedClass(config.ResourceService.type)
-
-        log.info(&quot;Configuring resource service of type: {resourceClass}&quot;,
-            resourceClass=resourceClass)
-
-        config.ResourceService.params.augmentService = augmentService
-        config.ResourceService.params.groupMembershipCache = groupMembershipCache
-        resourceDirectory = resourceClass(config.ResourceService.params)
-        resourceDirectory.realmName = baseDirectory.realmName
-        directories.append(resourceDirectory)
-
-    #
-    # Add wiki directory service
-    #
-    if config.Authentication.Wiki.Enabled:
-        wikiDirectory = WikiDirectoryService()
-        wikiDirectory.realmName = baseDirectory.realmName
-        directories.append(wikiDirectory)
-
-    directory = AggregateDirectoryService(directories, groupMembershipCache)
-
-    #
-    # Use system-wide realm on OSX
-    #
-    try:
-        import ServerFoundation
-        realmName = ServerFoundation.XSAuthenticator.defaultRealm().encode(&quot;utf-8&quot;)
-        directory.setRealm(realmName)
-    except ImportError:
-        pass
-    log.info(&quot;Setting up principal collection: {cls}&quot;, cls=principalResourceClass)
-    principalResourceClass(&quot;/principals/&quot;, directory)
-    return directory
-
-
</del><span class="cx"> # MOVE2WHO -- should we move this class somewhere else?
</span><span class="cx"> class PrincipalCredentialChecker(object):
</span><span class="cx">     credentialInterfaces = (IPrincipalCredentials,)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectoryaugmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -81,6 +81,7 @@
</span><span class="cx">     &quot;locations&quot;: &quot;Location&quot;,
</span><span class="cx">     &quot;resources&quot;: &quot;Resource&quot;,
</span><span class="cx">     &quot;addresses&quot;: &quot;Address&quot;,
</span><ins>+    &quot;wikis&quot;: &quot;Wiki&quot;,
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorycalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -38,7 +38,7 @@
</span><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></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectoryprincipalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -48,7 +48,7 @@
</span><span class="cx"> from twistedcaldav.directory.util import (
</span><span class="cx">     formatLink, formatLinks, formatPrincipals, formatList
</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 (
</span><span class="cx">     ReadOnlyResourceMixIn, DAVPrincipalResource, DAVResourceWithChildrenMixin
</span><span class="cx"> )
</span><span class="lines">@@ -1308,6 +1308,7 @@
</span><span class="cx">         Return a CUA for this principal, preferring in this order:
</span><span class="cx">             urn:uuid: form
</span><span class="cx">             mailto: form
</span><ins>+            /principal/__uids__/ form
</ins><span class="cx">             first in calendarUserAddresses( ) list
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.record.canonicalCalendarUserAddress()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavdirectorywikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -250,8 +250,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def getWikiACL(resource, request):
-    return succeed(None)
</del><ins>+# def getWikiACL(resource, request):
+#     return succeed(None)
</ins><span class="cx"> # @inlineCallbacks
</span><span class="cx"> # def getWikiACL(resource, request):
</span><span class="cx"> #     &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavsharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py        2014-03-28 02:06:05 UTC (rev 13015)
</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">@@ -269,15 +269,13 @@
</span><span class="cx">         if self._newStoreObject.direct():
</span><span class="cx">             owner = yield self.principalForUID(self._newStoreObject.ownerHome().uid())
</span><span class="cx">             sharee = yield self.principalForUID(self._newStoreObject.viewerHome().uid())
</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>-                userID = sharee.record.guid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
-                if access == &quot;read&quot;:
</del><ins>+                access = (yield owner.record.accessForRecord(sharee.record))
+                if access == WikiAccessLevel.read:
</ins><span class="cx">                     returnValue(&quot;read-only&quot;)
</span><del>-                elif access in (&quot;write&quot;, &quot;admin&quot;):
</del><ins>+                elif access == WikiAccessLevel.write:
</ins><span class="cx">                     returnValue(&quot;read-write&quot;)
</span><span class="cx">                 else:
</span><span class="cx">                     returnValue(None)
</span><span class="lines">@@ -502,7 +500,7 @@
</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><span class="cx">         sharee = yield self.principalForCalendarUserAddress(userid)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -1257,7 +1257,13 @@
</span><span class="cx"> 
</span><span class="cx">     # Default DirectoryRealmName from ServerHostName
</span><span class="cx">     if not configDict.DirectoryRealmName:
</span><del>-        configDict.DirectoryRealmName = configDict.ServerHostName
</del><ins>+        # 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><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -15,77 +15,89 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+import collections
+import hashlib
+import time
+from urlparse import urlsplit, urljoin
+import uuid
+
</ins><span class="cx"> from pycalendar.datetime import DateTime
</span><del>-
</del><span class="cx"> from twext.enterprise.locking import LockTimeout
</span><span class="cx"> from twext.python.log import Logger
</span><del>-from txweb2 import responsecode, http_headers, http
-from txweb2.dav.http import ErrorResponse, ResponseQueue, MultiStatusResponse
-from txweb2.dav.noneprops import NonePropertyStore
-from txweb2.dav.resource import TwistedACLInheritable, AccessDeniedError, \
-    davPrivilegeSet
-from txweb2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
-from txweb2.filter.location import addLocation
-from txweb2.http import HTTPError, StatusResponse, Response
-from txweb2.http_headers import ETag, MimeType, MimeDisposition
-from txweb2.iweb import IResponse
-from txweb2.responsecode import \
-    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED, \
-    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
-from txweb2.stream import ProducerStream, readStream, MemoryStream
-
</del><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
</span><span class="cx"> from twisted.internet.protocol import Protocol
</span><span class="cx"> from twisted.python.hashlib import md5
</span><span class="cx"> from twisted.python.util import FancyEqMixin
</span><del>-
</del><span class="cx"> from twistedcaldav import customxml, carddavxml, caldavxml, ical
</span><del>-from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance, \
-    MaxInstances, NoUIDConflict
</del><ins>+from twistedcaldav.caldavxml import (
+    caldav_namespace, MaxAttendeesPerInstance, MaxInstances, NoUIDConflict
+)
</ins><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NoUIDConflict as NovCardUIDConflict
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><del>-from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
-from twistedcaldav.ical import Component as VCalendar, Property as VProperty, \
-    InvalidICalendarDataError, iCalendarProductID, Component
-from twistedcaldav.instance import InvalidOverriddenInstanceError, \
-    TooManyInstancesError
</del><ins>+from twistedcaldav.ical import (
+    Component as VCalendar, Property as VProperty, InvalidICalendarDataError,
+    iCalendarProductID, Component
+)
+from twistedcaldav.instance import (
+    InvalidOverriddenInstanceError, TooManyInstancesError
+)
</ins><span class="cx"> from twistedcaldav.memcachelock import MemcacheLockTimeoutError
</span><span class="cx"> from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
</span><span class="cx"> from twistedcaldav.resource import CalDAVResource, DefaultAlarmPropertyMixin
</span><span class="cx"> from twistedcaldav.scheduling_store.caldav.resource import ScheduleInboxResource
</span><del>-from twistedcaldav.sharing import invitationBindStatusToXMLMap, \
-    invitationBindModeToXMLMap
</del><ins>+from twistedcaldav.sharing import (
+    invitationBindStatusToXMLMap, invitationBindModeToXMLMap
+)
</ins><span class="cx"> from twistedcaldav.util import bestAcceptType
</span><span class="cx"> from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
</span><del>-
</del><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><del>-from txdav.caldav.icalendarstore import QuotaExceeded, AttachmentStoreFailed, \
-    AttachmentStoreValidManagedID, AttachmentRemoveFailed, \
-    AttachmentDropboxNotAllowed, InvalidComponentTypeError, \
-    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError, \
-    InvalidPerUserDataMerge, \
-    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation, \
</del><ins>+from txdav.caldav.icalendarstore import (
+    QuotaExceeded, AttachmentStoreFailed,
+    AttachmentStoreValidManagedID, AttachmentRemoveFailed,
+    AttachmentDropboxNotAllowed, InvalidComponentTypeError,
+    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError,
+    InvalidPerUserDataMerge,
+    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation,
</ins><span class="cx">     ShareeAllowedError, DuplicatePrivateCommentsError, InvalidSplit
</span><del>-from txdav.carddav.iaddressbookstore import KindChangeNotAllowedError, \
-    GroupWithUnsharedAddressNotAllowedError
-from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
</del><ins>+)
+from txdav.carddav.iaddressbookstore import (
+    KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
+)
+from txdav.common.datastore.sql_tables import (
+    _BIND_MODE_READ, _BIND_MODE_WRITE,
</ins><span class="cx">     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
</span><del>-from txdav.common.icommondatastore import NoSuchObjectResourceError, \
-    TooManyObjectResourcesError, ObjectResourceTooBigError, \
-    InvalidObjectResourceError, ObjectResourceNameNotAllowedError, \
-    ObjectResourceNameAlreadyExistsError, UIDExistsError, \
-    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove, \
</del><ins>+)
+from txdav.common.icommondatastore import (
+    NoSuchObjectResourceError,
+    TooManyObjectResourcesError, ObjectResourceTooBigError,
+    InvalidObjectResourceError, ObjectResourceNameNotAllowedError,
+    ObjectResourceNameAlreadyExistsError, UIDExistsError,
+    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove,
</ins><span class="cx">     InvalidComponentForStoreError
</span><ins>+)
</ins><span class="cx"> from txdav.idav import PropertyChangeNotAllowedError
</span><ins>+from txdav.who.wiki import RecordType as WikiRecordType
</ins><span class="cx"> from txdav.xml import element as davxml, element
</span><span class="cx"> from txdav.xml.base import dav_namespace, WebDAVUnknownElement, encodeXMLName
</span><ins>+from txweb2 import responsecode, http_headers, http
+from txweb2.dav.http import ErrorResponse, ResponseQueue, MultiStatusResponse
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.resource import (
+    TwistedACLInheritable, AccessDeniedError, davPrivilegeSet
+)
+from txweb2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
+from txweb2.filter.location import addLocation
+from txweb2.http import HTTPError, StatusResponse, Response
+from txweb2.http_headers import ETag, MimeType, MimeDisposition
+from txweb2.iweb import IResponse
+from txweb2.responsecode import (
+    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
+    BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE, SERVICE_UNAVAILABLE
+)
+from txweb2.stream import ProducerStream, readStream, MemoryStream
</ins><span class="cx"> 
</span><del>-from urlparse import urlsplit, urljoin
-import collections
-import hashlib
-import time
-import uuid
</del><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
</span><span class="cx"> L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
</span><span class="lines">@@ -93,6 +105,7 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class _NewStorePropertiesWrapper(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Wrap a new-style property store (a L{txdav.idav.IPropertyStore}) in the old-
</span><span class="lines">@@ -1983,15 +1996,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if invite.mode in (_BIND_MODE_DIRECT,):
</span><span class="cx">             ownerUID = invite.ownerUID
</span><del>-            owner = self.principalForUID(ownerUID)
</del><ins>+            owner = yield self.principalForUID(ownerUID)
</ins><span class="cx">             shareeUID = invite.shareeUID
</span><del>-            if owner.record.recordType == WikiDirectoryService.recordType_wikis:
</del><ins>+            if owner.record.recordType == WikiRecordType.macOSXServerWiki:
</ins><span class="cx">                 # Access level comes from what the wiki has granted to the
</span><span class="cx">                 # sharee
</span><del>-                sharee = self.principalForUID(shareeUID)
-                userID = sharee.record.uid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
</del><ins>+                sharee = yield self.principalForUID(shareeUID)
+                access = (yield owner.record.accessForRecord(sharee.record))
</ins><span class="cx">                 if access == &quot;read&quot;:
</span><span class="cx">                     returnValue(&quot;read-only&quot;)
</span><span class="cx">                 elif access in (&quot;write&quot;, &quot;admin&quot;):
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4twistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -19,30 +19,31 @@
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.iweb import IResponse
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav import customxml
</span><del>-from twistedcaldav import sharing
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
</span><del>-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.sharing import WikiDirectoryService
</del><span class="cx"> from twistedcaldav.test.test_cache import StubResponseCacheResource
</span><span class="cx"> from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><del>-from txdav.caldav.datastore.test.util import buildDirectory
</del><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><span class="cx"> 
</span><span class="cx"> from xml.etree.cElementTree import XML
</span><ins>+from txdav.who.wiki import (
+    DirectoryRecord as WikiDirectoryRecord,
+    DirectoryService as WikiDirectoryService,
+    RecordType as WikiRecordType,
+    WikiAccessLevel
+)
</ins><span class="cx"> 
</span><ins>+sharedOwnerType = davxml.ResourceType.sharedownercalendar  # @UndefinedVariable
+regularCalendarType = davxml.ResourceType.calendar  # @UndefinedVariable
</ins><span class="cx"> 
</span><del>-sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
-regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> def normalize(x):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Normalize some XML by parsing it, collapsing whitespace, and
</span><span class="lines">@@ -63,8 +64,8 @@
</span><span class="cx">         self.fullName = name
</span><span class="cx">         self.guid = name
</span><span class="cx">         self.calendarUserAddresses = set((cuaddr,))
</span><del>-        if name.startswith(&quot;wiki-&quot;):
-            recordType = WikiDirectoryService.recordType_wikis
</del><ins>+        if name.startswith(WikiDirectoryService.uidPrefix):
+            recordType = WikiRecordType.macOSXServerWiki
</ins><span class="cx">         else:
</span><span class="cx">             recordType = None
</span><span class="cx">         self.recordType = recordType
</span><span class="lines">@@ -131,42 +132,45 @@
</span><span class="cx">         super(SharingTests, self).configure()
</span><span class="cx">         self.patch(config.Sharing, &quot;Enabled&quot;, True)
</span><span class="cx">         self.patch(config.Sharing.Calendars, &quot;Enabled&quot;, True)
</span><ins>+        self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(SharingTests, self).setUp()
</span><span class="cx"> 
</span><del>-        def patched(c):
-            &quot;&quot;&quot;
-            The decorated method is patched on L{CalDAVResource} for the
-            duration of the test.
-            &quot;&quot;&quot;
-            self.patch(CalDAVResource, c.__name__, c)
-            return c
</del><ins>+        # FIXME: not sure what these were for:
</ins><span class="cx"> 
</span><del>-        @patched
-        def principalForCalendarUserAddress(resourceSelf, cuaddr):
-            if &quot;bogus&quot; in cuaddr:
-                return None
-            else:
-                return FakePrincipal(cuaddr, self)
</del><ins>+        #     def patched(c):
+        #         &quot;&quot;&quot;
+        #         The decorated method is patched on L{CalDAVResource} for the
+        #         duration of the test.
+        #         &quot;&quot;&quot;
+        #         self.patch(CalDAVResource, c.__name__, c)
+        #         return c
</ins><span class="cx"> 
</span><del>-        @patched
-        def validUserIDForShare(resourceSelf, userid, request):
-            &quot;&quot;&quot;
-            Temporary replacement for L{CalDAVResource.validUserIDForShare}
-            that marks any principal without 'bogus' in its name.
-            &quot;&quot;&quot;
-            result = principalForCalendarUserAddress(resourceSelf, userid)
-            if result is None:
-                return result
-            return result.principalURL()
</del><ins>+        #     @patched
+        #     def principalForCalendarUserAddress(resourceSelf, cuaddr):
+        #         if &quot;bogus&quot; in cuaddr:
+        #             return None
+        #         else:
+        #             return FakePrincipal(cuaddr, self)
</ins><span class="cx"> 
</span><del>-        @patched
-        def principalForUID(resourceSelf, principalUID):
-            return FakePrincipal(&quot;urn:uuid:&quot; + principalUID, self)
</del><ins>+        #     @patched
+        #     def validUserIDForShare(resourceSelf, userid, request):
+        #         &quot;&quot;&quot;
+        #         Temporary replacement for L{CalDAVResource.validUserIDForShare}
+        #         that marks any principal without 'bogus' in its name.
+        #         &quot;&quot;&quot;
+        #         result = principalForCalendarUserAddress(resourceSelf, userid)
+        #         if result is None:
+        #             return result
+        #         return result.principalURL()
</ins><span class="cx"> 
</span><ins>+        #     @patched
+        #     def principalForUID(resourceSelf, principalUID):
+        #         return FakePrincipal(&quot;urn:uuid:&quot; + principalUID, self)
+
</ins><span class="cx">         self.resource = yield self._getResource()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -321,7 +325,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -351,7 +355,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -400,7 +404,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -475,21 +479,21 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user03&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER03&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 03&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user04&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER04&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 04&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -533,14 +537,14 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user04&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER04&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 04&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -584,14 +588,14 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user03&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER03&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 03&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -701,7 +705,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             )
</span><span class="lines">@@ -738,23 +742,24 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def wikiSetup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Create a wiki called C{wiki-testing}, and share it with the user whose
</del><ins>+        Create a wiki called C{[wiki]testing}, and share it with the user whose
</ins><span class="cx">         home is at /.  Return the name of the newly shared calendar in the
</span><span class="cx">         sharee's home.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self._sqlCalendarStore._directoryService = buildDirectory(homes=(&quot;wiki-testing&quot;,))
</del><ins>+        # self._sqlCalendarStore._directoryService = buildDirectory(homes=(&quot;wiki-testing&quot;,))
</ins><span class="cx">         wcreate = self._sqlCalendarStore.newTransaction(&quot;create wiki&quot;)
</span><del>-        yield wcreate.calendarHomeWithUID(&quot;wiki-testing&quot;, create=True)
</del><ins>+        yield wcreate.calendarHomeWithUID(
+            u&quot;{prefix}testing&quot;.format(prefix=WikiDirectoryService.uidPrefix),
+            create=True
+        )
</ins><span class="cx">         yield wcreate.commit()
</span><span class="cx"> 
</span><del>-        newService = WikiDirectoryService()
-        newService.realmName = self.directory.realmName
-        self.directory.addService(newService)
-
</del><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        sharee = yield self.homeUnderTest(name=&quot;user01&quot;)
-        sharer = yield txn.calendarHomeWithUID(&quot;wiki-testing&quot;)
</del><ins>+        sharee = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
+        sharer = yield txn.calendarHomeWithUID(
+            u&quot;{prefix}testing&quot;.format(prefix=WikiDirectoryService.uidPrefix),
+        )
</ins><span class="cx">         cal = yield sharer.calendarWithName(&quot;calendar&quot;)
</span><span class="cx">         sharedName = yield cal.shareWith(sharee, _BIND_MODE_DIRECT)
</span><span class="cx">         returnValue(sharedName)
</span><span class="lines">@@ -767,13 +772,14 @@
</span><span class="cx">         to the sharee, so that delegates of the sharee get the same level of
</span><span class="cx">         access.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        sharedName = yield self.wikiSetup()
+        access = WikiAccessLevel.read
</ins><span class="cx"> 
</span><del>-        access = &quot;read&quot;
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, &quot;getWikiAccess&quot;, stubWikiAccessMethod)
</del><ins>+        def stubAccessForRecord(*args):
+            return succeed(access)
</ins><span class="cx"> 
</span><del>-        sharedName = yield self.wikiSetup()
</del><ins>+        self.patch(WikiDirectoryRecord, &quot;accessForRecord&quot;, stubAccessForRecord)
+
</ins><span class="cx">         request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/calendars/__uids__/user01/&quot;)
</span><span class="cx">         collection = yield request.locateResource(&quot;/calendars/__uids__/user01/&quot; + sharedName)
</span><span class="cx"> 
</span><span class="lines">@@ -782,7 +788,7 @@
</span><span class="cx">         self.assertFalse(&quot;&lt;write/&gt;&quot; in acl.toxml())
</span><span class="cx"> 
</span><span class="cx">         # Simulate the wiki server granting Read-Write access
</span><del>-        access = &quot;write&quot;
</del><ins>+        access = WikiAccessLevel.write
</ins><span class="cx">         acl = (yield collection.shareeAccessControlList(request))
</span><span class="cx">         self.assertTrue(&quot;&lt;write/&gt;&quot; in acl.toxml())
</span><span class="cx"> 
</span><span class="lines">@@ -795,10 +801,13 @@
</span><span class="cx">         un-share that collection.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         sharedName = yield self.wikiSetup()
</span><del>-        access = &quot;write&quot;
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, &quot;getWikiAccess&quot;, stubWikiAccessMethod)
</del><ins>+        access = WikiAccessLevel.write
+
+        def stubAccessForRecord(*args):
+            return succeed(access)
+
+        self.patch(WikiDirectoryRecord, &quot;accessForRecord&quot;, stubAccessForRecord)
+
</ins><span class="cx">         @inlineCallbacks
</span><span class="cx">         def listChildrenViaPropfind():
</span><span class="cx">             authRecord = yield self.directory.recordWithUID(u&quot;user01&quot;)
</span><span class="lines">@@ -814,9 +823,10 @@
</span><span class="cx">             seq.remove(shortest)
</span><span class="cx">             filtered = [elem[len(shortest):].rstrip(&quot;/&quot;) for elem in seq]
</span><span class="cx">             returnValue(filtered)
</span><ins>+
</ins><span class="cx">         childNames = yield listChildrenViaPropfind()
</span><span class="cx">         self.assertIn(sharedName, childNames)
</span><del>-        access = &quot;no-access&quot;
</del><ins>+        access = WikiAccessLevel.none
</ins><span class="cx">         childNames = yield listChildrenViaPropfind()
</span><span class="cx">         self.assertNotIn(sharedName, childNames)
</span><span class="cx"> 
</span><span class="lines">@@ -841,7 +851,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -871,7 +881,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span><span class="lines">@@ -915,7 +925,7 @@
</span><span class="cx">             customxml.InviteUser(
</span><span class="cx">                 customxml.UID.fromString(&quot;&quot;),
</span><span class="cx">                 davxml.HRef.fromString(&quot;urn:uuid:user02&quot;),
</span><del>-                customxml.CommonName.fromString(&quot;USER02&quot;),
</del><ins>+                customxml.CommonName.fromString(&quot;User 02&quot;),
</ins><span class="cx">                 customxml.InviteAccess(customxml.ReadWriteAccess()),
</span><span class="cx">                 customxml.InviteStatusNoResponse(),
</span><span class="cx">             ),
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4txdavwhoaugmentpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -417,3 +417,7 @@
</span><span class="cx"> 
</span><span class="cx">     def verifyHTTPDigest(self, *args):
</span><span class="cx">         return self._baseRecord.verifyHTTPDigest(*args)
</span><ins>+
+
+    def accessForRecord(self, record):
+        return self._baseRecord.accessForRecord(record)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4txdavwhodirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -178,6 +178,7 @@
</span><span class="cx">         &quot;location&quot;: &quot;locations&quot;,
</span><span class="cx">         &quot;resource&quot;: &quot;resources&quot;,
</span><span class="cx">         &quot;user&quot;: &quot;users&quot;,
</span><ins>+        &quot;macOSXServerWiki&quot;: &quot;wikis&quot;,
</ins><span class="cx">         &quot;readDelegateGroup&quot;: &quot;readDelegateGroups&quot;,
</span><span class="cx">         &quot;writeDelegateGroup&quot;: &quot;writeDelegateGroups&quot;,
</span><span class="cx">         &quot;readDelegatorGroup&quot;: &quot;readDelegatorGroups&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4txdavwhoutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -23,7 +23,7 @@
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tap.util import getDBPool, storeFromConfig
</span><span class="cx"> from twext.who.idirectory import (
</span><del>-    RecordType, DirectoryConfigurationError, FieldName
</del><ins>+    RecordType, DirectoryConfigurationError
</ins><span class="cx"> )
</span><span class="cx"> from twext.who.ldap import DirectoryService as LDAPDirectoryService
</span><span class="cx"> from twext.who.util import ConstantsContainer
</span><span class="lines">@@ -36,6 +36,7 @@
</span><span class="cx">     FieldName as CalFieldName
</span><span class="cx"> )
</span><span class="cx"> from txdav.who.xml import DirectoryService as XMLDirectoryService
</span><ins>+from txdav.who.wiki import DirectoryService as WikiDirectoryService
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -160,15 +161,29 @@
</span><span class="cx">         log.error(&quot;No directory service set up for users&quot;)
</span><span class="cx">         raise DirectoryConfigurationError
</span><span class="cx"> 
</span><ins>+    # Delegate service
</ins><span class="cx">     delegateDirectory = DelegateDirectoryService(
</span><span class="cx">         userDirectory.realmName,
</span><span class="cx">         store
</span><span class="cx">     )
</span><span class="cx">     aggregatedServices.append(delegateDirectory)
</span><span class="cx"> 
</span><ins>+    # Wiki service
+    if config.Authentication.Wiki.Enabled:
+        aggregatedServices.append(
+            WikiDirectoryService(
+                userDirectory.realmName,
+                config.Authentication.Wiki.CollabHost,
+                config.Authentication.Wiki.CollabPort
+            )
+        )
+
+    # Aggregate service
</ins><span class="cx">     aggregateDirectory = AggregateDirectoryService(
</span><span class="cx">         userDirectory.realmName, aggregatedServices
</span><span class="cx">     )
</span><ins>+
+    # Augment service
</ins><span class="cx">     try:
</span><span class="cx">         fieldNames.append(CalFieldName)
</span><span class="cx">         augmented = AugmentedDirectoryService(
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenmove2who4txdavwhowikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py (13014 => 13015)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py        2014-03-28 00:46:42 UTC (rev 13014)
+++ CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py        2014-03-28 02:06:05 UTC (rev 13015)
</span><span class="lines">@@ -23,21 +23,28 @@
</span><span class="cx">     &quot;WikiAccessLevel&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-from twisted.python.constants import Names, NamedConstant
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-from twisted.web.error import Error as WebError
-
</del><ins>+from calendarserver.platform.darwin.wiki import accessForUserToWiki
+from twext.internet.gaiendpoint import MultiFailure
</ins><span class="cx"> from twext.python.log import Logger
</span><del>-from twext.internet.gaiendpoint import MultiFailure
-from .idirectory import FieldName
</del><span class="cx"> from twext.who.directory import (
</span><span class="cx">     DirectoryService as BaseDirectoryService,
</span><span class="cx">     DirectoryRecord as BaseDirectoryRecord
</span><span class="cx"> )
</span><ins>+from twext.who.idirectory import FieldName as BaseFieldName
+from twext.who.util import ConstantsContainer
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.python.constants import Names, NamedConstant
+from twisted.web.error import Error as WebError
+from txdav.who.idirectory import FieldName
+from txdav.who.directory import CalendarDirectoryRecordMixin
+from txdav.xml import element as davxml
</ins><span class="cx"> from txweb2 import responsecode
</span><ins>+from txweb2.auth.wrapper import UnauthorizedResponse
+from txweb2.dav.resource import TwistedACLInheritable
+from txweb2.http import HTTPError, StatusResponse
</ins><span class="cx"> 
</span><del>-from calendarserver.platform.darwin.wiki import accessForUserToWiki
</del><span class="cx"> 
</span><ins>+log = Logger()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> # FIXME: Should this be Flags?
</span><span class="lines">@@ -59,13 +66,20 @@
</span><span class="cx">     Mac OS X Server Wiki directory service.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    uidPrefix = &quot;[wiki]&quot;
</del><ins>+    uidPrefix = u&quot;[wiki]&quot;
</ins><span class="cx"> 
</span><span class="cx">     recordType = RecordType
</span><span class="cx"> 
</span><ins>+    fieldName = ConstantsContainer((
+        BaseFieldName,
+        FieldName,
+    ))
</ins><span class="cx"> 
</span><del>-    def __init__(self):
-        BaseDirectoryService.__init__(self)
</del><ins>+
+    def __init__(self, realmName, wikiHost, wikiPort):
+        BaseDirectoryService.__init__(self, realmName)
+        self.wikiHost = wikiHost
+        self.wikiPort = wikiPort
</ins><span class="cx">         self._recordsByName = {}
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -91,9 +105,10 @@
</span><span class="cx">             record = DirectoryRecord(
</span><span class="cx">                 self,
</span><span class="cx">                 {
</span><del>-                    FieldName.uid: &quot;{}{}&quot;.format(self.uidPrefix, name),
-                    FieldName.recordType: RecordType.macOSXServerWiki,
-                    FieldName.shortNames: [name],
</del><ins>+                    self.fieldName.uid: u&quot;{}{}&quot;.format(self.uidPrefix, name),
+                    self.fieldName.recordType: RecordType.macOSXServerWiki,
+                    self.fieldName.shortNames: [name],
+                    self.fieldName.fullNames: [u&quot;Wiki: {}&quot;.format(name)],
</ins><span class="cx">                 }
</span><span class="cx">             )
</span><span class="cx">             self._recordsByName[name] = record
</span><span class="lines">@@ -114,8 +129,12 @@
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def recordsFromExpression(self, expression, records=None):
+        return succeed(())
</ins><span class="cx"> 
</span><del>-class DirectoryRecord(BaseDirectoryRecord):
</del><ins>+
+
+class DirectoryRecord(BaseDirectoryRecord, CalendarDirectoryRecordMixin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Mac OS X Server Wiki directory record.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -133,9 +152,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Look up the access level for a record in this wiki.
</span><span class="cx"> 
</span><del>-        @param user: The record to check access for.
</del><ins>+        @param user: The record to check access for.  A value of None means
+            unauthenticated
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        guid = record.guid
</del><ins>+        if record is None:
+            uid = u&quot;unauthenticated&quot;
+        else:
+            uid = record.uid
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             # FIXME: accessForUserToWiki() API is lame.
</span><span class="lines">@@ -145,7 +168,7 @@
</span><span class="cx">             # When we do that note: isn't there a getPage() in twisted.web?
</span><span class="cx"> 
</span><span class="cx">             access = yield accessForUserToWiki(
</span><del>-                guid, self.shortNames[0],
</del><ins>+                uid, self.shortNames[0],
</ins><span class="cx">                 host=self.service.wikiHost,
</span><span class="cx">                 port=self.service.wikiPort,
</span><span class="cx">             )
</span><span class="lines">@@ -189,4 +212,124 @@
</span><span class="cx"> 
</span><span class="cx">         except KeyError:
</span><span class="cx">             self.log.error(&quot;Unknown wiki access level: {level}&quot;, level=access)
</span><del>-            return WikiAccessLevel.none
</del><ins>+            returnValue(WikiAccessLevel.none)
+
+
+@inlineCallbacks
+def getWikiACL(resource, request):
+    &quot;&quot;&quot;
+    Ask the wiki server we're paired with what level of access the authnUser has.
+
+    Returns an ACL.
+
+    Wiki authentication is a bit tricky because the end-user accessing a group
+    calendar may not actually be enabled for calendaring.  Therefore in that
+    situation, the authzUser will have been replaced with the wiki principal
+    in locateChild( ), so that any changes the user makes will have the wiki
+    as the originator.  The authnUser will always be the end-user.
+    &quot;&quot;&quot;
+    from twistedcaldav.directory.principal import DirectoryPrincipalResource
+
+    if (
+        not hasattr(resource, &quot;record&quot;) or
+        resource.record.recordType != RecordType.macOSXServerWiki
+    ):
+        returnValue(None)
+
+    if hasattr(request, 'wikiACL'):
+        returnValue(request.wikiACL)
+
+    wikiRecord = resource.record
+    wikiID = wikiRecord.shortNames[0]
+    userRecord = None
+
+    try:
+        url = str(request.authnUser.children[0])
+        principal = (yield request.locateResource(url))
+        if isinstance(principal, DirectoryPrincipalResource):
+            userRecord = principal.record
+    except:
+        # TODO: better error handling
+        pass
+
+    try:
+        access = yield wikiRecord.accessForRecord(userRecord)
+
+        # The ACL we returns has ACEs for the end-user and the wiki principal
+        # in case authzUser is the wiki principal.
+        if access == WikiAccessLevel.read:
+            request.wikiACL = davxml.ACL(
+                davxml.ACE(
+                    request.authnUser,
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+
+                        # We allow write-properties so that direct sharees can change
+                        # e.g. calendar color properties
+                        davxml.Privilege(davxml.WriteProperties()),
+                    ),
+                    TwistedACLInheritable(),
+                ),
+                davxml.ACE(
+                    davxml.Principal(
+                        davxml.HRef.fromString(&quot;/principals/wikis/%s/&quot; % (wikiID,))
+                    ),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    ),
+                    TwistedACLInheritable(),
+                )
+            )
+            returnValue(request.wikiACL)
+
+        elif access in (WikiAccessLevel.write, WikiAccessLevel.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(&quot;/principals/wikis/%s/&quot; % (wikiID,))
+                    ),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                        davxml.Privilege(davxml.Write()),
+                    ),
+                    TwistedACLInheritable(),
+                )
+            )
+            returnValue(request.wikiACL)
+
+        else:  # &quot;no-access&quot;:
+
+            if userRecord is None:
+                # Return a 401 so they have an opportunity to log in
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr,
+                ))
+                raise HTTPError(response)
+
+            raise HTTPError(
+                StatusResponse(
+                    responsecode.FORBIDDEN,
+                    &quot;You are not allowed to access this wiki&quot;
+                )
+            )
+
+    except HTTPError:
+        # pass through the HTTPError we might have raised above
+        raise
+
+    except Exception, e:
+        log.error(&quot;Wiki ACL lookup failed: %s&quot; % (e,))
+        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, &quot;Wiki ACL lookup failed&quot;))
</ins></span></pre>
</div>
</div>

</body>
</html>