[CalendarServer-changes] [13015] CalendarServer/branches/users/sagen/move2who-4

source_changes at macosforge.org source_changes at macosforge.org
Thu Mar 27 19:06:05 PDT 2014


Revision: 13015
          http://trac.calendarserver.org//changeset/13015
Author:   sagen at apple.com
Date:     2014-03-27 19:06:05 -0700 (Thu, 27 Mar 2014)
Log Message:
-----------
Port wiki and sharing to twext.who

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py
    CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py
    CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py
    CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py
    CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py
    CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py
    CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py
    CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py

Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/platform/darwin/wiki.py
===================================================================
--- 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)
@@ -27,16 +27,17 @@
 
 log = Logger()
 
+
 @inlineCallbacks
-def guidForAuthToken(token, host="localhost", port=80):
+def uidForAuthToken(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.
+    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 guid (C{str}) if successful, or
+    @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,)
@@ -44,8 +45,10 @@
     try:
         response = json.loads(jsonResponse)
     except Exception, e:
-        log.error("Error parsing JSON response from webauth: %s (%s)" %
-            (jsonResponse, str(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"])
@@ -57,10 +60,10 @@
 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
+    the given user (uid) has to the given wiki (in wiki short-name
     form).
 
-    @param user: The GUID of the user
+    @param user: The UID of the user
     @type user: C{str}
     @param wiki: The short name of the wiki
     @type wiki: C{str}
@@ -69,8 +72,9 @@
         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)
+    url = "http://%s:%s/cal/accessLevelForUserWikiCalendar/%s/%s" % (
+        host, port, user, wiki
+    )
     return _getPage(url, host, port)
 
 

Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/provision/root.py
===================================================================
--- 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)
@@ -19,29 +19,29 @@
     "RootResource",
 ]
 
+from calendarserver.platform.darwin.wiki import uidForAuthToken
 from twext.python.log import Logger
-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
-
+from twext.who.idirectory import RecordType
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.python.reflect import namedClass
+from twisted.web.error import Error as WebError
 from twisted.web.xmlrpc import Proxy
-from twisted.web.error import Error as WebError
-
+from twistedcaldav.cache import DisabledCache
+from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
 from twistedcaldav.cache import _CachedResponseResource
-from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
-from twistedcaldav.cache import DisabledCache
 from twistedcaldav.config import config
+from twistedcaldav.directory.principal import DirectoryPrincipalResource
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
 from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
 from twistedcaldav.extensions import ReadOnlyResourceMixIn
 from twistedcaldav.resource import CalDAVComplianceMixIn
-from twistedcaldav.directory.principal import DirectoryPrincipalResource
-from calendarserver.platform.darwin.wiki import guidForAuthToken
+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
 
 log = Logger()
 
@@ -234,20 +234,20 @@
                     record = None
                     try:
                         if wikiConfig.LionCompatibility:
-                            guid = None
+                            uid = None
                             proxy = Proxy(wikiConfig["URL"])
                             username = (yield proxy.callRemote(wikiConfig["UserMethod"], token))
                             directory = request.site.resource.getDirectory()
-                            record = directory.recordWithShortName("users", username)
+                            record = yield directory.recordWithShortName(RecordType.user, username)
                             if record is not None:
-                                guid = record.guid
+                                uid = record.uid
                         else:
-                            guid = (yield guidForAuthToken(token))
-                            if guid == "unauthenticated":
-                                guid = None
+                            uid = (yield uidForAuthToken(token))
+                            if uid == "unauthenticated":
+                                uid = None
 
                     except WebError, w:
-                        guid = None
+                        uid = None
                         # FORBIDDEN status means it's an unknown token
                         if int(w.status) == responsecode.NOT_FOUND:
                             log.debug("Unknown wiki token: %s" % (token,))
@@ -257,18 +257,18 @@
 
                     except Exception, e:
                         log.error("Failed to look up wiki token (%s)" % (e,))
-                        guid = None
+                        uid = None
 
-                    if guid is not None:
-                        log.debug("Wiki lookup returned guid: %s" % (guid,))
+                    if uid is not None:
+                        log.debug("Wiki lookup returned uid: %s" % (uid,))
                         principal = None
                         directory = request.site.resource.getDirectory()
-                        record = directory.recordWithGUID(guid)
+                        record = yield directory.recordWithUID(uid)
                         if record is not None:
                             username = record.shortNames[0]
                             log.debug("Wiki user record for user %s : %s" % (username, record))
                             for collection in self.principalCollections():
-                                principal = collection.principalForRecord(record)
+                                principal = yield collection.principalForRecord(record)
                                 if principal is not None:
                                     break
 
@@ -317,7 +317,7 @@
         elif (len(segments) > 2 and segments[0] in ("calendars", "principals") and
             (
                 segments[1] == "wikis" or
-                (segments[1] == "__uids__" and segments[2].startswith("wiki-"))
+                (segments[1] == "__uids__" and segments[2].startswith(WikiDirectoryService.uidPrefix))
             )
         ):
             # This is a wiki-related calendar resource. SACLs are not checked.

Modified: CalendarServer/branches/users/sagen/move2who-4/calendarserver/tap/util.py
===================================================================
--- 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)
@@ -28,7 +28,6 @@
 
 import errno
 import os
-from time import sleep
 from socket import fromfd, AF_UNIX, SOCK_STREAM, socketpair
 import psutil
 
@@ -46,17 +45,13 @@
 from twisted.internet import reactor as _reactor
 from twisted.internet.reactor import addSystemEventTrigger
 from twisted.internet.tcp import Connection
-from twisted.python.reflect import namedClass
-# from twisted.python.failure import Failure
 
 from twistedcaldav.bind import doBind
 from twistedcaldav.cache import CacheStoreNotifierFactory
-from twistedcaldav.directory import calendaruserproxy
 from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.wiki import WikiDirectoryService
 from calendarserver.push.notifier import NotifierFactory
 from calendarserver.push.applepush import APNSubscriptionResource
 from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
@@ -98,8 +93,6 @@
 from urllib import quote
 from twisted.python.usage import UsageError
 
-from txdav.dps.client import DirectoryService as DirectoryProxyClientService
-
 from twext.who.checker import UsernamePasswordCredentialChecker
 from twext.who.checker import HTTPDigestCredentialChecker
 from twisted.cred.error import UnauthorizedLogin
@@ -281,96 +274,6 @@
 
 
 
-def REMOVEMEdirectoryFromConfig(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
-
-    #
-    # 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("Configuring directory service of type: {directoryType}",
-        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("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
-
-
 # MOVE2WHO -- should we move this class somewhere else?
 class PrincipalCredentialChecker(object):
     credentialInterfaces = (IPrincipalCredentials,)

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/augment.py
===================================================================
--- 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)
@@ -81,6 +81,7 @@
     "locations": "Location",
     "resources": "Resource",
     "addresses": "Address",
+    "wikis": "Wiki",
 }
 
 

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/calendar.py
===================================================================
--- 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)
@@ -38,7 +38,7 @@
 from twistedcaldav.directory.common import uidsResourceName, \
     CommonUIDProvisioningResource, CommonHomeTypeProvisioningResource
 
-from twistedcaldav.directory.wiki import getWikiACL
+from txdav.who.wiki import getWikiACL
 from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource, \
     DAVResourceWithChildrenMixin
 from twistedcaldav.resource import CalendarHomeResource

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/principal.py
===================================================================
--- 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)
@@ -48,7 +48,7 @@
 from twistedcaldav.directory.util import (
     formatLink, formatLinks, formatPrincipals, formatList
 )
-from twistedcaldav.directory.wiki import getWikiACL
+from txdav.who.wiki import getWikiACL
 from twistedcaldav.extensions import (
     ReadOnlyResourceMixIn, DAVPrincipalResource, DAVResourceWithChildrenMixin
 )
@@ -1308,6 +1308,7 @@
         Return a CUA for this principal, preferring in this order:
             urn:uuid: form
             mailto: form
+            /principal/__uids__/ form
             first in calendarUserAddresses( ) list
         """
         return self.record.canonicalCalendarUserAddress()

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/directory/wiki.py
===================================================================
--- 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)
@@ -250,8 +250,8 @@
 
 
 
-def getWikiACL(resource, request):
-    return succeed(None)
+# def getWikiACL(resource, request):
+#     return succeed(None)
 # @inlineCallbacks
 # def getWikiACL(resource, request):
 #     """

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/sharing.py
===================================================================
--- 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)
@@ -44,7 +44,7 @@
 from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
+from txdav.who.wiki import RecordType as WikiRecordType, WikiAccessLevel
 from twistedcaldav.linkresource import LinkFollowerMixIn
 
 
@@ -269,15 +269,13 @@
         if self._newStoreObject.direct():
             owner = yield self.principalForUID(self._newStoreObject.ownerHome().uid())
             sharee = yield self.principalForUID(self._newStoreObject.viewerHome().uid())
-            if owner.record.recordType == WikiDirectoryService.recordType_wikis:
+            if owner.record.recordType == WikiRecordType.macOSXServerWiki:
                 # Access level comes from what the wiki has granted to the
                 # sharee
-                userID = sharee.record.guid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
-                if access == "read":
+                access = (yield owner.record.accessForRecord(sharee.record))
+                if access == WikiAccessLevel.read:
                     returnValue("read-only")
-                elif access in ("write", "admin"):
+                elif access == WikiAccessLevel.write:
                     returnValue("read-write")
                 else:
                     returnValue(None)
@@ -502,7 +500,7 @@
 
 
     @inlineCallbacks
-    def inviteSingleUserToShare(self, userid, cn, ace, summary, request): #@UnusedVariable
+    def inviteSingleUserToShare(self, userid, cn, ace, summary, request):  #@UnusedVariable
 
         # We currently only handle local users
         sharee = yield self.principalForCalendarUserAddress(userid)

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/stdconfig.py
===================================================================
--- 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)
@@ -1257,7 +1257,13 @@
 
     # Default DirectoryRealmName from ServerHostName
     if not configDict.DirectoryRealmName:
-        configDict.DirectoryRealmName = configDict.ServerHostName
+        # Use system-wide realm on OSX
+        try:
+            import ServerFoundation
+            realmName = ServerFoundation.XSAuthenticator.defaultRealm()
+            configDict.DirectoryRealmName = realmName
+        except ImportError:
+            configDict.DirectoryRealmName = configDict.ServerHostName
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/storebridge.py
===================================================================
--- 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)
@@ -15,77 +15,89 @@
 # limitations under the License.
 ##
 
+import collections
+import hashlib
+import time
+from urlparse import urlsplit, urljoin
+import uuid
+
 from pycalendar.datetime import DateTime
-
 from twext.enterprise.locking import LockTimeout
 from twext.python.log import Logger
-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
-
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
 from twisted.internet.protocol import Protocol
 from twisted.python.hashlib import md5
 from twisted.python.util import FancyEqMixin
-
 from twistedcaldav import customxml, carddavxml, caldavxml, ical
-from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance, \
-    MaxInstances, NoUIDConflict
+from twistedcaldav.caldavxml import (
+    caldav_namespace, MaxAttendeesPerInstance, MaxInstances, NoUIDConflict
+)
 from twistedcaldav.carddavxml import carddav_namespace, NoUIDConflict as NovCardUIDConflict
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
-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
+from twistedcaldav.ical import (
+    Component as VCalendar, Property as VProperty, InvalidICalendarDataError,
+    iCalendarProductID, Component
+)
+from twistedcaldav.instance import (
+    InvalidOverriddenInstanceError, TooManyInstancesError
+)
 from twistedcaldav.memcachelock import MemcacheLockTimeoutError
 from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
 from twistedcaldav.resource import CalDAVResource, DefaultAlarmPropertyMixin
 from twistedcaldav.scheduling_store.caldav.resource import ScheduleInboxResource
-from twistedcaldav.sharing import invitationBindStatusToXMLMap, \
-    invitationBindModeToXMLMap
+from twistedcaldav.sharing import (
+    invitationBindStatusToXMLMap, invitationBindModeToXMLMap
+)
 from twistedcaldav.util import bestAcceptType
 from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
-
 from txdav.base.propertystore.base import PropertyName
-from txdav.caldav.icalendarstore import QuotaExceeded, AttachmentStoreFailed, \
-    AttachmentStoreValidManagedID, AttachmentRemoveFailed, \
-    AttachmentDropboxNotAllowed, InvalidComponentTypeError, \
-    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError, \
-    InvalidPerUserDataMerge, \
-    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation, \
+from txdav.caldav.icalendarstore import (
+    QuotaExceeded, AttachmentStoreFailed,
+    AttachmentStoreValidManagedID, AttachmentRemoveFailed,
+    AttachmentDropboxNotAllowed, InvalidComponentTypeError,
+    TooManyAttendeesError, InvalidCalendarAccessError, ValidOrganizerError,
+    InvalidPerUserDataMerge,
+    AttendeeAllowedError, ResourceDeletedError, InvalidAttachmentOperation,
     ShareeAllowedError, DuplicatePrivateCommentsError, InvalidSplit
-from txdav.carddav.iaddressbookstore import KindChangeNotAllowedError, \
-    GroupWithUnsharedAddressNotAllowedError
-from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
+)
+from txdav.carddav.iaddressbookstore import (
+    KindChangeNotAllowedError, GroupWithUnsharedAddressNotAllowedError
+)
+from txdav.common.datastore.sql_tables import (
+    _BIND_MODE_READ, _BIND_MODE_WRITE,
     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
-from txdav.common.icommondatastore import NoSuchObjectResourceError, \
-    TooManyObjectResourcesError, ObjectResourceTooBigError, \
-    InvalidObjectResourceError, ObjectResourceNameNotAllowedError, \
-    ObjectResourceNameAlreadyExistsError, UIDExistsError, \
-    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove, \
+)
+from txdav.common.icommondatastore import (
+    NoSuchObjectResourceError,
+    TooManyObjectResourcesError, ObjectResourceTooBigError,
+    InvalidObjectResourceError, ObjectResourceNameNotAllowedError,
+    ObjectResourceNameAlreadyExistsError, UIDExistsError,
+    UIDExistsElsewhereError, InvalidUIDError, InvalidResourceMove,
     InvalidComponentForStoreError
+)
 from txdav.idav import PropertyChangeNotAllowedError
+from txdav.who.wiki import RecordType as WikiRecordType
 from txdav.xml import element as davxml, element
 from txdav.xml.base import dav_namespace, WebDAVUnknownElement, encodeXMLName
+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
 
-from urlparse import urlsplit, urljoin
-import collections
-import hashlib
-import time
-import uuid
+
 """
 Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
 L{txdav.carddav.iaddressbookstore} and those in L{twistedcaldav}.
@@ -93,6 +105,7 @@
 
 log = Logger()
 
+
 class _NewStorePropertiesWrapper(object):
     """
     Wrap a new-style property store (a L{txdav.idav.IPropertyStore}) in the old-
@@ -1983,15 +1996,13 @@
         """
         if invite.mode in (_BIND_MODE_DIRECT,):
             ownerUID = invite.ownerUID
-            owner = self.principalForUID(ownerUID)
+            owner = yield self.principalForUID(ownerUID)
             shareeUID = invite.shareeUID
-            if owner.record.recordType == WikiDirectoryService.recordType_wikis:
+            if owner.record.recordType == WikiRecordType.macOSXServerWiki:
                 # Access level comes from what the wiki has granted to the
                 # sharee
-                sharee = self.principalForUID(shareeUID)
-                userID = sharee.record.uid
-                wikiID = owner.record.shortNames[0]
-                access = (yield getWikiAccess(userID, wikiID))
+                sharee = yield self.principalForUID(shareeUID)
+                access = (yield owner.record.accessForRecord(sharee.record))
                 if access == "read":
                     returnValue("read-only")
                 elif access in ("write", "admin"):

Modified: CalendarServer/branches/users/sagen/move2who-4/twistedcaldav/test/test_sharing.py
===================================================================
--- 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)
@@ -19,30 +19,31 @@
 from txweb2.http_headers import MimeType
 from txweb2.iweb import IResponse
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 
 from twistedcaldav import customxml
-from twistedcaldav import sharing
 from twistedcaldav.config import config
 from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.sharing import WikiDirectoryService
 from twistedcaldav.test.test_cache import StubResponseCacheResource
 from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
 
-from txdav.caldav.datastore.test.util import buildDirectory
 from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT
 from txdav.xml import element as davxml
 from txdav.xml.parser import WebDAVDocument
 
 from xml.etree.cElementTree import XML
+from txdav.who.wiki import (
+    DirectoryRecord as WikiDirectoryRecord,
+    DirectoryService as WikiDirectoryService,
+    RecordType as WikiRecordType,
+    WikiAccessLevel
+)
 
+sharedOwnerType = davxml.ResourceType.sharedownercalendar  # @UndefinedVariable
+regularCalendarType = davxml.ResourceType.calendar  # @UndefinedVariable
 
-sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
-regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
 
 
-
 def normalize(x):
     """
     Normalize some XML by parsing it, collapsing whitespace, and
@@ -63,8 +64,8 @@
         self.fullName = name
         self.guid = name
         self.calendarUserAddresses = set((cuaddr,))
-        if name.startswith("wiki-"):
-            recordType = WikiDirectoryService.recordType_wikis
+        if name.startswith(WikiDirectoryService.uidPrefix):
+            recordType = WikiRecordType.macOSXServerWiki
         else:
             recordType = None
         self.recordType = recordType
@@ -131,42 +132,45 @@
         super(SharingTests, self).configure()
         self.patch(config.Sharing, "Enabled", True)
         self.patch(config.Sharing.Calendars, "Enabled", True)
+        self.patch(config.Authentication.Wiki, "Enabled", True)
 
 
     @inlineCallbacks
     def setUp(self):
         yield super(SharingTests, self).setUp()
 
-        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
+        # FIXME: not sure what these were for:
 
-        @patched
-        def principalForCalendarUserAddress(resourceSelf, cuaddr):
-            if "bogus" in cuaddr:
-                return None
-            else:
-                return FakePrincipal(cuaddr, self)
+        #     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
 
-        @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()
+        #     @patched
+        #     def principalForCalendarUserAddress(resourceSelf, cuaddr):
+        #         if "bogus" in cuaddr:
+        #             return None
+        #         else:
+        #             return FakePrincipal(cuaddr, self)
 
-        @patched
-        def principalForUID(resourceSelf, principalUID):
-            return FakePrincipal("urn:uuid:" + principalUID, self)
+        #     @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()
 
+        #     @patched
+        #     def principalForUID(resourceSelf, principalUID):
+        #         return FakePrincipal("urn:uuid:" + principalUID, self)
+
         self.resource = yield self._getResource()
 
 
@@ -321,7 +325,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             )
@@ -351,7 +355,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             )
@@ -400,7 +404,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadAccess()),
                 customxml.InviteStatusNoResponse(),
             )
@@ -475,21 +479,21 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user03"),
-                customxml.CommonName.fromString("USER03"),
+                customxml.CommonName.fromString("User 03"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user04"),
-                customxml.CommonName.fromString("USER04"),
+                customxml.CommonName.fromString("User 04"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
@@ -533,14 +537,14 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user04"),
-                customxml.CommonName.fromString("USER04"),
+                customxml.CommonName.fromString("User 04"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
@@ -584,14 +588,14 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user03"),
-                customxml.CommonName.fromString("USER03"),
+                customxml.CommonName.fromString("User 03"),
                 customxml.InviteAccess(customxml.ReadAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
@@ -701,7 +705,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             )
@@ -738,23 +742,24 @@
     @inlineCallbacks
     def wikiSetup(self):
         """
-        Create a wiki called C{wiki-testing}, and share it with the user whose
+        Create a wiki called C{[wiki]testing}, and share it with the user whose
         home is at /.  Return the name of the newly shared calendar in the
         sharee's home.
         """
 
-        self._sqlCalendarStore._directoryService = buildDirectory(homes=("wiki-testing",))
+        # self._sqlCalendarStore._directoryService = buildDirectory(homes=("wiki-testing",))
         wcreate = self._sqlCalendarStore.newTransaction("create wiki")
-        yield wcreate.calendarHomeWithUID("wiki-testing", create=True)
+        yield wcreate.calendarHomeWithUID(
+            u"{prefix}testing".format(prefix=WikiDirectoryService.uidPrefix),
+            create=True
+        )
         yield wcreate.commit()
 
-        newService = WikiDirectoryService()
-        newService.realmName = self.directory.realmName
-        self.directory.addService(newService)
-
         txn = self.transactionUnderTest()
-        sharee = yield self.homeUnderTest(name="user01")
-        sharer = yield txn.calendarHomeWithUID("wiki-testing")
+        sharee = yield self.homeUnderTest(name="user01", create=True)
+        sharer = yield txn.calendarHomeWithUID(
+            u"{prefix}testing".format(prefix=WikiDirectoryService.uidPrefix),
+        )
         cal = yield sharer.calendarWithName("calendar")
         sharedName = yield cal.shareWith(sharee, _BIND_MODE_DIRECT)
         returnValue(sharedName)
@@ -767,13 +772,14 @@
         to the sharee, so that delegates of the sharee get the same level of
         access.
         """
+        sharedName = yield self.wikiSetup()
+        access = WikiAccessLevel.read
 
-        access = "read"
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, "getWikiAccess", stubWikiAccessMethod)
+        def stubAccessForRecord(*args):
+            return succeed(access)
 
-        sharedName = yield self.wikiSetup()
+        self.patch(WikiDirectoryRecord, "accessForRecord", stubAccessForRecord)
+
         request = SimpleStoreRequest(self, "GET", "/calendars/__uids__/user01/")
         collection = yield request.locateResource("/calendars/__uids__/user01/" + sharedName)
 
@@ -782,7 +788,7 @@
         self.assertFalse("<write/>" in acl.toxml())
 
         # Simulate the wiki server granting Read-Write access
-        access = "write"
+        access = WikiAccessLevel.write
         acl = (yield collection.shareeAccessControlList(request))
         self.assertTrue("<write/>" in acl.toxml())
 
@@ -795,10 +801,13 @@
         un-share that collection.
         """
         sharedName = yield self.wikiSetup()
-        access = "write"
-        def stubWikiAccessMethod(userID, wikiID):
-            return access
-        self.patch(sharing, "getWikiAccess", stubWikiAccessMethod)
+        access = WikiAccessLevel.write
+
+        def stubAccessForRecord(*args):
+            return succeed(access)
+
+        self.patch(WikiDirectoryRecord, "accessForRecord", stubAccessForRecord)
+
         @inlineCallbacks
         def listChildrenViaPropfind():
             authRecord = yield self.directory.recordWithUID(u"user01")
@@ -814,9 +823,10 @@
             seq.remove(shortest)
             filtered = [elem[len(shortest):].rstrip("/") for elem in seq]
             returnValue(filtered)
+
         childNames = yield listChildrenViaPropfind()
         self.assertIn(sharedName, childNames)
-        access = "no-access"
+        access = WikiAccessLevel.none
         childNames = yield listChildrenViaPropfind()
         self.assertNotIn(sharedName, childNames)
 
@@ -841,7 +851,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
@@ -871,7 +881,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
@@ -915,7 +925,7 @@
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:uuid:user02"),
-                customxml.CommonName.fromString("USER02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),

Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/augment.py
===================================================================
--- 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)
@@ -417,3 +417,7 @@
 
     def verifyHTTPDigest(self, *args):
         return self._baseRecord.verifyHTTPDigest(*args)
+
+
+    def accessForRecord(self, record):
+        return self._baseRecord.accessForRecord(record)

Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/directory.py
===================================================================
--- 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)
@@ -178,6 +178,7 @@
         "location": "locations",
         "resource": "resources",
         "user": "users",
+        "macOSXServerWiki": "wikis",
         "readDelegateGroup": "readDelegateGroups",
         "writeDelegateGroup": "writeDelegateGroups",
         "readDelegatorGroup": "readDelegatorGroups",

Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/util.py
===================================================================
--- 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)
@@ -23,7 +23,7 @@
 
 from calendarserver.tap.util import getDBPool, storeFromConfig
 from twext.who.idirectory import (
-    RecordType, DirectoryConfigurationError, FieldName
+    RecordType, DirectoryConfigurationError
 )
 from twext.who.ldap import DirectoryService as LDAPDirectoryService
 from twext.who.util import ConstantsContainer
@@ -36,6 +36,7 @@
     FieldName as CalFieldName
 )
 from txdav.who.xml import DirectoryService as XMLDirectoryService
+from txdav.who.wiki import DirectoryService as WikiDirectoryService
 
 
 log = Logger()
@@ -160,15 +161,29 @@
         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(

Modified: CalendarServer/branches/users/sagen/move2who-4/txdav/who/wiki.py
===================================================================
--- 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)
@@ -23,21 +23,28 @@
     "WikiAccessLevel",
 ]
 
-from twisted.python.constants import Names, NamedConstant
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-from twisted.web.error import Error as WebError
-
+from calendarserver.platform.darwin.wiki import accessForUserToWiki
+from twext.internet.gaiendpoint import MultiFailure
 from twext.python.log import Logger
-from twext.internet.gaiendpoint import MultiFailure
-from .idirectory import FieldName
 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.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
 from txweb2 import responsecode
+from txweb2.auth.wrapper import UnauthorizedResponse
+from txweb2.dav.resource import TwistedACLInheritable
+from txweb2.http import HTTPError, StatusResponse
 
-from calendarserver.platform.darwin.wiki import accessForUserToWiki
 
+log = Logger()
 
 
 # FIXME: Should this be Flags?
@@ -59,13 +66,20 @@
     Mac OS X Server Wiki directory service.
     """
 
-    uidPrefix = "[wiki]"
+    uidPrefix = u"[wiki]"
 
     recordType = RecordType
 
+    fieldName = ConstantsContainer((
+        BaseFieldName,
+        FieldName,
+    ))
 
-    def __init__(self):
-        BaseDirectoryService.__init__(self)
+
+    def __init__(self, realmName, wikiHost, wikiPort):
+        BaseDirectoryService.__init__(self, realmName)
+        self.wikiHost = wikiHost
+        self.wikiPort = wikiPort
         self._recordsByName = {}
 
 
@@ -91,9 +105,10 @@
             record = DirectoryRecord(
                 self,
                 {
-                    FieldName.uid: "{}{}".format(self.uidPrefix, name),
-                    FieldName.recordType: RecordType.macOSXServerWiki,
-                    FieldName.shortNames: [name],
+                    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
@@ -114,8 +129,12 @@
         return succeed(None)
 
 
+    def recordsFromExpression(self, expression, records=None):
+        return succeed(())
 
-class DirectoryRecord(BaseDirectoryRecord):
+
+
+class DirectoryRecord(BaseDirectoryRecord, CalendarDirectoryRecordMixin):
     """
     Mac OS X Server Wiki directory record.
     """
@@ -133,9 +152,13 @@
         """
         Look up the access level for a record in this wiki.
 
-        @param user: The record to check access for.
+        @param user: The record to check access for.  A value of None means
+            unauthenticated
         """
-        guid = record.guid
+        if record is None:
+            uid = u"unauthenticated"
+        else:
+            uid = record.uid
 
         try:
             # FIXME: accessForUserToWiki() API is lame.
@@ -145,7 +168,7 @@
             # When we do that note: isn't there a getPage() in twisted.web?
 
             access = yield accessForUserToWiki(
-                guid, self.shortNames[0],
+                uid, self.shortNames[0],
                 host=self.service.wikiHost,
                 port=self.service.wikiPort,
             )
@@ -189,4 +212,124 @@
 
         except KeyError:
             self.log.error("Unknown wiki access level: {level}", level=access)
-            return WikiAccessLevel.none
+            returnValue(WikiAccessLevel.none)
+
+
+ at 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/%s/" % (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("/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 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, e:
+        log.error("Wiki ACL lookup failed: %s" % (e,))
+        raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Wiki ACL lookup failed"))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140327/38472856/attachment-0001.html>


More information about the calendarserver-changes mailing list