[CalendarServer-changes] [12819] CalendarServer/branches/users/sagen/move2who

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 5 10:24:32 PST 2014


Revision: 12819
          http://trac.calendarserver.org//changeset/12819
Author:   sagen at apple.com
Date:     2014-03-05 10:24:32 -0800 (Wed, 05 Mar 2014)
Log Message:
-----------
Checking in the work-in-progress.

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py
    CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py
    CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py
    CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py
    CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py
    CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py
    CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py
    CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py
    CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py
    CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py
    CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py
    CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py
    CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml
    CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py
    CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py
    CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml
    CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py
    CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py
    CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py
    CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py

Added Paths:
-----------
    CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -103,10 +103,15 @@
                         else:
                             return uid
 
-                    uidn = convertUIDtoShortName(uidn)
-                    if uidz:
-                        uidz = convertUIDtoShortName(uidz)
+                    # MOVE2WHO
+                    # Better to stick the records directly on the request at
+                    # an earlier point, since we can't do anything deferred
+                    # in here.
 
+                    # uidn = convertUIDtoShortName(uidn)
+                    # if uidz:
+                    #     uidz = convertUIDtoShortName(uidz)
+
                     if uidn and uidz:
                         uid = '"%s as %s"' % (uidn, uidz,)
                     else:

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -27,7 +27,7 @@
 from txweb2.http import HTTPError, StatusResponse, RedirectResponse
 
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.python.reflect import namedClass
 from twisted.web.xmlrpc import Proxy
 from twisted.web.error import Error as WebError
@@ -110,7 +110,7 @@
 
 
     def defaultAccessControlList(self):
-        return config.RootResourceACL
+        return succeed(config.RootResourceACL)
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -820,23 +820,25 @@
 
 
     def defaultAccessControlList(self):
-        return davxml.ACL(
-            # DAV:Read for authenticated principals
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
+        return succeed(
+            davxml.ACL(
+                # DAV:Read for authenticated principals
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
                 ),
-                davxml.Protected(),
-            ),
-            # DAV:Write for authenticated principals
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Write()),
+                # DAV:Write for authenticated principals
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Write()),
+                    ),
+                    davxml.Protected(),
                 ),
-                davxml.Protected(),
-            ),
+            )
         )
 
 

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1011,14 +1011,17 @@
                 namespace=config.GroupCaching.MemcachedPool,
                 useExternalProxies=config.GroupCaching.UseExternalProxies,
             )
+            newGroupCacher = NewGroupCacher(DirectoryProxyClientService(None))
         else:
             groupCacher = None
+            newGroupCacher = None
 
         def decorateTransaction(txn):
             txn._pushDistributor = pushDistributor
             txn._rootResource = result.rootResource
             txn._mailRetriever = mailRetriever
             txn._groupCacher = groupCacher
+            txn._newGroupCacher = newGroupCacher
 
         store.callWithNewTransactions(decorateTransaction)
 
@@ -1392,7 +1395,9 @@
 
             # Optionally enable Directory Proxy
             if config.DirectoryProxy.Enabled:
-                dps = DirectoryProxyServiceMaker().makeService(None)
+                dps = DirectoryProxyServiceMaker().makeService(
+                    None, store=store
+                )
                 dps.setServiceParent(result)
 
             def decorateTransaction(txn):
@@ -1936,14 +1941,17 @@
                     namespace=config.GroupCaching.MemcachedPool,
                     useExternalProxies=config.GroupCaching.UseExternalProxies
                 )
+                newGroupCacher = NewGroupCacher(DirectoryProxyClientService(None))
             else:
                 groupCacher = None
+                newGroupCacher = None
 
             def decorateTransaction(txn):
                 txn._pushDistributor = None
                 txn._rootResource = rootResource
                 txn._mailRetriever = mailRetriever
                 txn._groupCacher = groupCacher
+                txn._newGroupCacher = newGroupCacher
 
             store.callWithNewTransactions(decorateTransaction)
 

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -100,7 +100,12 @@
 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
+from txweb2.dav.auth import IPrincipalCredentials
 log = Logger()
 
 
@@ -285,6 +290,10 @@
     """
     Create an L{AggregateDirectoryService} from the given configuration.
     """
+
+    # MOVE2WHO
+    return DirectoryProxyClientService("XYZZY")
+
     #
     # Setup the Augment Service
     #
@@ -370,7 +379,56 @@
     return directory
 
 
+# MOVE2WHO -- should we move this class somewhere else?
+class PrincipalCredentialChecker(object):
+    credentialInterfaces = (IPrincipalCredentials,)
 
+    @inlineCallbacks
+    def requestAvatarId(self, credentials):
+        credentials = IPrincipalCredentials(credentials)
+
+        if credentials.authnPrincipal is None:
+            raise UnauthorizedLogin("No such user: %s" % (credentials.credentials.username,))
+
+        # See if record is enabledForLogin
+        if not credentials.authnPrincipal.record.isLoginEnabled():
+            raise UnauthorizedLogin(
+                "User not allowed to log in: {user}".format(
+                    user=credentials.credentials.username
+                )
+            )
+
+        # Handle Kerberos as a separate behavior
+        try:
+            from twistedcaldav.authkerb import NegotiateCredentials
+        except ImportError:
+            NegotiateCredentials = None
+
+        if NegotiateCredentials and isinstance(credentials.credentials,
+                                               NegotiateCredentials):
+            # If we get here with Kerberos, then authentication has already succeeded
+            returnValue(
+                (
+                    credentials.authnPrincipal.principalURL(),
+                    credentials.authzPrincipal.principalURL(),
+                    credentials.authnPrincipal,
+                    credentials.authzPrincipal,
+                )
+            )
+        else:
+            if (yield credentials.authnPrincipal.record.verifyCredentials(credentials.credentials)):
+                returnValue(
+                    (
+                        credentials.authnPrincipal.principalURL(),
+                        credentials.authzPrincipal.principalURL(),
+                        credentials.authnPrincipal,
+                        credentials.authzPrincipal,
+                    )
+                )
+            else:
+                raise UnauthorizedLogin("Incorrect credentials for %s" % (credentials.credentials.username,))
+
+
 def getRootResource(config, newStore, resources=None):
     """
     Set up directory service and resource hierarchy based on config.
@@ -407,22 +465,27 @@
     addressBookResourceClass = DirectoryAddressBookHomeProvisioningResource
     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
     apnSubscriptionResourceClass = APNSubscriptionResource
+    principalResourceClass = DirectoryPrincipalProvisioningResource
 
     directory = newStore.directoryService()
+    principalCollection = principalResourceClass("/principals/", directory)
 
+
     #
     # Setup the ProxyDB Service
     #
-    proxydbClass = namedClass(config.ProxyDBService.type)
 
-    log.info("Configuring proxydb service of type: {cls}", cls=proxydbClass)
+    # MOVE2WHO
+    # proxydbClass = namedClass(config.ProxyDBService.type)
 
-    try:
-        calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
-    except IOError:
-        log.error("Could not start proxydb service")
-        raise
+    # log.info("Configuring proxydb service of type: {cls}", cls=proxydbClass)
 
+    # try:
+    #     calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+    # except IOError:
+    #     log.error("Could not start proxydb service")
+    #     raise
+
     #
     # Configure the Site and Wrappers
     #
@@ -431,7 +494,9 @@
 
     portal = Portal(auth.DavRealm())
 
-    portal.registerChecker(directory)
+    portal.registerChecker(UsernamePasswordCredentialChecker(directory))
+    portal.registerChecker(HTTPDigestCredentialChecker(directory))
+    portal.registerChecker(PrincipalCredentialChecker())
 
     realm = directory.realmName or ""
 
@@ -491,7 +556,7 @@
     #
     log.info("Setting up document root at: {root}", root=config.DocumentRoot)
 
-    principalCollection = directory.principalCollection
+    # principalCollection = directory.principalCollection
 
     if config.EnableCalDAV:
         log.info("Setting up calendar collection: {cls}", cls=calendarResourceClass)
@@ -712,6 +777,7 @@
     #
     # Configure ancillary data
     #
+    # MOVE2WHO
     log.info("Configuring authentication wrapper")
 
     overrides = {}

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -392,16 +392,17 @@
 
 
 
+ at inlineCallbacks
 def runListPrincipals(service, rootResource, directory, store, listPrincipals):
     try:
-        records = list(directory.listRecords(listPrincipals))
+        records = list((yield directory.listRecords(listPrincipals)))
         if records:
             printRecordList(records)
         else:
             print("No records of type %s" % (listPrincipals,))
     except UnknownRecordTypeError, e:
         usage(e)
-    return succeed(None)
+    returnValue(None)
 
 
 
@@ -411,7 +412,7 @@
     for principalID in principalIDs:
         # Resolve the given principal IDs to principals
         try:
-            principal = principalForPrincipalID(principalID, directory=directory)
+            principal = yield principalForPrincipalID(principalID, directory=directory)
         except ValueError:
             principal = None
 
@@ -525,7 +526,7 @@
 @inlineCallbacks
 def action_addProxy(rootResource, directory, store, principal, proxyType, *proxyIDs):
     for proxyID in proxyIDs:
-        proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+        proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
         if proxyPrincipal is None:
             print("Invalid principal ID: %s" % (proxyID,))
         else:
@@ -556,7 +557,7 @@
                 prettyPrincipal(principal)))
         memberURLs = []
         for proxyID in proxyIDs:
-            proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+            proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
             proxyURL = proxyPrincipal.url()
             memberURLs.append(davxml.HRef(proxyURL))
         membersProperty = davxml.GroupMemberSet(*memberURLs)
@@ -584,7 +585,7 @@
             membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
             if membersProperty.children:
                 for member in membersProperty.children:
-                    proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
+                    proxyPrincipal = yield principalForPrincipalID(str(member), directory=directory)
                     proxies[proxyType].append(proxyPrincipal.record.guid)
 
     returnValue((proxies['read'], proxies['write']))
@@ -594,7 +595,7 @@
 @inlineCallbacks
 def action_removeProxy(rootResource, directory, store, principal, *proxyIDs, **kwargs):
     for proxyID in proxyIDs:
-        proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+        proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
         if proxyPrincipal is None:
             print("Invalid principal ID: %s" % (proxyID,))
         else:
@@ -682,7 +683,7 @@
         print("Setting auto-accept-group for %s is not allowed." % (principal,))
 
     else:
-        groupPrincipal = principalForPrincipalID(autoAcceptGroup, directory=directory)
+        groupPrincipal = yield principalForPrincipalID(autoAcceptGroup, directory=directory)
         if groupPrincipal is None or groupPrincipal.record.recordType != "groups":
             print("Invalid principal ID: %s" % (autoAcceptGroup,))
         else:
@@ -705,9 +706,9 @@
 def action_getAutoAcceptGroup(rootResource, directory, store, principal):
     autoAcceptGroup = principal.getAutoAcceptGroup()
     if autoAcceptGroup:
-        record = directory.recordWithGUID(autoAcceptGroup)
+        record = yield directory.recordWithGUID(autoAcceptGroup)
         if record is not None:
-            groupPrincipal = directory.principalCollection.principalForUID(record.uid)
+            groupPrincipal = yield directory.principalCollection.principalForUID(record.uid)
             if groupPrincipal is not None:
                 print("Auto-accept-group for %s is %s" % (
                     prettyPrincipal(principal),
@@ -859,16 +860,16 @@
             kwargs[key] = newValue
 
     if create:
-        record = directory.createRecord(recordType, **kwargs)
+        record = yield directory.createRecord(recordType, **kwargs)
         kwargs['guid'] = record.guid
     else:
         try:
-            record = directory.updateRecord(recordType, **kwargs)
+            record = yield directory.updateRecord(recordType, **kwargs)
         except NotImplementedError:
             # Updating of directory information is not supported by underlying
             # directory implementation, but allow augment information to be
             # updated
-            record = directory.recordWithGUID(kwargs["guid"])
+            record = yield directory.recordWithGUID(kwargs["guid"])
             pass
 
     augmentService = directory.serviceForRecordType(recordType).augmentService
@@ -882,7 +883,7 @@
         augmentRecord.autoAcceptGroup = autoAcceptGroup
     (yield augmentService.addAugmentRecords([augmentRecord]))
     try:
-        directory.updateRecord(recordType, **kwargs)
+        yield directory.updateRecord(recordType, **kwargs)
     except NotImplementedError:
         # Updating of directory information is not supported by underlying
         # directory implementation, but allow augment information to be

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -587,7 +587,7 @@
 
     # Only allow administrators to access
     def defaultAccessControlList(self):
-        return davxml.ACL(*config.AdminACEs)
+        return succeed(davxml.ACL(*config.AdminACEs))
 
 
     def etag(self):

Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -48,15 +48,17 @@
 class WebCalendarResource (ReadOnlyResourceMixIn, DAVFile):
 
     def defaultAccessControlList(self):
-        return davxml.ACL(
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
+        return succeed(
+            davxml.ACL(
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
                 ),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-            ),
+            )
         )
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1456,6 +1456,8 @@
 
 ResourceType.calendarproxyread = ResourceType(Principal(), Collection(), CalendarProxyRead())
 ResourceType.calendarproxywrite = ResourceType(Principal(), Collection(), CalendarProxyWrite())
+ResourceType.calendarproxyreadfor = ResourceType(Principal(), Collection(), CalendarProxyReadFor())
+ResourceType.calendarproxywritefor = ResourceType(Principal(), Collection(), CalendarProxyWriteFor())
 
 ResourceType.timezones = ResourceType(Timezones())
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -65,7 +65,7 @@
     DAVResource,
 ):
     def defaultAccessControlList(self):
-        return config.ProvisioningResourceACL
+        return succeed(config.ProvisioningResourceACL)
 
 
     def etag(self):
@@ -93,7 +93,8 @@
 
         super(DirectoryAddressBookHomeProvisioningResource, self).__init__()
 
-        self.directory = IDirectoryService(directory)
+        # MOVE2WHO
+        self.directory = directory  # IDirectoryService(directory)
         self._url = url
         self._newStore = store
 
@@ -103,7 +104,7 @@
         #
         # Create children
         #
-        for recordType in self.directory.recordTypes():
+        for recordType in [r.name for r in self.directory.recordTypes()]:
             self.putChild(recordType, DirectoryAddressBookHomeTypeProvisioningResource(self, recordType))
 
         self.putChild(uidsResourceName, DirectoryAddressBookHomeUIDProvisioningResource(self))
@@ -114,7 +115,7 @@
 
 
     def listChildren(self):
-        return self.directory.recordTypes()
+        return [r.name for r in self.directory.recordTypes()]
 
 
     def principalCollections(self):
@@ -129,12 +130,13 @@
         return self.directory.principalCollection.principalForRecord(record)
 
 
+    @inlineCallbacks
     def homeForDirectoryRecord(self, record, request):
-        uidResource = self.getChild(uidsResourceName)
+        uidResource = yield self.getChild(uidsResourceName)
         if uidResource is None:
-            return None
+            returnValue(None)
         else:
-            return uidResource.homeResourceForRecord(record, request)
+            returnValue((yield uidResource.homeResourceForRecord(record, request)))
 
 
     ##

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -46,6 +46,7 @@
     "automatic",
 ))
 
+
 class AugmentRecord(object):
     """
     Augmented directory record information
@@ -75,13 +76,14 @@
         self.clonedFromDefault = False
 
 recordTypesMap = {
-    "users" : "User",
-    "groups" : "Group",
-    "locations" : "Location",
-    "resources" : "Resource",
-    "addresses" : "Address",
+    "users": "User",
+    "groups": "Group",
+    "locations": "Location",
+    "resources": "Resource",
+    "addresses": "Address",
 }
 
+
 class AugmentDB(object):
     """
     Abstract base class for an augment record database.
@@ -128,7 +130,6 @@
 
         @return: L{Deferred}
         """
-
         recordType = recordTypesMap[recordType]
 
         result = (yield self._lookupAugmentRecord(uid))
@@ -266,9 +267,9 @@
         self.xmlFiles = [fullServerPath(config.DataRoot, path) for path in xmlFiles]
         self.xmlFileStats = {}
         for path in self.xmlFiles:
-            self.xmlFileStats[path] = (0, 0) # mtime, size
+            self.xmlFileStats[path] = (0, 0)  # mtime, size
 
-        self.statSeconds = statSeconds # Don't stat more often than this value
+        self.statSeconds = statSeconds  # Don't stat more often than this value
         self.lastCached = 0
         self.db = {}
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -65,7 +65,7 @@
     DAVResource,
 ):
     def defaultAccessControlList(self):
-        return config.ProvisioningResourceACL
+        return succeed(config.ProvisioningResourceACL)
 
 
     def etag(self):
@@ -91,7 +91,8 @@
 
         super(DirectoryCalendarHomeProvisioningResource, self).__init__()
 
-        self.directory = IDirectoryService(directory)
+        # MOVE2WHO
+        self.directory = directory  # IDirectoryService(directory)
         self._url = url
         self._newStore = store
 
@@ -101,8 +102,9 @@
         #
         # Create children
         #
-        for recordType in self.directory.recordTypes():
-            self.putChild(recordType, DirectoryCalendarHomeTypeProvisioningResource(self, recordType))
+        # MOVE2WHO
+        for name, recordType in [(r.name + "s", r) for r in self.directory.recordTypes()]:
+            self.putChild(name, DirectoryCalendarHomeTypeProvisioningResource(self, name, recordType))
 
         self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
 
@@ -112,7 +114,8 @@
 
 
     def listChildren(self):
-        return self.directory.recordTypes()
+        # MOVE2WHO
+        return [r.name + "s" for r in self.directory.recordTypes()]
 
 
     def principalCollections(self):
@@ -127,12 +130,13 @@
         return self.directory.principalCollection.principalForRecord(record)
 
 
+    @inlineCallbacks
     def homeForDirectoryRecord(self, record, request):
-        uidResource = self.getChild(uidsResourceName)
+        uidResource = yield self.getChild(uidsResourceName)
         if uidResource is None:
-            return None
+            returnValue(None)
         else:
-            return uidResource.homeResourceForRecord(record, request)
+            returnValue((yield uidResource.homeResourceForRecord(record, request)))
 
 
     ##
@@ -156,23 +160,25 @@
     Resource which provisions calendar home collections of a specific
     record type as needed.
     """
-    def __init__(self, parent, recordType):
+    def __init__(self, parent, name, recordType):
         """
         @param parent: the parent of this resource
         @param recordType: the directory record type to provision.
         """
         assert parent is not None
+        assert name is not None
         assert recordType is not None
 
         super(DirectoryCalendarHomeTypeProvisioningResource, self).__init__()
 
         self.directory = parent.directory
+        self.name = name
         self.recordType = recordType
         self._parent = parent
 
 
     def url(self):
-        return joinURL(self._parent.url(), self.recordType)
+        return joinURL(self._parent.url(), self.name)
 
 
     def listChildren(self):
@@ -203,7 +209,7 @@
 
 
     def displayName(self):
-        return self.recordType
+        return self.name
 
 
     ##
@@ -258,7 +264,7 @@
             else:
                 # ...otherwise permissions are fixed, and are not subject to
                 # inheritance rules, etc.
-                return succeed(self.defaultAccessControlList())
+                return self.defaultAccessControlList()
 
         d = getWikiACL(self, request)
         d.addCallback(gotACL)

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -29,37 +29,40 @@
 
 import itertools
 import time
+import uuid
 
+from twext.python.log import Logger
+from twext.who.idirectory import RecordType as BaseRecordType
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from txweb2 import responsecode
-from txweb2.http import HTTPError, StatusResponse
-from txdav.xml import element as davxml
-from txdav.xml.base import dav_namespace
-from txweb2.dav.util import joinURL
-from txweb2.dav.noneprops import NonePropertyStore
-
-from twext.python.log import Logger
-
+from twisted.python.modules import getModule
 from twisted.web.template import XMLFile, Element, renderer
-from twisted.python.modules import getModule
-from twistedcaldav.extensions import DirectoryElement
+from twistedcaldav.config import config, fullServerPath
+from twistedcaldav.database import (
+    AbstractADBAPIDatabase, ADBAPISqliteMixin, ADBAPIPostgreSQLMixin
+)
 from twistedcaldav.directory.principal import formatLink
 from twistedcaldav.directory.principal import formatLinks
 from twistedcaldav.directory.principal import formatPrincipals
-
 from twistedcaldav.directory.util import normalizeUUID
-from twistedcaldav.config import config, fullServerPath
-from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin, \
-    ADBAPIPostgreSQLMixin
-from twistedcaldav.extensions import DAVPrincipalResource, \
-    DAVResourceWithChildrenMixin
+from twistedcaldav.extensions import (
+    DAVPrincipalResource, DAVResourceWithChildrenMixin
+)
+from twistedcaldav.extensions import DirectoryElement
 from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
 from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.resource import CalDAVComplianceMixIn
+from txdav.who.delegates import RecordType as DelegateRecordType
+from txdav.xml import element as davxml
+from txdav.xml.base import dav_namespace
+from txweb2 import responsecode
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.util import joinURL
+from txweb2.http import HTTPError, StatusResponse
 
 thisModule = getModule(__name__)
 log = Logger()
 
+
 class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
     def defaultAccessControlList(self):
         aces = (
@@ -86,13 +89,13 @@
             for principal in config.AdminPrincipals
         ))
 
-        return davxml.ACL(*aces)
+        return succeed(davxml.ACL(*aces))
 
 
     def accessControlList(self, request, inheritance=True, expanding=False,
                           inherited_aces=None):
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
+        return self.defaultAccessControlList()
 
 
 
@@ -119,13 +122,20 @@
         record = self.resource.parent.record
         resource = self.resource
         parent = self.resource.parent
+        try:
+            if isinstance(record.guid, uuid.UUID):
+                guid = str(record.guid).upper()
+            else:
+                guid = record.guid
+        except AttributeError:
+            guid = ""
         return tag.fillSlots(
             directoryGUID=record.service.guid,
             realm=record.service.realmName,
-            guid=record.guid,
-            recordType=record.recordType,
+            guid=guid,
+            recordType=record.recordType.name + "s",  # MOVE2WHO need mapping
             shortNames=record.shortNames,
-            fullName=record.fullName,
+            fullName=record.displayName,
             principalUID=parent.principalUID(),
             principalURL=formatLink(parent.principalURL()),
             proxyPrincipalUID=resource.principalUID(),
@@ -209,9 +219,13 @@
 
     def resourceType(self):
         if self.proxyType == "calendar-proxy-read":
-            return davxml.ResourceType.calendarproxyread #@UndefinedVariable
+            return davxml.ResourceType.calendarproxyread  # @UndefinedVariable
         elif self.proxyType == "calendar-proxy-write":
-            return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
+            return davxml.ResourceType.calendarproxywrite  # @UndefinedVariable
+        elif self.proxyType == "calendar-proxy-read-for":
+            return davxml.ResourceType.calendarproxyreadfor  # @UndefinedVariable
+        elif self.proxyType == "calendar-proxy-write-for":
+            return davxml.ResourceType.calendarproxywritefor  # @UndefinedVariable
         else:
             return super(CalendarUserProxyPrincipalResource, self).resourceType()
 
@@ -282,7 +296,9 @@
             newUIDs.add(principal.principalUID())
 
         # Get the old set of UIDs
-        oldUIDs = (yield self._index().getMembers(self.uid))
+        # oldUIDs = (yield self._index().getMembers(self.uid))
+        oldPrincipals = yield self.groupMembers()
+        oldUIDs = [p.uid for p in oldPrincipals]
 
         # Change membership
         yield self.setGroupMemberSetPrincipals(principals)
@@ -349,7 +365,7 @@
 
 
     @inlineCallbacks
-    def _expandMemberUIDs(self, uid=None, relatives=None, uids=None, infinity=False):
+    def _expandMemberPrincipals(self, uid=None, relatives=None, uids=None, infinity=False):
         if uid is None:
             uid = self.principalUID()
         if relatives is None:
@@ -360,14 +376,14 @@
         if uid not in uids:
             from twistedcaldav.directory.principal import DirectoryPrincipalResource
             uids.add(uid)
-            principal = self.pcollection.principalForUID(uid)
+            principal = yield self.pcollection.principalForUID(uid)
             if isinstance(principal, CalendarUserProxyPrincipalResource):
                 members = yield self._directGroupMembers()
                 for member in members:
                     if member.principalUID() not in uids:
                         relatives.add(member)
                         if infinity:
-                            yield self._expandMemberUIDs(member.principalUID(), relatives, uids, infinity=infinity)
+                            yield self._expandMemberPrincipals(member.principalUID(), relatives, uids, infinity=infinity)
             elif isinstance(principal, DirectoryPrincipalResource):
                 if infinity:
                     members = yield principal.expandedGroupMembers()
@@ -378,30 +394,45 @@
         returnValue(relatives)
 
 
+    def _recordTypeFromProxyType(self):
+        return {
+            "calendar-proxy-read": DelegateRecordType.readDelegateGroup,
+            "calendar-proxy-write": DelegateRecordType.writeDelegateGroup,
+            "calendar-proxy-read-for": DelegateRecordType.readDelegatorGroup,
+            "calendar-proxy-write-for": DelegateRecordType.writeDelegatorGroup,
+        }.get(self.proxyType)
+
+
     @inlineCallbacks
     def _directGroupMembers(self):
-        # Get member UIDs from database and map to principal resources
-        members = yield self._index().getMembers(self.uid)
-        found = []
-        for uid in members:
-            p = self.pcollection.principalForUID(uid)
-            if p:
-                # Only principals enabledForLogin can be a delegate
-                # (and groups as well)
-                if (p.record.enabledForLogin or
-                    p.record.recordType == p.record.service.recordType_groups):
-                    found.append(p)
-                # Make sure any outstanding deletion timer entries for
-                # existing principals are removed
-                yield self._index().refreshPrincipal(uid)
-            else:
-                self.log.warn("Delegate is missing from directory: %s" % (uid,))
+        """
+        Fault in the record representing the sub principal for this proxy type
+        (either read-only or read-write), then fault in the direct members of
+        that record.
+        """
+        memberPrincipals = []
+        record = yield self.parent.record.service.recordWithShortName(
+            self._recordTypeFromProxyType(),
+            self.parent.principalUID()
+        )
+        if record is not None:
+            memberRecords = yield record.members()
+            for record in memberRecords:
+                if record is not None:
+                    principal = yield self.pcollection.principalForRecord(
+                        record
+                    )
+                    if principal is not None:
+                        if (
+                            principal.record.loginAllowed or
+                            principal.record.recordType is BaseRecordType.group
+                        ):
+                            memberPrincipals.append(principal)
+        returnValue(memberPrincipals)
 
-        returnValue(found)
 
-
     def groupMembers(self):
-        return self._expandMemberUIDs()
+        return self._expandMemberPrincipals()
 
 
     @inlineCallbacks
@@ -410,18 +441,12 @@
         Return the complete, flattened set of principals belonging to this
         group.
         """
-        returnValue((yield self._expandMemberUIDs(infinity=True)))
+        returnValue((yield self._expandMemberPrincipals(infinity=True)))
 
 
     def groupMemberships(self):
-        # Get membership UIDs and map to principal resources
-        d = self._index().getMemberships(self.uid)
-        d.addCallback(lambda memberships: [
-            p for p
-            in [self.pcollection.principalForUID(uid) for uid in memberships]
-            if p
-        ])
-        return d
+        # Unlikely to ever want to put a subprincipal into a group
+        return succeed([])
 
 
     @inlineCallbacks
@@ -437,7 +462,7 @@
         @return: True if principal is a proxy (of the correct type) of our parent
         @rtype: C{boolean}
         """
-        readWrite = self.isProxyType(True) # is read-write
+        readWrite = self.isProxyType(True)  # is read-write
         if principal and self.parent in (yield principal.proxyFor(readWrite)):
             returnValue(True)
         returnValue(False)
@@ -630,7 +655,7 @@
 
             overdue = yield self._memcacher.checkDeletionTimer(principalUID)
 
-            if overdue == False:
+            if overdue is False:
                 # Do nothing
                 returnValue(None)
 
@@ -855,9 +880,9 @@
         )
         if alreadyDone is None:
             for (groupname, member) in (
-                    (yield self._db_all_values_for_sql(
-                        "select GROUPNAME, MEMBER from GROUPS"))
-                ):
+                (yield self._db_all_values_for_sql(
+                    "select GROUPNAME, MEMBER from GROUPS"))
+            ):
                 grouplist = groupname.split("#")
                 grouplist[0] = normalizeUUID(grouplist[0])
                 newGroupName = "#".join(grouplist)

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -71,10 +71,11 @@
             log.debug("No directory record with GUID %r" % (name,))
             returnValue(None)
 
-        if not getattr(record, self.enabledAttribute):
-            log.debug("Directory record %r is not enabled for %s" % (
-                record, self.homeResourceTypeName))
-            returnValue(None)
+        # MOVE2WHO
+        # if not getattr(record, self.enabledAttribute):
+        #     log.debug("Directory record %r is not enabled for %s" % (
+        #         record, self.homeResourceTypeName))
+        #     returnValue(None)
 
         assert len(name) > 4, "Directory record has an invalid GUID: %r" % (
             name,)
@@ -94,7 +95,7 @@
         if name == "":
             returnValue((self, ()))
 
-        record = self.directory.recordWithUID(name)
+        record = yield self.directory.recordWithUID(name)
         if record:
             child = yield self.homeResourceForRecord(record, request)
             returnValue((child, segments[1:]))
@@ -149,7 +150,7 @@
         if name == "":
             returnValue((self, segments[1:]))
 
-        record = self.directory.recordWithShortName(self.recordType, name)
+        record = yield self.directory.recordWithShortName(self.recordType, name)
         if record is None:
             returnValue(
                 (NotFoundResource(principalCollections=self._parent.principalCollections()), [])

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html	2014-03-05 18:24:32 UTC (rev 12819)
@@ -11,10 +11,7 @@
 GUID: <t:slot name="principalGUID"/>
 Record type: <t:slot name="recordType"/>
 Short names: <t:slot name="shortNames"/>
-Security Identities: <t:slot name="securityIDs"/>
 Full name: <t:slot name="fullName"/>
-First name: <t:slot name="firstName"/>
-Last name: <t:slot name="lastName"/>
 Email addresses:
 <t:slot name="emailAddresses" />Principal UID: <t:slot name="principalUID"/>
 Principal URL: <t:slot name="principalURL"/>

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -239,7 +239,7 @@
                 else:
                     record = self.recordWithShortName(parts[2], parts[3])
 
-        return record if record and record.enabledForCalendaring else None
+        return record if record and record.hasCalendars else None
 
 
     def recordWithCachedGroupsAlias(self, recordType, alias):
@@ -531,7 +531,7 @@
         )
         for record in resources:
             guid = record.guid
-            if record.enabledForCalendaring:
+            if record.hasCalendars:
                 assignments.append(("%s#calendar-proxy-write" % (guid,),
                                    record.externalProxies()))
                 assignments.append(("%s#calendar-proxy-read" % (guid,),
@@ -937,7 +937,8 @@
 
             self.log.info("Retrieving list of all proxies")
             # This is always a set of guids:
-            delegatedGUIDs = set((yield self.proxyDB.getAllMembers()))
+            # MOVE2WHO
+            delegatedGUIDs = set() # set((yield self.proxyDB.getAllMembers()))
             self.log.info("There are %d proxies" % (len(delegatedGUIDs),))
             self.log.info("Retrieving group hierarchy from directory")
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -28,6 +28,7 @@
     "DirectoryCalendarPrincipalResource",
 ]
 
+import uuid
 from urllib import unquote
 from urlparse import urlparse
 
@@ -76,7 +77,7 @@
 
 class PermissionsMixIn (ReadOnlyResourceMixIn):
     def defaultAccessControlList(self):
-        return authReadACL
+        return succeed(authReadACL)
 
 
     @inlineCallbacks
@@ -94,7 +95,7 @@
         else:
             # ...otherwise permissions are fixed, and are not subject to
             # inheritance rules, etc.
-            returnValue(self.defaultAccessControlList())
+            returnValue((yield self.defaultAccessControlList()))
 
 
 
@@ -150,18 +151,21 @@
         CalendarPrincipalCollectionResource.__init__(self, url)
         DAVResourceWithChildrenMixin.__init__(self)
 
-        self.directory = IDirectoryService(directory)
+        # MOVE2WHO
+        # self.directory = IDirectoryService(directory)
+        self.directory = directory
 
 
     def __repr__(self):
         return "<%s: %s %s>" % (self.__class__.__name__, self.directory, self._url)
 
 
+    @inlineCallbacks
     def locateChild(self, req, segments):
-        child = self.getChild(segments[0])
+        child = (yield self.getChild(segments[0]))
         if child is not None:
-            return (child, segments[1:])
-        return (NotFoundResource(principalCollections=self.principalCollections()), ())
+            returnValue((child, segments[1:]))
+        returnValue((NotFoundResource(principalCollections=self.principalCollections()), ()))
 
 
     def deadProperties(self):
@@ -174,12 +178,14 @@
         return succeed(None)
 
 
+    @inlineCallbacks
     def principalForShortName(self, recordType, name):
-        return self.principalForRecord(self.directory.recordWithShortName(recordType, name))
+        record = (yield self.directory.recordWithShortName(recordType, name))
+        returnValue((yield self.principalForRecord(record)))
 
 
     def principalForUser(self, user):
-        return self.principalForShortName(DirectoryService.recordType_users, user)
+        return self.principalForShortName(self.directory.recordType.lookupByName("user"), user)
 
 
     def principalForAuthID(self, user):
@@ -207,7 +213,7 @@
 
     def principalForRecord(self, record):
         if record is None or not record.enabled:
-            return None
+            return succeed(None)
         return self.principalForUID(record.uid)
 
     ##
@@ -281,16 +287,21 @@
         #
         # Create children
         #
-        for recordType in self.directory.recordTypes():
-            self.putChild(recordType, DirectoryPrincipalTypeProvisioningResource(self, recordType))
+        # MOVE2WHO - hack: appending "s" -- need mapping
+        for name, recordType in [(r.name + "s", r) for r in self.directory.recordTypes()]:
+            self.putChild(name, DirectoryPrincipalTypeProvisioningResource(self,
+                name, recordType))
 
         self.putChild(uidsResourceName, DirectoryPrincipalUIDProvisioningResource(self))
 
 
+    @inlineCallbacks
     def principalForUID(self, uid):
-        return self.getChild(uidsResourceName).getChild(uid)
+        child = (yield self.getChild(uidsResourceName))
+        returnValue((yield child.getChild(uid)))
 
 
+    @inlineCallbacks
     def _principalForURI(self, uri):
         scheme, netloc, path, _ignore_params, _ignore_query, _ignore_fragment = urlparse(uri)
 
@@ -312,56 +323,62 @@
 
             if (host != config.ServerHostName and
                 host not in config.Scheduling.Options.PrincipalHostAliases):
-                return None
+                returnValue(None)
 
             if port != {
                 "http" : config.HTTPPort,
                 "https": config.SSLPort,
             }[scheme]:
-                return None
+                returnValue(None)
 
         elif scheme == "urn":
             if path.startswith("uuid:"):
-                return self.principalForUID(path[5:])
+                returnValue((yield self.principalForUID(path[5:])))
             else:
-                return None
+                returnValue(None)
         else:
-            return None
+            returnValue(None)
 
         if not path.startswith(self._url):
-            return None
+            returnValue(None)
 
         path = path[len(self._url) - 1:]
 
         segments = [unquote(s) for s in path.rstrip("/").split("/")]
         if segments[0] == "" and len(segments) == 3:
-            typeResource = self.getChild(segments[1])
+            typeResource = yield self.getChild(segments[1])
             if typeResource is not None:
-                principalResource = typeResource.getChild(segments[2])
+                principalResource = yield typeResource.getChild(segments[2])
                 if principalResource:
-                    return principalResource
+                    returnValue(principalResource)
 
-        return None
+        returnValue(None)
 
 
+    @inlineCallbacks
     def principalForCalendarUserAddress(self, address):
         # First see if the address is a principal URI
-        principal = self._principalForURI(address)
+        principal = yield self._principalForURI(address)
         if principal:
-            if isinstance(principal, DirectoryCalendarPrincipalResource) and principal.record.enabledForCalendaring:
-                return principal
+            if (
+                isinstance(principal, DirectoryCalendarPrincipalResource) and
+                principal.record.hasCalendars
+            ):
+                returnValue(principal)
         else:
             # Next try looking it up in the directory
-            record = self.directory.recordWithCalendarUserAddress(address)
-            if record is not None and record.enabled and record.enabledForCalendaring:
-                return self.principalForRecord(record)
+            record = yield self.directory.recordWithCalendarUserAddress(address)
+            if record is not None and record.hasCalendars:
+                returnValue((yield self.principalForRecord(record)))
 
         log.debug("No principal for calendar user address: %r" % (address,))
-        return None
+        returnValue(None)
 
 
+    @inlineCallbacks
     def principalForRecord(self, record):
-        return self.getChild(uidsResourceName).principalForRecord(record)
+        child = (yield self.getChild(uidsResourceName))
+        returnValue((yield child.principalForRecord(record)))
 
 
     ##
@@ -375,13 +392,14 @@
 
     def getChild(self, name):
         if name == "":
-            return self
+            return succeed(self)
         else:
-            return self.putChildren.get(name, None)
+            return succeed(self.putChildren.get(name, None))
 
 
     def listChildren(self):
-        return self.directory.recordTypes()
+        # MOVE2WHO hack
+        return [r.name + "s" for r in self.directory.recordTypes()]
 
 
     ##
@@ -421,14 +439,14 @@
     Collection resource which provisions directory principals of a
     specific type as its children, indexed by short name.
     """
-    def __init__(self, parent, recordType):
+    def __init__(self, parent, name, recordType):
         """
         @param parent: the parent L{DirectoryPrincipalProvisioningResource}.
         @param recordType: the directory record type to provision.
         """
         DirectoryProvisioningResource.__init__(
             self,
-            joinURL(parent.principalCollectionURL(), recordType) + "/",
+            joinURL(parent.principalCollectionURL(), name) + "/",
             parent.directory
         )
 
@@ -459,7 +477,7 @@
 
     def getChild(self, name):
         if name == "":
-            return self
+            return succeed(self)
         else:
             return self.principalForShortName(self.recordType, name)
 
@@ -517,16 +535,18 @@
 
 
     def principalForRecord(self, record):
-        if record is None or not record.enabled:
-            return None
+        # MOVE2WHO
+        if record is None: #  or not record.enabled:
+            return succeed(None)
 
-        if record.enabledForCalendaring or record.enabledForAddressBooks:
+        # MOVE2WHO
+        if record.hasCalendars or record.hasContacts:
             # XXX these are different features and one should not automatically
             # imply the other...
             principal = DirectoryCalendarPrincipalResource(self, record)
         else:
             principal = DirectoryPrincipalResource(self, record)
-        return principal
+        return succeed(principal)
 
     ##
     # Static
@@ -538,9 +558,10 @@
         raise HTTPError(responsecode.NOT_FOUND)
 
 
+    @inlineCallbacks
     def getChild(self, name):
         if name == "":
-            return self
+            returnValue(self)
 
         if "#" in name:
             # This UID belongs to a sub-principal
@@ -549,16 +570,16 @@
             primaryUID = name
             subType = None
 
-        record = self.directory.recordWithUID(primaryUID)
-        primaryPrincipal = self.principalForRecord(record)
+        record = (yield self.directory.recordWithUID(primaryUID))
+        primaryPrincipal = (yield self.principalForRecord(record))
         if primaryPrincipal is None:
             log.info("No principal found for UID: %s" % (name,))
-            return None
+            returnValue(None)
 
         if subType is None:
-            return primaryPrincipal
+            returnValue(primaryPrincipal)
         else:
-            return primaryPrincipal.getChild(subType)
+            returnValue((yield primaryPrincipal.getChild(subType)))
 
 
     def listChildren(self):
@@ -610,17 +631,31 @@
         Top-level renderer in the template.
         """
         record = self.resource.record
+        try:
+            if isinstance(record.guid, uuid.UUID):
+                guid = str(record.guid).upper()
+            else:
+                guid = record.guid
+        except AttributeError:
+            guid = ""
+        try:
+            emailAddresses = record.emailAddresses
+        except AttributeError:
+            emailAddresses = []
         return tag.fillSlots(
             directoryGUID=str(record.service.guid),
             realm=str(record.service.realmName),
-            principalGUID=str(record.guid),
-            recordType=str(record.recordType),
+            principalGUID=guid,
+            recordType=record.recordType.name + "s",  # MOVE2WHO need mapping
             shortNames=",".join(record.shortNames),
-            securityIDs=",".join(record.authIDs),
-            fullName=str(record.fullName),
-            firstName=str(record.firstName),
-            lastName=str(record.lastName),
-            emailAddresses=formatList(record.emailAddresses),
+            # MOVE2WHO: need this?
+            # securityIDs=",".join(record.authIDs),
+            fullName=str(record.displayName),
+            # MOVE2WHO: need this?
+            # firstName=str(record.firstName),
+            # MOVE2WHO: need this?
+            # lastName=str(record.lastName),
+            emailAddresses=formatList(emailAddresses),
             principalUID=str(self.resource.principalUID()),
             principalURL=formatLink(self.resource.principalURL()),
             alternateURIs=formatLinks(self.resource.alternateURIs()),
@@ -697,7 +732,7 @@
         """
         resource = self.resource
         record = resource.record
-        if record.enabledForCalendaring:
+        if record.hasCalendars:
             return tag.fillSlots(
                 calendarUserAddresses=formatLinks(
                     sorted(resource.calendarUserAddresses())
@@ -715,7 +750,7 @@
         """
         resource = self.resource
         record = resource.record
-        if record.enabledForAddressBooks:
+        if record.hasContacts:
             return tag.fillSlots(
                 addressBookHomes=formatLinks(resource.addressBookHomeURLs())
             )
@@ -750,7 +785,8 @@
             (calendarserver_namespace, "first-name"),
             (calendarserver_namespace, "last-name"),
             (calendarserver_namespace, "email-address-set"),
-            davxml.ResourceID.qname(),
+            # MOVE2WHO
+            # davxml.ResourceID.qname(),
         )
 
     cacheNotifierFactory = DisabledCacheNotifier
@@ -778,8 +814,9 @@
         url = joinURL(parent.principalCollectionURL(), self.principalUID()) + slash
         self._url = url
 
+        # MOVE2WHO - hack: just adding an "s" using recordType.name (need a mapping)
         self._alternate_urls = tuple([
-            joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames
+            joinURL(parent.parent.principalCollectionURL(), record.recordType.name+"s", shortName) + slash for shortName in record.shortNames
         ])
 
 
@@ -811,24 +848,27 @@
 
         namespace, name = qname
 
-        if qname == davxml.ResourceID.qname():
-            returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
-        elif namespace == calendarserver_namespace:
-            if name == "first-name":
-                firstName = self.record.firstName
-                if firstName is not None:
-                    returnValue(customxml.FirstNameProperty(firstName))
-                else:
-                    returnValue(None)
+        # MOVE2WHO -- does principal need ResourceID ?
+        # if qname == davxml.ResourceID.qname():
+        #     returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
+        if namespace == calendarserver_namespace:
 
-            elif name == "last-name":
-                lastName = self.record.lastName
-                if lastName is not None:
-                    returnValue(customxml.LastNameProperty(lastName))
-                else:
-                    returnValue(None)
+            # MOVE2WHO
+            # if name == "first-name":
+            #     firstName = self.record.firstName
+            #     if firstName is not None:
+            #         returnValue(customxml.FirstNameProperty(firstName))
+            #     else:
+            #         returnValue(None)
 
-            elif name == "email-address-set":
+            # elif name == "last-name":
+            #     lastName = self.record.lastName
+            #     if lastName is not None:
+            #         returnValue(customxml.LastNameProperty(lastName))
+            #     else:
+            #         returnValue(None)
+
+            if name == "email-address-set":
                 returnValue(customxml.EmailAddressSet(
                     *[customxml.EmailAddressProperty(addr) for addr in sorted(self.record.emailAddresses)]
                 ))
@@ -867,7 +907,7 @@
 
 
     def displayName(self):
-        return self.record.displayName()
+        return self.record.displayName
 
     ##
     # ACL
@@ -939,51 +979,40 @@
 
 
     @inlineCallbacks
-    def proxyFor(self, read_write, resolve_memberships=True):
+    def proxyFor(self, readWrite):
+        """
+        Returns the set of principals currently delegating to this principal
+        with the access indicated by the readWrite argument.  If readWrite is
+        True, then write-access delegators are returned, otherwise the read-
+        only-access delegators are returned.
 
+        @param readWrite: Whether to look up read-write delegators, or
+            read-only delegators
+        @type readWrite: C{bool}
+
+        @return: A Deferred firing with a set of principals
+        """
         proxyFors = set()
 
-        if resolve_memberships:
-            cache = getattr(self.record.service, "groupMembershipCache", None)
-            if cache:
-                log.debug("proxyFor is using groupMembershipCache")
-                guids = (yield self.record.cachedGroups())
-                memberships = set()
-                for guid in guids:
-                    principal = self.parent.principalForUID(guid)
-                    if principal:
-                        memberships.add(principal)
-            else:
-                memberships = self._getRelatives("groups", infinity=True)
-
-            for membership in memberships:
-                results = (yield membership.proxyFor(read_write, False))
-                proxyFors.update(results)
-
         if config.EnableProxyPrincipals:
-            # Get proxy group UIDs and map to principal resources
-            proxies = []
-            memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
-            for uid in memberships:
-                subprincipal = self.parent.principalForUID(uid)
-                if subprincipal:
-                    if subprincipal.isProxyType(read_write):
-                        proxies.append(subprincipal.parent)
-                else:
-                    yield self._calendar_user_proxy_index().removeGroup(uid)
+            childName = "calendar-proxy-{rw}-for".format(
+                rw=("write" if readWrite else "read")
+            )
+            proxyForGroup = yield self.getChild(childName)
+            if proxyForGroup:
+                proxyFors = yield proxyForGroup.groupMembers()
 
-            proxyFors.update(proxies)
+                uids = set()
+                for principal in tuple(proxyFors):
+                    if principal.principalUID() in uids:
+                        proxyFors.remove(principal)
+                    else:
+                        uids.add(principal.principalUID())
 
-        uids = set()
-        for principal in tuple(proxyFors):
-            if principal.principalUID() in uids:
-                proxyFors.remove(principal)
-            else:
-                uids.add(principal.principalUID())
-
         returnValue(proxyFors)
 
 
+    @inlineCallbacks
     def _getRelatives(self, method, record=None, relatives=None, records=None, proxy=None, infinity=False):
         if record is None:
             record = self.record
@@ -994,61 +1023,62 @@
 
         if record not in records:
             records.add(record)
-            for relative in getattr(record, method)():
+            for relative in (yield getattr(record, method)()):
                 if relative not in records:
-                    found = self.parent.principalForRecord(relative)
+                    found = (yield self.parent.principalForRecord(relative))
                     if found is None:
                         log.error("No principal found for directory record: %r" % (relative,))
                     else:
                         if proxy:
                             if proxy == "read-write":
-                                found = found.getChild("calendar-proxy-write")
+                                found = (yield found.getChild("calendar-proxy-write"))
                             else:
-                                found = found.getChild("calendar-proxy-read")
+                                found = (yield found.getChild("calendar-proxy-read"))
                         relatives.add(found)
 
                     if infinity:
-                        self._getRelatives(method, relative, relatives, records,
+                        yield self._getRelatives(method, relative, relatives, records,
                             infinity=infinity)
 
-        return relatives
+        returnValue(relatives)
 
 
     def groupMembers(self):
-        return succeed(self._getRelatives("members"))
+        return self._getRelatives("members")
 
 
     def expandedGroupMembers(self):
-        return succeed(self._getRelatives("members", infinity=True))
+        return self._getRelatives("members", infinity=True)
 
 
     @inlineCallbacks
     def groupMemberships(self, infinity=False):
 
-        cache = getattr(self.record.service, "groupMembershipCache", None)
-        if cache:
-            log.debug("groupMemberships is using groupMembershipCache")
-            guids = (yield self.record.cachedGroups())
-            groups = set()
-            for guid in guids:
-                principal = self.parent.principalForUID(guid)
-                if principal:
-                    groups.add(principal)
-        else:
-            groups = self._getRelatives("groups", infinity=infinity)
+        # cache = getattr(self.record.service, "groupMembershipCache", None)
+        # if cache:
+        #     log.debug("groupMemberships is using groupMembershipCache")
+        #     guids = (yield self.record.cachedGroups())
+        #     groups = set()
+        #     for guid in guids:
+        #         principal = yield self.parent.principalForUID(guid)
+        #         if principal:
+        #             groups.add(principal)
+        # else:
+        groups = yield self._getRelatives("groups", infinity=infinity)
 
-        if config.EnableProxyPrincipals:
-            # Get proxy group UIDs and map to principal resources
-            proxies = []
-            memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
-            for uid in memberships:
-                subprincipal = self.parent.principalForUID(uid)
-                if subprincipal:
-                    proxies.append(subprincipal)
-                else:
-                    yield self._calendar_user_proxy_index().removeGroup(uid)
+        # MOVE2WHO
+        # if config.EnableProxyPrincipals:
+        #     # Get proxy group UIDs and map to principal resources
+        #     proxies = []
+        #     memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
+        #     for uid in memberships:
+        #         subprincipal = yield self.parent.principalForUID(uid)
+        #         if subprincipal:
+        #             proxies.append(subprincipal)
+        #         else:
+        #             yield self._calendar_user_proxy_index().removeGroup(uid)
 
-            groups.update(proxies)
+        #     groups.update(proxies)
 
         returnValue(groups)
 
@@ -1099,7 +1129,9 @@
 
 
     def getAutoSchedule(self):
-        return self.record.autoSchedule
+        # MOVE2WHO
+        return True
+        # return self.record.autoSchedule
 
 
     def canAutoSchedule(self, organizer=None):
@@ -1187,18 +1219,19 @@
         raise HTTPError(responsecode.NOT_FOUND)
 
 
+    @inlineCallbacks
     def locateChild(self, req, segments):
-        child = self.getChild(segments[0])
+        child = (yield self.getChild(segments[0]))
         if child is not None:
-            return (child, segments[1:])
-        return (None, ())
+            returnValue((child, segments[1:]))
+        returnValue((None, ()))
 
 
     def getChild(self, name):
         if name == "":
-            return self
+            return succeed(self)
 
-        return None
+        return succeed(None)
 
 
     def listChildren(self):
@@ -1221,7 +1254,7 @@
 
 
     def addressBooksEnabled(self):
-        return config.EnableCardDAV and self.record.enabledForAddressBooks
+        return config.EnableCardDAV and self.record.hasContacts
 
 
     @inlineCallbacks
@@ -1288,7 +1321,7 @@
 
 
     def calendarHomeURLs(self):
-        if self.record.enabledForCalendaring:
+        if self.record.hasCalendars:
             homeURL = self._homeChildURL(None)
         else:
             homeURL = ""
@@ -1318,7 +1351,7 @@
 
 
     def addressBookHomeURLs(self):
-        if self.record.enabledForAddressBooks:
+        if self.record.hasContacts:
             homeURL = self._addressBookHomeChildURL(None)
         else:
             homeURL = ""
@@ -1391,22 +1424,27 @@
 
     def getChild(self, name):
         if name == "":
-            return self
+            return succeed(self)
 
-        if config.EnableProxyPrincipals and name in ("calendar-proxy-read",
-                                                     "calendar-proxy-write"):
+        if config.EnableProxyPrincipals and name in (
+            "calendar-proxy-read", "calendar-proxy-write",
+            "calendar-proxy-read-for", "calendar-proxy-write-for",
+            ):
             # name is required to be str
             from twistedcaldav.directory.calendaruserproxy import (
                 CalendarUserProxyPrincipalResource
             )
-            return CalendarUserProxyPrincipalResource(self, str(name))
+            return succeed(CalendarUserProxyPrincipalResource(self, str(name)))
         else:
-            return None
+            return succeed(None)
 
 
     def listChildren(self):
         if config.EnableProxyPrincipals:
-            return ("calendar-proxy-read", "calendar-proxy-write")
+            return (
+                "calendar-proxy-read", "calendar-proxy-write",
+                "calendar-proxy-read-for", "calendar-proxy-write-for",
+            )
         else:
             return ()
 
@@ -1442,7 +1480,7 @@
 
     def describe(principal):
         if hasattr(principal, "record"):
-            return " - %s" % (principal.record.fullName,)
+            return " - %s" % (principal.record.displayName,)
         else:
             return ""
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -112,16 +112,18 @@
             # DAV:Read for all authenticated principals (does not include anonymous)
             accessPrincipal = davxml.Authenticated()
 
-        return davxml.ACL(
-            davxml.ACE(
-                davxml.Principal(accessPrincipal),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
-                                ),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-           ),
+        return succeed(
+            davxml.ACL(
+                davxml.ACE(
+                    davxml.Principal(accessPrincipal),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
+                                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+               ),
+            )
         )
 
 
@@ -160,7 +162,7 @@
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
+        return self.defaultAccessControlList()
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -299,7 +299,7 @@
         records = (yield dir.recordsMatchingTokens(tokens, context=context))
 
         for record in records:
-            resource = principalCollection.principalForRecord(record)
+            resource = yield principalCollection.principalForRecord(record)
             if resource:
                 matchingResources.append(resource)
 
@@ -420,9 +420,9 @@
                 f.trap(HTTPError)
                 code = f.value.response.code
                 if code == responsecode.NOT_FOUND:
-                    log.error("Property %s was returned by listProperties() "
-                              "but does not exist for resource %s."
-                              % (name, self.resource))
+                    log.error("Property {p} was returned by listProperties() "
+                              "but does not exist for resource {r}.",
+                              p=name, r=self.resource)
                     return (name, None)
                 if code == responsecode.UNAUTHORIZED:
                     return (name, accessDeniedValue)
@@ -721,7 +721,8 @@
 
             elif name == "record-type":
                 if hasattr(self, "record"):
-                    returnValue(customxml.RecordType(self.record.recordType))
+                    # MOVE2WHO -- need mapping
+                    returnValue(customxml.RecordType(self.record.recordType.name + "s"))
                 else:
                     raise HTTPError(StatusResponse(
                         responsecode.NOT_FOUND,
@@ -848,7 +849,7 @@
     ):
         # Permissions here are fixed, and are not subject to
         # inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
+        return self.defaultAccessControlList()
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -102,7 +102,7 @@
                     davxml.Protected(),
                 ),
             )
-        return davxml.ACL(*aces)
+        return succeed(davxml.ACL(*aces))
 
 
     def resourceType(self):
@@ -243,7 +243,7 @@
         # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long)
 
         # Now lookup the principal details for the targeted user
-        principal = self.parent.principalForRecord()
+        principal = (yield self.parent.principalForRecord())
 
         # Pick the first mailto cu address or the first other type
         cuaddr = None

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -34,6 +34,7 @@
 import itertools
 import uuid
 
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twext.python.log import Logger
 from txweb2.stream import IStream
 from txweb2.dav.util import allDataFromStream
@@ -3239,6 +3240,7 @@
                         self.removeProperty(attachment)
 
 
+    @inlineCallbacks
     def normalizeCalendarUserAddresses(self, lookupFunction, principalFunction,
         toUUID=True):
         """
@@ -3259,7 +3261,7 @@
                 # Check that we can lookup this calendar user address - if not
                 # we cannot do anything with it
                 cuaddr = normalizeCUAddr(prop.value())
-                name, guid, cuaddrs = lookupFunction(cuaddr, principalFunction, config)
+                name, guid, cuaddrs = yield lookupFunction(cuaddr, principalFunction, config)
                 if guid is None:
                     continue
 
@@ -3275,7 +3277,9 @@
 
                 if toUUID:
                     # Always re-write value to urn:uuid
-                    prop.setValue("urn:uuid:%s" % (guid,))
+                    if isinstance(guid, uuid.UUID):
+                        guid = unicode(guid).upper()
+                    prop.setValue("urn:uuid:{guid}".format(guid=guid))
 
                 # If it is already a non-UUID address leave it be
                 elif cuaddr.startswith("urn:uuid:"):
@@ -3353,7 +3357,7 @@
 
             # For VPOLL also do immediate children
             if component.name() == "VPOLL":
-                component.normalizeCalendarUserAddresses(lookupFunction, principalFunction, toUUID)
+                yield component.normalizeCalendarUserAddresses(lookupFunction, principalFunction, toUUID)
 
 
     def allPerUserUIDs(self):
@@ -3563,15 +3567,16 @@
 # Utilities
 # #
 
+ at inlineCallbacks
 def normalizeCUAddress(cuaddr, lookupFunction, principalFunction, toUUID=True):
     # Check that we can lookup this calendar user address - if not
     # we cannot do anything with it
-    _ignore_name, guid, cuaddrs = lookupFunction(normalizeCUAddr(cuaddr), principalFunction, config)
+    _ignore_name, guid, cuaddrs = (yield lookupFunction(normalizeCUAddr(cuaddr), principalFunction, config))
 
     if toUUID:
         # Always re-write value to urn:uuid
         if guid:
-            return "urn:uuid:%s" % (guid,)
+            returnValue("urn:uuid:%s" % (guid,))
 
     # If it is already a non-UUID address leave it be
     elif cuaddr.startswith("urn:uuid:"):
@@ -3610,9 +3615,9 @@
 
         # Make the change
         if newaddr:
-            return newaddr
+            returnValue(newaddr)
 
-    return cuaddr
+    returnValue(cuaddr)
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -863,7 +863,8 @@
                 home = self._newStoreObject.parentCollection().ownerHome()
             else:
                 home = self._newStoreObject.ownerHome()
-            returnValue(element.HRef(self.principalForUID(home.uid()).principalURL()))
+            principal = (yield self.principalForUID(home.uid()))
+            returnValue(element.HRef(principal.principalURL()))
         else:
             parent = (yield self.locateParent(request, request.urlForResource(self)))
         if parent and isinstance(parent, CalDAVResource):
@@ -883,7 +884,7 @@
                 home = self._newStoreObject.parentCollection().ownerHome()
             else:
                 home = self._newStoreObject.ownerHome()
-            returnValue(self.principalForUID(home.uid()))
+            returnValue((yield self.principalForUID(home.uid())))
         else:
             parent = (yield self.locateParent(request, request.urlForResource(self)))
         if parent and isinstance(parent, CalDAVResource):
@@ -933,8 +934,8 @@
             return None
 
         if 'record' in dir(self):
-            if self.record.fullName:
-                return self.record.fullName
+            if self.record.fullNames:
+                return self.record.fullNames[0]
             elif self.record.shortNames:
                 return self.record.shortNames[0]
             else:
@@ -1063,6 +1064,7 @@
         returnValue(PerUserDataFilter(accessUID).filter(caldata))
 
 
+    # MOVE2WHO returns Deferred
     def iCalendarAddressDoNormalization(self, ical):
         """
         Normalize calendar user addresses in the supplied iCalendar object into their
@@ -1071,24 +1073,26 @@
         @param ical: calendar object to normalize.
         @type ical: L{Component}
         """
-        ical.normalizeCalendarUserAddresses(normalizationLookup,
+        return ical.normalizeCalendarUserAddresses(normalizationLookup,
             self.principalForCalendarUserAddress)
 
 
+    @inlineCallbacks
     def principalForCalendarUserAddress(self, address):
         for principalCollection in self.principalCollections():
-            principal = principalCollection.principalForCalendarUserAddress(address)
+            principal = (yield principalCollection.principalForCalendarUserAddress(address))
             if principal is not None:
-                return principal
-        return None
+                returnValue(principal)
+        returnValue(None)
 
 
+    @inlineCallbacks
     def principalForUID(self, principalUID):
         for principalCollection in self.principalCollections():
-            principal = principalCollection.principalForUID(principalUID)
+            principal = (yield principalCollection.principalForUID(principalUID))
             if principal is not None:
-                return principal
-        return None
+                returnValue(principal)
+        returnValue(None)
 
 
     @inlineCallbacks
@@ -1874,7 +1878,7 @@
 
             elif name == "auto-schedule-mode" and self.calendarsEnabled():
                 autoScheduleMode = self.getAutoScheduleMode()
-                returnValue(customxml.AutoScheduleMode(autoScheduleMode if autoScheduleMode else "default"))
+                returnValue(customxml.AutoScheduleMode(autoScheduleMode.description if autoScheduleMode else "default"))
 
         elif namespace == carddav_namespace and self.addressBooksEnabled():
             if name == "addressbook-home-set":
@@ -2302,20 +2306,23 @@
     # ACL
     ##
 
+    @inlineCallbacks
     def owner(self, request):
-        return succeed(element.HRef(self.principalForRecord().principalURL()))
+        principal = yield self.principalForRecord()
+        returnValue(element.HRef(principal.principalURL()))
 
 
     def ownerPrincipal(self, request):
-        return succeed(self.principalForRecord())
+        return self.principalForRecord()
 
 
     def resourceOwnerPrincipal(self, request):
-        return succeed(self.principalForRecord())
+        return self.principalForRecord()
 
 
+    @inlineCallbacks
     def defaultAccessControlList(self):
-        myPrincipal = self.principalForRecord()
+        myPrincipal = yield self.principalForRecord()
 
         # Server may be read only
         if config.EnableReadOnlyServer:
@@ -2342,12 +2349,12 @@
         # Give all access to config.AdminPrincipals
         aces += config.AdminACEs
 
-        return element.ACL(*aces)
+        returnValue(element.ACL(*aces))
 
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
+        return self.defaultAccessControlList()
 
 
     def principalCollections(self):
@@ -2555,9 +2562,10 @@
         return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
 
 
+    @inlineCallbacks
     def _otherPrincipalHomeURL(self, otherUID):
-        ownerPrincipal = self.principalForUID(otherUID)
-        return ownerPrincipal.calendarHomeURLs()[0]
+        ownerPrincipal = (yield self.principalForUID(otherUID))
+        returnValue(ownerPrincipal.calendarHomeURLs()[0])
 
 
     @inlineCallbacks
@@ -2584,8 +2592,9 @@
         return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, mode)
 
 
+    @inlineCallbacks
     def defaultAccessControlList(self):
-        myPrincipal = self.principalForRecord()
+        myPrincipal = yield self.principalForRecord()
 
         # Server may be read only
         if config.EnableReadOnlyServer:
@@ -2652,7 +2661,7 @@
                 ),
             )
 
-        return element.ACL(*aces)
+        returnValue(element.ACL(*aces))
 
 
     @inlineCallbacks
@@ -2808,9 +2817,10 @@
         return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
 
 
+    @inlineCallbacks
     def _otherPrincipalHomeURL(self, otherUID):
-        ownerPrincipal = self.principalForUID(otherUID)
-        return ownerPrincipal.addressBookHomeURLs()[0]
+        ownerPrincipal = (yield self.principalForUID(otherUID))
+        returnValue(ownerPrincipal.addressBookHomeURLs()[0])
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -373,12 +373,14 @@
         if config.Scheduling.CalDAV.OldDraftCompatibility:
             privs += (davxml.Privilege(caldavxml.Schedule()),)
 
-        return davxml.ACL(
-            # CalDAV:schedule-deliver for any authenticated user
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(*privs),
-            ),
+        return succeed(
+            davxml.ACL(
+                # CalDAV:schedule-deliver for any authenticated user
+                davxml.ACE(
+                    davxml.Principal(davxml.Authenticated()),
+                    davxml.Grant(*privs),
+                ),
+            )
         )
 
 
@@ -532,9 +534,10 @@
         return succeed(sendSchedulePrivilegeSet)
 
 
+    @inlineCallbacks
     def defaultAccessControlList(self):
         if config.EnableProxyPrincipals:
-            myPrincipal = self.parent.principalForRecord()
+            myPrincipal = yield self.parent.principalForRecord()
 
             privs = (
                 davxml.Privilege(caldavxml.ScheduleSend()),
@@ -542,16 +545,18 @@
             if config.Scheduling.CalDAV.OldDraftCompatibility:
                 privs += (davxml.Privilege(caldavxml.Schedule()),)
 
-            return davxml.ACL(
-                # CalDAV:schedule for associated write proxies
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
-                    davxml.Grant(*privs),
-                    davxml.Protected(),
-                ),
+            returnValue(
+                davxml.ACL(
+                    # CalDAV:schedule for associated write proxies
+                    davxml.ACE(
+                        davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
+                        davxml.Grant(*privs),
+                        davxml.Protected(),
+                    ),
+                )
             )
         else:
-            return super(ScheduleOutboxResource, self).defaultAccessControlList()
+            returnValue(super(ScheduleOutboxResource, self).defaultAccessControlList())
 
 
     def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -792,7 +792,7 @@
         Set shared state and check access control.
         """
         if child._newStoreObject is not None and not child._newStoreObject.owned():
-            ownerHomeURL = self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid())
+            ownerHomeURL = (yield self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid()))
             ownerView = yield child._newStoreObject.ownerView()
             child.setShare(joinURL(ownerHomeURL, ownerView.name()))
             access = yield child._checkAccessControl()
@@ -802,6 +802,7 @@
 
 
     def _otherPrincipalHomeURL(self, otherUID):
+        # Is this only meant to be overridden?
         pass
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1763,11 +1763,12 @@
         return succeed(davPrivilegeSet)
 
 
+    @inlineCallbacks
     def defaultAccessControlList(self):
         """
         Only read privileges allowed for managed attachments.
         """
-        myPrincipal = self.parent.principalForRecord()
+        myPrincipal = yield self.parent.principalForRecord()
 
         read_privs = (
             davxml.Privilege(davxml.Read()),
@@ -1808,12 +1809,12 @@
                 ),
             )
 
-        return davxml.ACL(*aces)
+        returnValue(davxml.ACL(*aces))
 
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
+        return self.defaultAccessControlList()
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -19,6 +19,7 @@
 import itertools
 
 from twisted.trial.unittest import SkipTest
+from twisted.internet.defer import inlineCallbacks, succeed
 
 from twistedcaldav.ical import Component, Property, InvalidICalendarDataError, \
     normalizeCUAddress, normalize_iCalStr
@@ -32,6 +33,8 @@
 from twistedcaldav.dateops import normalizeForExpand
 from pycalendar.value import Value
 
+
+
 class iCalendar (twistedcaldav.test.util.TestCase):
     """
     iCalendar support tests
@@ -7497,6 +7500,7 @@
             self.assertEquals(expected, ical.hasInstancesAfter(cutoff))
 
 
+    @inlineCallbacks
     def test_normalizeCalendarUserAddressesFromUUID(self):
         """
         Ensure mailto is preferred, followed by path form, then http form.
@@ -7520,25 +7524,27 @@
 
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "urn:uuid:foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo", "http://example.com/foo", "/foo")
-                ),
-                "urn:uuid:bar" : (
-                    "Bar",
-                    "bar",
-                    ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
-                ),
-                "urn:uuid:baz" : (
-                    "Baz",
-                    "baz",
-                    ("urn:uuid:baz", "http://example.com/baz")
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "urn:uuid:foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo", "http://example.com/foo", "/foo")
+                    ),
+                    "urn:uuid:bar" : (
+                        "Bar",
+                        "bar",
+                        ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
+                    ),
+                    "urn:uuid:baz" : (
+                        "Baz",
+                        "baz",
+                        ("urn:uuid:baz", "http://example.com/baz")
+                    ),
+                }[cuaddr]
+            )
 
-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
 
         self.assertEquals("mailto:bar at example.com",
             component.getAttendeeProperty(("mailto:bar at example.com",)).value())
@@ -7548,6 +7554,7 @@
             component.getAttendeeProperty(("http://example.com/baz",)).value())
 
 
+    @inlineCallbacks
     def test_normalizeCalendarUserAddressesAndLocationChange(self):
         """
         Ensure http(s) and /path CUA values are tucked away into the property
@@ -7573,25 +7580,27 @@
 
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "/principals/users/foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo",)
-                ),
-                "http://example.com/principals/users/bar" : (
-                    "Bar",
-                    "bar",
-                    ("urn:uuid:bar",)
-                ),
-                "http://example.com/principals/locations/buzz" : (
-                    "{Restricted} Buzz",
-                    "buzz",
-                    ("urn:uuid:buzz",)
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "/principals/users/foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo",)
+                    ),
+                    "http://example.com/principals/users/bar" : (
+                        "Bar",
+                        "bar",
+                        ("urn:uuid:bar",)
+                    ),
+                    "http://example.com/principals/locations/buzz" : (
+                        "{Restricted} Buzz",
+                        "buzz",
+                        ("urn:uuid:buzz",)
+                    ),
+                }[cuaddr]
+            )
 
-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
 
         # Location value changed
         prop = component.mainComponent().getProperty("LOCATION")
@@ -7601,6 +7610,7 @@
         self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
 
 
+    @inlineCallbacks
     def test_normalizeCalendarUserAddressesAndLocationNoChange(self):
         """
         Ensure http(s) and /path CUA values are tucked away into the property
@@ -7626,25 +7636,27 @@
 
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "/principals/users/foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo",)
-                ),
-                "http://example.com/principals/users/bar" : (
-                    "Bar",
-                    "bar",
-                    ("urn:uuid:bar",)
-                ),
-                "http://example.com/principals/locations/buzz" : (
-                    "{Restricted} Buzz",
-                    "buzz",
-                    ("urn:uuid:buzz",)
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "/principals/users/foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo",)
+                    ),
+                    "http://example.com/principals/users/bar" : (
+                        "Bar",
+                        "bar",
+                        ("urn:uuid:bar",)
+                    ),
+                    "http://example.com/principals/locations/buzz" : (
+                        "{Restricted} Buzz",
+                        "buzz",
+                        ("urn:uuid:buzz",)
+                    ),
+                }[cuaddr]
+            )
 
-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
 
         # Location value changed
         prop = component.mainComponent().getProperty("LOCATION")
@@ -7654,6 +7666,7 @@
         self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
 
 
+    @inlineCallbacks
     def test_normalizeCalendarUserAddressesAndLocationNoChangeOtherCUType(self):
         """
         Ensure http(s) and /path CUA values are tucked away into the property
@@ -7679,25 +7692,27 @@
 
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "/principals/users/foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo",)
-                ),
-                "http://example.com/principals/users/bar" : (
-                    "Bar",
-                    "bar",
-                    ("urn:uuid:bar",)
-                ),
-                "http://example.com/principals/locations/buzz" : (
-                    "{Restricted} Buzz",
-                    "buzz",
-                    ("urn:uuid:buzz",)
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "/principals/users/foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo",)
+                    ),
+                    "http://example.com/principals/users/bar" : (
+                        "Bar",
+                        "bar",
+                        ("urn:uuid:bar",)
+                    ),
+                    "http://example.com/principals/locations/buzz" : (
+                        "{Restricted} Buzz",
+                        "buzz",
+                        ("urn:uuid:buzz",)
+                    ),
+                }[cuaddr]
+            )
 
-        component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+        yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
 
         # Location value changed
         prop = component.mainComponent().getProperty("LOCATION")
@@ -8404,6 +8419,7 @@
             self.assertEqual(changed, result_changed)
 
 
+    @inlineCallbacks
     def test_normalizeCUAddressFromUUID(self):
         """
         Ensure mailto is preferred, followed by path form, then http form.
@@ -8418,34 +8434,37 @@
         )
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "urn:uuid:foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo", "http://example.com/foo", "/foo")
-                ),
-                "urn:uuid:bar" : (
-                    "Bar",
-                    "bar",
-                    ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
-                ),
-                "urn:uuid:baz" : (
-                    "Baz",
-                    "baz",
-                    ("urn:uuid:baz", "http://example.com/baz")
-                ),
-                "urn:uuid:buz" : (
-                    "Buz",
-                    "buz",
-                    ("urn:uuid:buz",)
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "urn:uuid:foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo", "http://example.com/foo", "/foo")
+                    ),
+                    "urn:uuid:bar" : (
+                        "Bar",
+                        "bar",
+                        ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
+                    ),
+                    "urn:uuid:baz" : (
+                        "Baz",
+                        "baz",
+                        ("urn:uuid:baz", "http://example.com/baz")
+                    ),
+                    "urn:uuid:buz" : (
+                        "Buz",
+                        "buz",
+                        ("urn:uuid:buz",)
+                    ),
+                }[cuaddr]
+            )
 
         for cuaddr, result in data:
-            new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
+            new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
             self.assertEquals(new_cuaddr, result)
 
 
+    @inlineCallbacks
     def test_normalizeCUAddressToUUID(self):
         """
         Ensure http(s) and /path CUA values are tucked away into the property
@@ -8459,21 +8478,23 @@
 
 
         def lookupFunction(cuaddr, ignored1, ignored2):
-            return {
-                "/principals/users/foo" : (
-                    "Foo",
-                    "foo",
-                    ("urn:uuid:foo",)
-                ),
-                "http://example.com/principals/users/buz" : (
-                    "Buz",
-                    "buz",
-                    ("urn:uuid:buz",)
-                ),
-            }[cuaddr]
+            return succeed(
+                {
+                    "/principals/users/foo" : (
+                        "Foo",
+                        "foo",
+                        ("urn:uuid:foo",)
+                    ),
+                    "http://example.com/principals/users/buz" : (
+                        "Buz",
+                        "buz",
+                        ("urn:uuid:buz",)
+                    ),
+                }[cuaddr]
+            )
 
         for cuaddr, result in data:
-            new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
+            new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
             self.assertEquals(new_cuaddr, result)
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1536,6 +1536,7 @@
         self.assertFalse(changed)
 
 
+    @inlineCallbacks
     def test_normalizeCUAddrs(self):
         """
         Ensure that calendar user addresses (CUAs) are cached so we can
@@ -1577,8 +1578,8 @@
 
         directory = StubDirectory()
         cuaCache = {}
-        normalizeCUAddrs(normalizeEvent, directory, cuaCache)
-        normalizeCUAddrs(normalizeEvent, directory, cuaCache)
+        yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
+        yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
 
         # Ensure we only called principalForCalendarUserAddress 3 times.  It
         # would have been 8 times without the cuaCache.

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -86,15 +86,17 @@
 
 
     def defaultAccessControlList(self):
-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
                 ),
-                davxml.Protected(),
-            ),
+            )
         )
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -146,15 +146,17 @@
 
 
     def defaultAccessControlList(self):
-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
                 ),
-                davxml.Protected(),
-            ),
+            )
         )
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -121,6 +121,7 @@
 
 
 
+ at inlineCallbacks
 def upgradeCalendarCollection(calPath, directory, cuaCache):
     errorOccurred = False
     collectionUpdated = False
@@ -164,7 +165,7 @@
                 continue
 
             try:
-                data, fixed = normalizeCUAddrs(data, directory, cuaCache)
+                data, fixed = (yield normalizeCUAddrs(data, directory, cuaCache))
                 if fixed:
                     log.debug("Normalized CUAddrs in %s" % (resPath,))
                     needsRewrite = True
@@ -207,10 +208,11 @@
         except:
             raise
 
-    return errorOccurred
+    returnValue(errorOccurred)
 
 
 
+ at inlineCallbacks
 def upgradeCalendarHome(homePath, directory, cuaCache):
 
     errorOccurred = False
@@ -229,7 +231,7 @@
                 rmdir(calPath)
                 continue
             log.debug("Upgrading calendar: %s" % (calPath,))
-            if not upgradeCalendarCollection(calPath, directory, cuaCache):
+            if not (yield upgradeCalendarCollection(calPath, directory, cuaCache)):
                 errorOccurred = True
 
             # Change the calendar-free-busy-set xattrs of the inbox to the
@@ -254,7 +256,7 @@
         log.error("Failed to upgrade calendar home %s: %s" % (homePath, e))
         raise
 
-    return errorOccurred
+    returnValue(errorOccurred)
 
 
 
@@ -288,9 +290,10 @@
 
 
     @UpgradeOneHome.responder
+    @inlineCallbacks
     def upgradeOne(self, path):
-        result = upgradeCalendarHome(path, self.directory, self.cuaCache)
-        return dict(succeeded=result)
+        result = yield upgradeCalendarHome(path, self.directory, self.cuaCache)
+        returnValue(dict(succeeded=result))
 
 
 
@@ -543,9 +546,9 @@
                                         # Skip non-directories
                                         continue
 
-                                    if not upgradeCalendarHome(
+                                    if not (yield upgradeCalendarHome(
                                         homePath, directory, cuaCache
-                                    ):
+                                    )):
                                         setError()
 
                                     count += 1
@@ -564,6 +567,7 @@
 
 
 
+ at inlineCallbacks
 def normalizeCUAddrs(data, directory, cuaCache):
     """
     Normalize calendar user addresses to urn:uuid: form.
@@ -583,23 +587,24 @@
     """
     cal = Component.fromString(data)
 
+    @inlineCallbacks
     def lookupFunction(cuaddr, principalFunction, config):
 
         # Return cached results, if any.
         if cuaddr in cuaCache:
-            return cuaCache[cuaddr]
+            returnValue(cuaCache[cuaddr])
 
-        result = normalizationLookup(cuaddr, principalFunction, config)
+        result = yield normalizationLookup(cuaddr, principalFunction, config)
 
         # Cache the result
         cuaCache[cuaddr] = result
-        return result
+        returnValue(result)
 
-    cal.normalizeCalendarUserAddresses(lookupFunction,
+    yield cal.normalizeCalendarUserAddresses(lookupFunction,
         directory.principalForCalendarUserAddress)
 
     newData = str(cal)
-    return newData, not newData == data
+    returnValue(newData, not newData == data)
 
 
 
@@ -997,7 +1002,9 @@
         """
         Execute the step.
         """
-        return self.doUpgrade()
+        return succeed(None)
+        # MOVE2WHO
+        # return self.doUpgrade()
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -23,6 +23,7 @@
 from hashlib import md5, sha1
 
 from twisted.internet import ssl, reactor
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web import client
 from twisted.python import failure
 from twext.python.log import Logger
@@ -495,6 +496,7 @@
 
 
 
+ at inlineCallbacks
 def normalizationLookup(cuaddr, principalFunction, config):
     """
     Lookup function to be passed to ical.normalizeCalendarUserAddresses.
@@ -503,13 +505,13 @@
     principal for the cuaddr.
     """
     try:
-        principal = principalFunction(cuaddr)
+        principal = yield principalFunction(cuaddr)
     except Exception, e:
         log.debug("Lookup of %s failed: %s" % (cuaddr, e))
         principal = None
 
     if principal is None:
-        return (None, None, None)
+        returnValue((None, None, None))
     else:
         rec = principal.record
 
@@ -520,9 +522,9 @@
         # to single-quotes.
         fullName = rec.fullName.replace('"', "'")
 
-        cuas = principal.record.calendarUserAddresses
+        cuas = principal.record.calendarUserAddresses()
 
-        return (fullName, rec.guid, cuas)
+        returnValue((fullName, rec.guid, cuas))
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -85,13 +85,14 @@
             ))
 
 
+    @inlineCallbacks
     def checkOriginator(self):
         """
         Check the validity of the Originator header. Extract the corresponding principal.
         """
 
         # Verify that Originator is a valid calendar user
-        originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
+        originatorPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
         if originatorPrincipal is None:
             # Local requests MUST have a principal.
             log.error("Could not find principal for originator: %s" % (self.originator,))
@@ -122,7 +123,7 @@
         results = []
         for recipient in self.recipients:
             # Get the principal resource for this recipient
-            principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
+            principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
 
             # If no principal we may have a remote recipient but we should check whether
             # the address is one that ought to be on our server and treat that as a missing
@@ -161,7 +162,7 @@
         # Verify that the ORGANIZER's cu address maps to a valid user
         organizer = self.calendar.getOrganizer()
         if organizer:
-            organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
+            organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
             if organizerPrincipal:
                 if organizerPrincipal.calendarsEnabled():
 
@@ -225,6 +226,7 @@
             ))
 
 
+    @inlineCallbacks
     def checkAttendeeAsOriginator(self):
         """
         Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
@@ -232,7 +234,7 @@
         """
 
         # Attendee's Outbox MUST be the request URI
-        attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
+        attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
         if attendeePrincipal:
             if self.doingPOST is not None and attendeePrincipal.uid != self.originator_uid:
                 log.error("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,))
@@ -257,11 +259,11 @@
 
         # Prevent spoofing of ORGANIZER with specific METHODs when local
         if self.isiTIPRequest:
-            self.checkOrganizerAsOriginator()
+            return self.checkOrganizerAsOriginator()
 
         # Prevent spoofing when doing reply-like METHODs
         else:
-            self.checkAttendeeAsOriginator()
+            return self.checkAttendeeAsOriginator()
 
 
     def finalChecks(self):

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -212,12 +212,12 @@
     # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy
 
     # May need organizer principal
-    organizer_record = calresource.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
+    organizer_record = (yield calresource.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
     organizer_uid = organizer_record.uid if organizer_record else ""
 
     # Free busy is per-user
     attendee_uid = calresource.viewerHome().uid()
-    attendee_record = calresource.directoryService().recordWithUID(attendee_uid)
+    attendee_record = yield calresource.directoryService().recordWithUID(attendee_uid)
 
     # Get the timezone property from the collection.
     tz = calresource.getTimezone()
@@ -237,7 +237,7 @@
         authz_record = organizer_record
         if hasattr(calresource._txn, "_authz_uid") and calresource._txn._authz_uid != organizer_uid:
             authz_uid = calresource._txn._authz_uid
-            authz_record = calresource.directoryService().recordWithUID(authz_uid)
+            authz_record = yield calresource.directoryService().recordWithUID(unicode(authz_uid))
 
         # Check if attendee is also the organizer or the delegate doing the request
         if attendee_uid in (organizer_uid, authz_uid):
@@ -335,7 +335,7 @@
                 if excludeuid:
                     # See if we have a UID match
                     if (excludeuid == uid):
-                        test_record = calresource.directoryService().recordWithCalendarUserAddress(test_organizer) if test_organizer else None
+                        test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer)) if test_organizer else None
                         test_uid = test_record.uid if test_record else ""
 
                         # Check that ORGANIZER's match (security requirement)

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -329,7 +329,7 @@
                 toAddr = organizer[7:]
             elif organizer.startswith("urn:uuid:"):
                 guid = organizer[9:]
-                record = self.directory.recordWithGUID(guid)
+                record = yield self.directory.recordWithGUID(guid)
                 if record and record.emailAddresses:
                     toAddr = list(record.emailAddresses)[0]
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -297,7 +297,7 @@
         organizer_scheduling = (yield self.isOrganizerScheduling())
         if organizer_scheduling:
             self.state = "organizer"
-        elif self.isAttendeeScheduling():
+        elif (yield self.isAttendeeScheduling()):
             self.state = "attendee"
         elif self.organizer:
             # There is an ORGANIZER that is not this user but no ATTENDEE property for
@@ -365,7 +365,7 @@
 
         # Get some useful information from the calendar
         yield self.extractCalendarData()
-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
 
         # Originator is the organizer in this case
@@ -447,7 +447,7 @@
             self.calendar = calendar_old
 
         yield self.extractCalendarData()
-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
 
         # Originator is the organizer in this case
@@ -479,7 +479,7 @@
         # Get some useful information from the calendar
         yield self.extractCalendarData()
 
-        self.attendeePrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
+        self.attendeePrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
         self.originator = self.attendee = self.attendeePrincipal.canonicalCalendarUserAddress()
 
         result = (yield self.scheduleWithOrganizer())
@@ -491,7 +491,7 @@
     def extractCalendarData(self):
 
         # Get the originator who is the owner of the calendar resource being modified
-        self.originatorPrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
+        self.originatorPrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
 
         # Pick the canonical CUA:
         self.originator = self.originatorPrincipal.canonicalCalendarUserAddress()
@@ -555,7 +555,7 @@
             returnValue(False)
 
         # Organizer must map to a valid principal
-        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+        self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
         if not self.organizerPrincipal:
             returnValue(False)
@@ -567,21 +567,22 @@
         returnValue(True)
 
 
+    @inlineCallbacks
     def isAttendeeScheduling(self):
 
         # First must have organizer property
         if not self.organizer:
-            return False
+            returnValue(False)
 
         # Check to see whether any attendee is the owner
         for attendee in self.attendees:
-            attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+            attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
             if attendeePrincipal and attendeePrincipal.uid == self.calendar_home.uid():
                 self.attendee = attendee
                 self.attendeePrincipal = attendeePrincipal
-                return True
+                returnValue(True)
 
-        return False
+        returnValue(False)
 
 
     def makeScheduler(self):
@@ -1015,7 +1016,7 @@
             if attendee.parameterValue("SCHEDULE-AGENT", "SERVER").upper() == "CLIENT":
                 cuaddr = attendee.value()
                 if cuaddr not in coerced:
-                    attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
+                    attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
                     attendeeAddress = (yield addressmapping.mapper.getCalendarUser(cuaddr, attendeePrincipal))
                     local_attendee = type(attendeeAddress) in (LocalCalendarUser, OtherServerCalendarUser,)
                     coerced[cuaddr] = local_attendee
@@ -1078,7 +1079,7 @@
 
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue
@@ -1135,7 +1136,7 @@
 
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue
@@ -1195,7 +1196,7 @@
 
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue
@@ -1260,7 +1261,7 @@
 
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
                 attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -228,7 +228,7 @@
             # Loop over at most 3 redirects
             ssl, host, port, path = self.server.details()
             for _ignore in xrange(3):
-                self._prepareRequest(host, port)
+                yield self._prepareRequest(host, port)
                 response = (yield self._processRequest(ssl, host, port, path))
                 if response.code not in (responsecode.MOVED_PERMANENTLY, responsecode.TEMPORARY_REDIRECT,):
                     break
@@ -334,16 +334,18 @@
         returnValue(iostr.getvalue())
 
 
+    @inlineCallbacks
     def _prepareRequest(self, host, port):
         """
         Setup the request for sending. We might need to do this several times
         whilst following redirects.
         """
 
-        component, method = self._prepareData()
-        self._prepareHeaders(host, port, component, method)
+        component, method = (yield self._prepareData())
+        yield self._prepareHeaders(host, port, component, method)
 
 
+    @inlineCallbacks
     def _prepareHeaders(self, host, port, component, method):
         """
         Always generate a new set of headers because the Host may varying during redirects,
@@ -357,7 +359,7 @@
         # The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
         originator = self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee
         if self.server.unNormalizeAddresses:
-            originator = normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
+            originator = yield normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
         self.headers.addRawHeader("Originator", utf8String(originator))
         self.sign_headers.append("Originator")
 
@@ -399,6 +401,7 @@
             self.sign_headers.append("Authorization")
 
 
+    @inlineCallbacks
     def _prepareData(self):
         """
         Prepare data via normalization etc. Only need to do this once even when
@@ -411,7 +414,7 @@
             normalizedCalendar = self.scheduler.calendar.duplicate()
             self.original_organizer = normalizedCalendar.getOrganizer()
             if self.server.unNormalizeAddresses:
-                normalizedCalendar.normalizeCalendarUserAddresses(
+                yield normalizedCalendar.normalizeCalendarUserAddresses(
                     normalizationLookup,
                     self.scheduler.txn.directoryService().recordWithCalendarUserAddress,
                     toUUID=False)
@@ -423,12 +426,12 @@
             component = normalizedCalendar.mainType()
             method = normalizedCalendar.propertyValue("METHOD")
             self.data = str(normalizedCalendar)
-            return component, method
+            returnValue(component, method)
         else:
             cal = Component.fromString(self.data)
             component = cal.mainType()
             method = cal.propertyValue("METHOD")
-            return component, method
+            returnValue(component, method)
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -353,11 +353,13 @@
             davxml.Privilege(caldavxml.ScheduleDeliver()),
         )
 
-        return davxml.ACL(
-            # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
+        return succeed(
+            davxml.ACL(
+                # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(*privs),
+                    davxml.Protected(),
+                ),
+            )
         )

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -186,7 +186,7 @@
         # Normalize recipient addresses
         results = []
         for recipient in recipients:
-            normalized = normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
+            normalized = yield normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
             self.recipientsNormalizationMap[normalized] = recipient
             results.append(normalized)
         recipients = results
@@ -205,7 +205,7 @@
         if not self.checkForFreeBusy():
             # Need to normalize the calendar data and recipient values to keep those in sync,
             # as we might later try to match them
-            self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
+            return self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
 
 
     def checkAuthorization(self):
@@ -226,7 +226,7 @@
         """
 
         # For remote requests we do not allow the originator to be a local user or one within our domain.
-        originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
+        originatorPrincipal = (yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator))
         localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
         if originatorPrincipal or localUser:
             if originatorPrincipal.thisServer():
@@ -367,7 +367,7 @@
         # Verify that the ORGANIZER's cu address does not map to a valid user
         organizer = self.calendar.getOrganizer()
         if organizer:
-            organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
+            organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
             if organizerPrincipal:
                 if organizerPrincipal.thisServer():
                     log.error("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
@@ -408,7 +408,7 @@
         """
 
         # Attendee cannot be local.
-        attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
+        attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
         if attendeePrincipal:
             if attendeePrincipal.thisServer():
                 log.error("Invalid ATTENDEE in calendar data: %s" % (self.calendar,))

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -36,6 +36,7 @@
 from txdav.caldav.datastore.scheduling.work import ScheduleRefreshWork, \
     ScheduleAutoReplyWork
 from txdav.caldav.icalendarstore import ComponentUpdateState, ComponentRemoveState
+from txdav.who.idirectory import AutoScheduleMode
 
 import collections
 import hashlib
@@ -58,6 +59,8 @@
 
 log = Logger()
 
+
+
 class ImplicitProcessorException(Exception):
 
     def __init__(self, msg):
@@ -604,19 +607,28 @@
         @param calendar: the iTIP message to process
         @type calendar: L{Component}
         @param automode: the auto-schedule mode for the recipient
-        @type automode: C{str}
+        @type automode: L{txdav.who.idirectory.AutoScheduleMode}
 
         @return: C{tuple} of C{bool}, C{bool}, C{str} indicating whether changes were made, whether the inbox item
             should be added, and the new PARTSTAT.
         """
-
         # First ignore the none mode
-        if automode == "none":
+        if automode == AutoScheduleMode.none:
             returnValue((False, True, "",))
-        elif not automode or automode == "default":
-            automode = config.Scheduling.Options.AutoSchedule.DefaultMode
+        elif not automode:
+            automode = {
+                "none": AutoScheduleMode.none,
+                "accept-always": AutoScheduleMode.accept,
+                "decline-always": AutoScheduleMode.decline,
+                "accept-if-free": AutoScheduleMode.acceptIfFree,
+                "decline-if-busy": AutoScheduleMode.declineIfBusy,
+                "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+            }.get(
+                config.Scheduling.Options.AutoSchedule.DefaultMode,
+                "automatic"
+            )
 
-        log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode,))
+        log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode.name,))
 
         cuas = self.recipient.principal.calendarUserAddresses
 
@@ -704,13 +716,19 @@
         partstat_counts = collections.defaultdict(int)
         for instance in instances.instances.itervalues():
             if instance.partstat == "NEEDS-ACTION" and instance.active:
-                if automode == "accept-always":
+                if automode == AutoScheduleMode.accept:
                     freePartstat = busyPartstat = "ACCEPTED"
-                elif automode == "decline-always":
+                elif automode == AutoScheduleMode.decline:
                     freePartstat = busyPartstat = "DECLINED"
                 else:
-                    freePartstat = "ACCEPTED" if automode in ("accept-if-free", "automatic",) else "NEEDS-ACTION"
-                    busyPartstat = "DECLINED" if automode in ("decline-if-busy", "automatic",) else "NEEDS-ACTION"
+                    freePartstat = "ACCEPTED" if automode in (
+                        AutoScheduleMode.acceptIfFree,
+                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+                    ) else "NEEDS-ACTION"
+                    busyPartstat = "DECLINED" if automode in (
+                        AutoScheduleMode.declineIfBusy,
+                        AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+                    ) else "NEEDS-ACTION"
                 instance.partstat = freePartstat if instance.free else busyPartstat
             partstat_counts[instance.partstat] += 1
 
@@ -901,7 +919,7 @@
 
         # We only need to fix data that already exists
         if recipient_resource is not None:
-            if originator_calendar.mainType() != None:
+            if originator_calendar.mainType() is not None:
                 yield self.writeCalendarResource(None, recipient_resource, originator_calendar)
             else:
                 yield self.deleteCalendarResource(recipient_resource)

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -162,7 +162,7 @@
         """
 
         self.calendar = calendar
-        self.preProcessCalendarData()
+        yield self.preProcessCalendarData()
 
         if self.logItems is not None:
             self.logItems["recipients"] = len(recipients)
@@ -550,7 +550,7 @@
         results = []
         for recipient in self.recipients:
             # Get the principal resource for this recipient
-            principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
+            principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
 
             # If no principal we may have a remote recipient but we should check whether
             # the address is one that ought to be on our server and treat that as a missing

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -189,7 +189,7 @@
         try:
             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
             resource = (yield home.objectResourceWithID(self.resourceID))
-            organizerPrincipal = home.directoryService().recordWithUID(home.uid())
+            organizerPrincipal = yield home.directoryService().recordWithUID(home.uid())
             organizer = organizerPrincipal.canonicalCalendarUserAddress()
             calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
             calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
@@ -311,7 +311,7 @@
         try:
             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
             resource = (yield home.objectResourceWithID(self.resourceID))
-            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+            attendeePrincipal = yield home.directoryService().recordWithUID(home.uid())
             attendee = attendeePrincipal.canonicalCalendarUserAddress()
             calendar = (yield resource.componentForUser())
             organizer = calendar.validOrganizerForScheduling()
@@ -336,6 +336,7 @@
             self._dequeued()
 
         except Exception, e:
+            # FIXME: calendar may not be set here!
             log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e))
             raise
         except:
@@ -381,7 +382,7 @@
 
         try:
             home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
-            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+            attendeePrincipal = yield home.directoryService().recordWithUID(home.uid())
             attendee = attendeePrincipal.canonicalCalendarUserAddress()
             calendar = Component.fromString(self.icalendarText)
             organizer = calendar.validOrganizerForScheduling()

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1915,14 +1915,14 @@
 
             # Normalize the calendar user addresses once we know we have valid
             # calendar data
-            component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
+            yield component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
 
         # Possible timezone stripping
         if config.EnableTimezonesByReference:
             component.stripKnownTimezones()
 
         # Check location/resource organizer requirement
-        self.validLocationResourceOrganizer(component, inserting, internal_state)
+        yield self.validLocationResourceOrganizer(component, inserting, internal_state)
 
         # Check access
         if config.EnablePrivateEvents:
@@ -1986,13 +1986,14 @@
                     raise TooManyAttendeesError("Attendee list size %d is larger than allowed limit %d" % (attendeeListLength, config.MaxAttendeesPerInstance))
 
 
+    @inlineCallbacks
     def validLocationResourceOrganizer(self, component, inserting, internal_state):
         """
         If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
         """
 
         if internal_state == ComponentUpdateState.NORMAL:
-            originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+            originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
             cutype = originatorPrincipal.getCUType() if originatorPrincipal is not None else "INDIVIDUAL"
             organizer = component.getOrganizer()
 
@@ -2009,10 +2010,10 @@
 
                 # Find current principal and update modified by details
                 if hasattr(self._txn, "_authz_uid"):
-                    authz = self.directoryService().recordWithUID(self._txn._authz_uid)
+                    authz = yield self.directoryService().recordWithUID(self._txn._authz_uid)
                     prop = Property("X-CALENDARSERVER-MODIFIED-BY", authz.canonicalCalendarUserAddress())
                     prop.setParameter("CN", authz.displayName())
-                    for candidate in authz.calendarUserAddresses:
+                    for candidate in authz.calendarUserAddresses():
                         if candidate.startswith("mailto:"):
                             prop.setParameter("EMAIL", candidate[7:])
                             break
@@ -2108,7 +2109,7 @@
                 log.debug("Organizer and attendee properties were entirely removed by the client. Restoring existing properties.")
 
                 # Get the originator who is the owner of the calendar resource being modified
-                originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+                originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
                 originatorAddresses = originatorPrincipal.calendarUserAddresses
 
                 for component in calendar.subcomponents():
@@ -2145,7 +2146,7 @@
                 log.debug("Sync COMPLETED property change.")
 
                 # Get the originator who is the owner of the calendar resource being modified
-                originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+                originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
                 originatorAddresses = originatorPrincipal.calendarUserAddresses
 
                 for component in calendar.subcomponents():
@@ -2264,6 +2265,7 @@
             self._componentChanged = True
 
 
+    @inlineCallbacks
     def addStructuredLocation(self, component):
         """
         Scan the component for ROOM attendees; if any are associated with an
@@ -2277,12 +2279,12 @@
                     value = attendee.value()
                     if value.startswith("urn:uuid:"):
                         guid = value[9:]
-                        loc = self.directoryService().recordWithGUID(guid)
+                        loc = yield self.directoryService().recordWithGUID(guid)
                         if loc is not None:
                             guid = loc.extras.get("associatedAddress",
                                 None)
                             if guid is not None:
-                                addr = self.directoryService().recordWithGUID(guid)
+                                addr = yield self.directoryService().recordWithGUID(guid)
                                 if addr is not None:
                                     street = addr.extras.get("streetAddress", "")
                                     geo = addr.extras.get("geo", "")
@@ -2539,7 +2541,7 @@
             self.processAlarms(component, inserting)
 
             # Process structured location
-            self.addStructuredLocation(component)
+            yield self.addStructuredLocation(component)
 
             # Do scheduling
             implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state))
@@ -3760,9 +3762,9 @@
             raise InvalidSplit()
 
         # Cannot be attendee
-        ownerPrincipal = self.calendar().ownerHome().directoryRecord()
+        ownerPrincipal = yield self.calendar().ownerHome().directoryRecord()
         organizer = component.getOrganizer()
-        organizerPrincipal = self.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
+        organizerPrincipal = (yield self.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
         if organizer is not None and organizerPrincipal.uid != ownerPrincipal.uid:
             raise InvalidSplit()
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -16,7 +16,7 @@
 ##
 from twisted.trial.unittest import TestCase
 from twext.python.clsprop import classproperty
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, succeed
 
 """
 Store test utility functions
@@ -40,7 +40,7 @@
 
 
     def recordWithCalendarUserAddress(self, cuaddr):
-        return self.recordsByCUA.get(cuaddr)
+        return succeed(self.recordsByCUA.get(cuaddr))
 
 
     def addRecord(self, record):
@@ -117,7 +117,7 @@
 
 
     def isProxyFor(self, other):
-        return False
+        return succeed(False)
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -100,6 +100,7 @@
 
 
 
+ at inlineCallbacks
 def normalizationLookup(cuaddr, recordFunction, config):
     """
     Lookup function to be passed to ical.normalizeCalendarUserAddresses.
@@ -108,23 +109,25 @@
     record for the cuaddr.
     """
     try:
-        record = recordFunction(cuaddr)
+        record = yield recordFunction(cuaddr)
     except Exception, e:
         log.debug("Lookup of %s failed: %s" % (cuaddr, e))
         record = None
 
     if record is None:
-        return (None, None, None)
+        returnValue((None, None, None))
     else:
         # RFC5545 syntax does not allow backslash escaping in
         # parameter values. A double-quote is thus not allowed
         # in a parameter value except as the start/end delimiters.
         # Single quotes are allowed, so we convert any double-quotes
         # to single-quotes.
-        return (
-            record.fullName.replace('"', "'"),
-            record.uid,
-            record.calendarUserAddresses,
+        returnValue(
+            (
+                record.displayName.replace('"', "'"),
+                record.uid,
+                record.calendarUserAddresses,
+            )
         )
 
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -37,8 +37,8 @@
         """
         Return the record for the specified calendar user address.
 
-        @return: the record.
-        @rtype: L{ICalendarStoreDirectoryRecord}
+        @return: Deferred resulting in the record.
+        @rtype: L{Deferred} resulting in L{ICalendarStoreDirectoryRecord}
         """
 
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -76,7 +76,8 @@
         self.store = store
 
 
-    def validRequst(self, source_guid, destination_guid):
+    @inlineCallbacks
+    def validRequest(self, source_guid, destination_guid):
         """
         Verify that the specified GUIDs are valid for the request and return the
         matching directory records.
@@ -86,22 +87,22 @@
         @param destination_guid: GUID for the user to whom the request is being sent
         @type destination_guid: C{str}
 
-        @return: C{tuple} of L{IStoreDirectoryRecord}
+        @return: L{Deferred} resulting in C{tuple} of L{IStoreDirectoryRecord}
         """
 
-        source = self.store.directoryService().recordWithUID(source_guid)
+        source = yield self.store.directoryService().recordWithUID(source_guid)
         if source is None:
             raise DirectoryRecordNotFoundError("Cross-pod source: {}".format(source_guid))
         if not source.thisServer():
             raise FailedCrossPodRequestError("Cross-pod source not on this server: {}".format(source_guid))
 
-        destination = self.store.directoryService().recordWithUID(destination_guid)
+        destination = yield self.store.directoryService().recordWithUID(destination_guid)
         if destination is None:
             raise DirectoryRecordNotFoundError("Cross-pod destination: {}".format(destination_guid))
         if destination.thisServer():
             raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(destination_guid))
 
-        return (source, destination,)
+        returnValue((source, destination,))
 
 
     @inlineCallbacks
@@ -186,7 +187,7 @@
         @type supported_components: C{str}
         """
 
-        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
 
         action = {
             "action": "shareinvite",
@@ -260,7 +261,7 @@
         @type shareUID: C{str}
         """
 
-        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
 
         action = {
             "action": "shareuninvite",
@@ -325,7 +326,7 @@
         @type summary: C{str}
         """
 
-        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
 
         action = {
             "action": "sharereply",
@@ -398,7 +399,7 @@
 
         actionName = "add-attachment"
         shareeView = objectResource._parentCollection
-        action, recipient = self._send(actionName, shareeView, objectResource)
+        action, recipient = yield self._send(actionName, shareeView, objectResource)
         action["rids"] = rids
         action["filename"] = filename
         result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
@@ -458,7 +459,7 @@
 
         actionName = "update-attachment"
         shareeView = objectResource._parentCollection
-        action, recipient = self._send(actionName, shareeView, objectResource)
+        action, recipient = yield self._send(actionName, shareeView, objectResource)
         action["managedID"] = managed_id
         action["filename"] = filename
         result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
@@ -514,7 +515,7 @@
 
         actionName = "remove-attachment"
         shareeView = objectResource._parentCollection
-        action, recipient = self._send(actionName, shareeView, objectResource)
+        action, recipient = yield self._send(actionName, shareeView, objectResource)
         action["rids"] = rids
         action["managedID"] = managed_id
         result = yield self.sendRequest(shareeView._txn, recipient, action)
@@ -557,6 +558,7 @@
     # Sharer data access related apis
     #
 
+    @inlineCallbacks
     def _send(self, action, parent, child=None):
         """
         Base behavior for an operation on a L{CommonHomeChild}.
@@ -570,7 +572,7 @@
         ownerID = parent.external_id()
         shareeUID = parent.viewerHome().uid()
 
-        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
 
         result = {
             "action": action,
@@ -581,7 +583,7 @@
         }
         if child is not None:
             result["resource_id"] = child.id()
-        return result, recipient
+        returnValue((result, recipient))
 
 
     @inlineCallbacks
@@ -644,7 +646,7 @@
         @type kwargs: C{dict}
         """
 
-        action, recipient = self._send(actionName, shareeView, objectResource)
+        action, recipient = yield self._send(actionName, shareeView, objectResource)
         if args is not None:
             action["arguments"] = args
         if kwargs is not None:
@@ -710,7 +712,7 @@
         servertoserver,
         event_details,
     ):
-        action, recipient = self._send("freebusy", calresource)
+        action, recipient = yield self._send("freebusy", calresource)
         action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
         action["matchtotal"] = matchtotal
         action["excludeuid"] = excludeuid

Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -179,11 +179,13 @@
             davxml.Privilege(davxml.Read()),
         )
 
-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
+        return succeed(
+            davxml.ACL(
+                # DAV:Read for all principals (includes anonymous)
+                davxml.ACE(
+                    davxml.Principal(davxml.All()),
+                    davxml.Grant(*privs),
+                    davxml.Protected(),
+                ),
+            )
         )

Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -123,29 +123,41 @@
     }
 
 
-    def test_validRequst(self):
+    @inlineCallbacks
+    def test_validRequest(self):
         """
         Cross-pod request fails when there is no shared secret header present.
         """
 
         conduit = PoddingConduit(self.storeUnderTest())
-        r1, r2 = conduit.validRequst("user01", "puser02")
+        r1, r2 = yield conduit.validRequest("user01", "puser02")
         self.assertTrue(r1 is not None)
         self.assertTrue(r2 is not None)
 
-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "bogus01", "user02")
-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "user01", "bogus02")
-        self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, "user01", "user02")
+        self.assertFailure(
+            conduit.validRequest("bogus01", "user02"),
+            DirectoryRecordNotFoundError
+        )
 
+        self.assertFailure(
+            conduit.validRequest("user01", "bogus02"),
+            DirectoryRecordNotFoundError
+        )
 
+        self.assertFailure(
+            conduit.validRequest("user01", "user02"),
+            FailedCrossPodRequestError
+        )
 
+
+
 class TestConduitToConduit(MultiStoreConduitTest):
 
     class FakeConduit(PoddingConduit):
 
         @inlineCallbacks
         def send_fake(self, txn, ownerUID, shareeUID):
-            _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
+            _ignore_owner, sharee = yield self.validRequest(ownerUID, shareeUID)
             action = {
                 "action": "fake",
                 "echo": "bravo"

Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -922,102 +922,164 @@
     @classproperty
     def _addGroupQuery(cls):
         gr = schema.GROUPS
-        return Insert({gr.NAME: Parameter("name"),
-                       gr.GROUP_GUID: Parameter("groupGUID"),
-                       gr.MEMBERSHIP_HASH: Parameter("membershipHash")},
-                       Return=gr.GROUP_ID)
+        return Insert(
+            {
+                gr.NAME: Parameter("name"),
+                gr.GROUP_GUID: Parameter("groupUID"),
+                gr.MEMBERSHIP_HASH: Parameter("membershipHash")
+            },
+            Return=gr.GROUP_ID
+        )
 
 
     @classproperty
     def _updateGroupQuery(cls):
         gr = schema.GROUPS
-        return Update({gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
-            gr.NAME: Parameter("name"), gr.MODIFIED: Parameter("timestamp")},
-            Where=(gr.GROUP_GUID == Parameter("groupGUID")))
+        return Update(
+            {
+                gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
+                gr.NAME: Parameter("name"),
+                gr.MODIFIED:
+                Parameter("timestamp")
+            },
+            Where=(gr.GROUP_GUID == Parameter("groupUID"))
+        )
 
 
     @classproperty
-    def _groupByGUID(cls):
+    def _groupByUID(cls):
         gr = schema.GROUPS
-        return Select([gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
-                Where=(
-                    gr.GROUP_GUID == Parameter("groupGUID")
-                )
-            )
+        return Select(
+            [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH],
+            From=gr,
+            Where=(gr.GROUP_GUID == Parameter("groupUID"))
+        )
 
 
     @classproperty
     def _groupByID(cls):
         gr = schema.GROUPS
-        return Select([gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
-                Where=(
-                    gr.GROUP_ID == Parameter("groupID")
-                )
-            )
+        return Select(
+            [gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH],
+            From=gr,
+            Where=(gr.GROUP_ID == Parameter("groupID"))
+        )
 
 
     @classproperty
     def _deleteGroup(cls):
         gr = schema.GROUPS
-        return Delete(From=gr,
-              Where=(gr.GROUP_ID == Parameter("groupID")))
+        return Delete(
+            From=gr,
+            Where=(gr.GROUP_ID == Parameter("groupID"))
+        )
 
 
-    def addGroup(self, groupGUID, name, membershipHash):
+    def addGroup(self, groupUID, name, membershipHash):
         """
-        @type groupGUID: C{UUID}
+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
         """
-        return self._addGroupQuery.on(self, name=name,
-            groupGUID=str(groupGUID), membershipHash=membershipHash)
+        return self._addGroupQuery.on(
+            self,
+            name=name.encode("utf-8"),
+            groupUID=groupUID.encode("utf-8"),
+            membershipHash=membershipHash
+        )
 
 
-    def updateGroup(self, groupGUID, name, membershipHash):
+    def updateGroup(self, groupUID, name, membershipHash):
         """
-        @type groupGUID: C{UUID}
+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
         """
         timestamp = datetime.datetime.utcnow()
-        return self._updateGroupQuery.on(self, name=name,
-            groupGUID=str(groupGUID), timestamp=timestamp,
-            membershipHash=membershipHash)
+        return self._updateGroupQuery.on(
+            self,
+            name=name.encode("utf-8"),
+            groupUID=groupUID.encode("utf-8"),
+            timestamp=timestamp,
+            membershipHash=membershipHash
+        )
 
 
     @inlineCallbacks
-    def groupByGUID(self, groupGUID):
+    def groupByUID(self, groupUID):
         """
-        @type groupGUID: C{UUID}
+        Return or create a record for the group UID.
+
+        @type groupUID: C{unicode}
+
+        @return: Deferred firing with tuple of group ID C{str}, group name
+            C{unicode}, and membership hash C{str}
         """
-        results = (yield self._groupByGUID.on(self, groupGUID=str(groupGUID)))
+        results = (
+            yield self._groupByUID.on(
+                self, groupUID=groupUID.encode("utf-8")
+            )
+        )
         if results:
-            returnValue(results[0])
+            returnValue((
+                results[0][0],  # group id
+                results[0][1].decode("utf-8"),  # name
+                results[0][2],  # membership hash
+            ))
         else:
-            savepoint = SavepointAction("groupByGUID")
+            savepoint = SavepointAction("groupByUID")
             yield savepoint.acquire(self)
             try:
-                yield self.addGroup(groupGUID, "", "")
+                yield self.addGroup(groupUID, u"", "")
             except Exception:
                 yield savepoint.rollback(self)
-                results = (yield self._groupByGUID.on(self,
-                    groupGUID=str(groupGUID)))
+                results = (
+                    yield self._groupByUID.on(
+                        self, groupUID=groupUID.encode("utf-8")
+                    )
+                )
                 if results:
-                    returnValue(results[0])
+                    returnValue((
+                        results[0][0],  # group id
+                        results[0][1].decode("utf-8"),  # name
+                        results[0][2],  # membership hash
+                    ))
                 else:
                     raise
             else:
                 yield savepoint.release(self)
-                results = (yield self._groupByGUID.on(self,
-                    groupGUID=str(groupGUID)))
+                results = (
+                    yield self._groupByUID.on(
+                        self, groupUID=groupUID.encode("utf-8")
+                    )
+                )
                 if results:
-                    returnValue(results[0])
+                    returnValue((
+                        results[0][0],  # group id
+                        results[0][1].decode("utf-8"),  # name
+                        results[0][2],  # membership hash
+                    ))
                 else:
                     raise
 
 
     @inlineCallbacks
     def groupByID(self, groupID):
+        """
+        Given a group ID, return the group UID, or raise NotFoundError
+
+        @type groupID: C{str}
+        @return: Deferred firing with a tuple of group UID C{unicode},
+            group name C{unicode}, and membership hash C{str}
+        """
         try:
             results = (yield self._groupByID.on(self, groupID=groupID))[0]
             if results:
-                results = [UUID("urn:uuid:" + results[0])] + results[1:]
+                results = (
+                    results[0].decode("utf-8"),
+                    results[1].decode("utf-8"),
+                    results[2]
+                )
             returnValue(results)
         except IndexError:
             raise NotFoundError
@@ -1037,7 +1099,7 @@
         return Insert(
             {
                 gm.GROUP_ID: Parameter("groupID"),
-                gm.MEMBER_GUID: Parameter("memberGUID")
+                gm.MEMBER_GUID: Parameter("memberUID")
             }
         )
 
@@ -1050,7 +1112,7 @@
             Where=(
                 gm.GROUP_ID == Parameter("groupID")
             ).And(
-                gm.MEMBER_GUID == Parameter("memberGUID")
+                gm.MEMBER_GUID == Parameter("memberUID")
             )
         )
 
@@ -1069,25 +1131,35 @@
 
     @classproperty
     def _selectGroupsForQuery(cls):
+        gr = schema.GROUPS
         gm = schema.GROUP_MEMBERSHIP
+
         return Select(
-            [gm.GROUP_ID],
-            From=gm,
+            [gr.GROUP_GUID],
+            From=gr,
             Where=(
-                gm.MEMBER_GUID == Parameter("guid")
+                gr.GROUP_ID.In(
+                    Select(
+                        [gm.GROUP_ID],
+                        From=gm,
+                        Where=(
+                            gm.MEMBER_GUID == Parameter("uid")
+                        )
+                    )
+                )
             )
         )
 
 
-    def addMemberToGroup(self, memberGUID, groupID):
+    def addMemberToGroup(self, memberUID, groupID):
         return self._addMemberToGroupQuery.on(
-            self, groupID=groupID, memberGUID=str(memberGUID)
+            self, groupID=groupID, memberUID=memberUID.encode("utf-8")
         )
 
 
-    def removeMemberFromGroup(self, memberGUID, groupID):
+    def removeMemberFromGroup(self, memberUID, groupID):
         return self._removeMemberFromGroupQuery.on(
-            self, groupID=groupID, memberGUID=str(memberGUID)
+            self, groupID=groupID, memberUID=memberUID.encode("utf-8")
         )
 
 
@@ -1107,25 +1179,29 @@
         members = set()
         results = (yield self._selectGroupMembersQuery.on(self, groupID=groupID))
         for row in results:
-            members.add(UUID("urn:uuid:" + row[0]))
+            members.add(row[0].decode("utf-8"))
         returnValue(members)
 
 
     @inlineCallbacks
-    def groupsFor(self, guid):
+    def groupsFor(self, uid):
         """
-        Returns the cached set of GUIDs for the groups this given guid is
+        Returns the cached set of UIDs for the groups this given uid is
         a member of.
 
-        @param guid: the guid
-        @type guid: C{UUID}
+        @param uid: the uid
+        @type uid: C{unicode}
         @return: the set of group IDs
         @rtype: a Deferred which fires with a set() of C{int} group IDs
         """
         groups = set()
-        results = (yield self._selectGroupsForQuery.on(self, guid=str(guid)))
+        results = (
+            yield self._selectGroupsForQuery.on(
+                self, uid=uid.encode("utf-8")
+            )
+        )
         for row in results:
-            groups.add(row[0])
+            groups.add(row[0].decode("utf-8"))
         returnValue(groups)
 
     # End of Group Members
@@ -1199,13 +1275,23 @@
     @classproperty
     def _selectDelegateGroupsQuery(cls):
         ds = schema.DELEGATE_GROUPS
+        gr = schema.GROUPS
+
         return Select(
-            [ds.GROUP_ID],
-            From=ds,
+            [gr.GROUP_GUID],
+            From=gr,
             Where=(
-                ds.DELEGATOR == Parameter("delegator")
-            ).And(
-                ds.READ_WRITE == Parameter("readWrite")
+                gr.GROUP_ID.In(
+                    Select(
+                        [ds.GROUP_ID],
+                        From=ds,
+                        Where=(
+                            ds.DELEGATOR == Parameter("delegator")
+                        ).And(
+                            ds.READ_WRITE == Parameter("readWrite")
+                        )
+                    )
+                )
             )
         )
 
@@ -1317,18 +1403,18 @@
         Adds a row to the DELEGATES table.  The delegate should not be a
         group.  To delegate to a group, call addDelegateGroup() instead.
 
-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
         @param readWrite: grant read and write access if True, otherwise
             read-only access
         @type readWrite: C{boolean}
         """
         return self._addDelegateQuery.on(
             self,
-            delegator=str(delegator),
-            delegate=str(delegate),
+            delegator=delegator.encode("utf-8"),
+            delegate=delegate.encode("utf-8"),
             readWrite=1 if readWrite else 0
         )
 
@@ -1339,8 +1425,8 @@
         Adds a row to the DELEGATE_GROUPS table.  The delegate should be a
         group.  To delegate to a person, call addDelegate() instead.
 
-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
         @param delegateGroupID: the GROUP_ID of the delegate group
         @type delegateGroupID: C{int}
         @param readWrite: grant read and write access if True, otherwise
@@ -1349,7 +1435,7 @@
         """
         return self._addDelegateGroupQuery.on(
             self,
-            delegator=str(delegator),
+            delegator=delegator.encode("utf-8"),
             groupID=delegateGroupID,
             readWrite=1 if readWrite else 0,
             isExternal=1 if isExternal else 0
@@ -1361,18 +1447,18 @@
         Removes a row from the DELEGATES table.  The delegate should not be a
         group.  To remove a delegate group, call removeDelegateGroup() instead.
 
-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
         @param readWrite: remove read and write access if True, otherwise
             read-only access
         @type readWrite: C{boolean}
         """
         return self._removeDelegateQuery.on(
             self,
-            delegator=str(delegator),
-            delegate=str(delegate),
+            delegator=delegator.encode("utf-8"),
+            delegate=delegate.encode("utf-8"),
             readWrite=1 if readWrite else 0
         )
 
@@ -1382,8 +1468,8 @@
         Removes a row from the DELEGATE_GROUPS table.  The delegate should be a
         group.  To remove a delegate person, call removeDelegate() instead.
 
-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
         @param delegateGroupID: the GROUP_ID of the delegate group
         @type delegateGroupID: C{int}
         @param readWrite: remove read and write access if True, otherwise
@@ -1392,26 +1478,26 @@
         """
         return self._removeDelegateGroupQuery.on(
             self,
-            delegator=str(delegator),
+            delegator=delegator.encode("utf-8"),
             groupID=delegateGroupID,
             readWrite=1 if readWrite else 0
         )
 
 
     @inlineCallbacks
-    def delegates(self, delegator, readWrite):
+    def delegates(self, delegator, readWrite, expanded=False):
         """
-        Returns the GUIDs of all delegates for the given delegator.  If
-        delegate access was granted to any groups, those groups' members
-        (flattened) will be included. No GUIDs of the groups themselves
-        will be returned.
+        Returns the UIDs of all delegates for the given delegator.  If
+        expanded is False, only the direct delegates (users and groups)
+        are returned.  If expanded is True, the expanded membmership is
+        returned, not including the groups themselves.
 
-        @param delegator: the GUID of the delegator
-        @type delegator: C{UUID}
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
         @param readWrite: the access-type to check for; read and write
             access if True, otherwise read-only access
         @type readWrite: C{boolean}
-        @returns: the GUIDs of the delegates (for the specified access
+        @returns: the UIDs of the delegates (for the specified access
             type)
         @rtype: a Deferred resulting in a set
         """
@@ -1421,35 +1507,48 @@
         results = (
             yield self._selectDelegatesQuery.on(
                 self,
-                delegator=str(delegator),
+                delegator=delegator.encode("utf-8"),
                 readWrite=1 if readWrite else 0
             )
         )
         for row in results:
-            delegates.add(UUID("urn:uuid:" + row[0]))
+            delegates.add(row[0].decode("utf-8"))
 
-        # Finally get those who are in groups which have been delegated to
-        results = (
-            yield self._selectIndirectDelegatesQuery.on(
-                self,
-                delegator=str(delegator),
-                readWrite=1 if readWrite else 0
+        if expanded:
+            # Get those who are in groups which have been delegated to
+            results = (
+                yield self._selectIndirectDelegatesQuery.on(
+                    self,
+                    delegator=delegator.encode("utf-8"),
+                    readWrite=1 if readWrite else 0
+                )
             )
-        )
-        for row in results:
-            delegates.add(UUID("urn:uuid:" + row[0]))
+            for row in results:
+                delegates.add(row[0].decode("utf-8"))
 
+        else:
+            # Get the directly-delegated-to groups
+            results = (
+                yield self._selectDelegateGroupsQuery.on(
+                    self,
+                    delegator=delegator.encode("utf-8"),
+                    readWrite=1 if readWrite else 0
+                )
+            )
+            for row in results:
+                delegates.add(row[0].decode("utf-8"))
+
         returnValue(delegates)
 
 
     @inlineCallbacks
     def delegators(self, delegate, readWrite):
         """
-        Returns the GUIDs of all delegators which have granted access to
+        Returns the UIDs of all delegators which have granted access to
         the given delegate, either directly or indirectly via groups.
 
-        @param delegate: the GUID of the delegate
-        @type delegate: C{UUID}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
         @param readWrite: the access-type to check for; read and write
             access if True, otherwise read-only access
         @type readWrite: C{boolean}
@@ -1463,24 +1562,24 @@
         results = (
             yield self._selectDirectDelegatorsQuery.on(
                 self,
-                delegate=str(delegate),
+                delegate=delegate.encode("utf-8"),
                 readWrite=1 if readWrite else 0
             )
         )
         for row in results:
-            delegators.add(UUID("urn:uuid:" + row[0]))
+            delegators.add(row[0].decode("utf-8"))
 
         # Finally get those who have delegated to groups the delegate
         # is a member of
         results = (
             yield self._selectIndirectDelegatorsQuery.on(
                 self,
-                delegate=str(delegate),
+                delegate=delegate.encode("utf-8"),
                 readWrite=1 if readWrite else 0
             )
         )
         for row in results:
-            delegators.add(UUID("urn:uuid:" + row[0]))
+            delegators.add(row[0].decode("utf-8"))
 
         returnValue(delegators)
 
@@ -1488,11 +1587,11 @@
     @inlineCallbacks
     def allGroupDelegates(self):
         """
-        Return the GUIDs of all groups which have been delegated to.  Useful
+        Return the UIDs of all groups which have been delegated to.  Useful
         for obtaining the set of groups which need to be synchronized from
         the directory.
 
-        @returns: the GUIDs of all delegated-to groups
+        @returns: the UIDs of all delegated-to groups
         @rtype: a Deferred resulting in a set
         """
         gr = schema.GROUPS
@@ -1505,7 +1604,7 @@
         ).on(self))
         delegates = set()
         for row in results:
-            delegates.add(UUID("urn:uuid:" + row[0]))
+            delegates.add(row[0].decode("utf-8"))
 
         returnValue(delegates)
 
@@ -1513,22 +1612,22 @@
     @inlineCallbacks
     def externalDelegates(self):
         """
-        Returns a dictionary mapping delegate GUIDs to (read-group, write-group)
+        Returns a dictionary mapping delegate UIDs to (read-group, write-group)
         tuples, including only those assignments that originated from the
         directory.
 
-        @returns: dictionary mapping delegator guid to (readDelegateGUID,
-            writeDelegateGUID) tuples
+        @returns: dictionary mapping delegator uid to (readDelegateUID,
+            writeDelegateUID) tuples
         @rtype: a Deferred resulting in a dictionary
         """
         delegates = {}
 
         # Get the externally managed delegates (which are all groups)
         results = (yield self._selectExternalDelegateGroupsQuery.on(self))
-        for delegator, readDelegateGUID, writeDelegateGUID in results:
-            delegates[UUID(delegator)] = (
-                UUID(readDelegateGUID) if readDelegateGUID else None,
-                UUID(writeDelegateGUID) if writeDelegateGUID else None
+        for delegator, readDelegateUID, writeDelegateUID in results:
+            delegates[delegator.encode("utf-8")] = (
+                readDelegateUID.encode("utf-8") if readDelegateUID else None,
+                writeDelegateUID.encode("utf-8") if writeDelegateUID else None
             )
 
         returnValue(delegates)
@@ -1537,7 +1636,7 @@
     @inlineCallbacks
     def assignExternalDelegates(
         self, delegator, readDelegateGroupID, writeDelegateGroupID,
-        readDelegateGUID, writeDelegateGUID
+        readDelegateUID, writeDelegateUID
     ):
         """
         Update the external delegate group table so we can quickly identify
@@ -1560,12 +1659,12 @@
         )
 
         # Store new assignments in the external comparison table
-        if readDelegateGUID or writeDelegateGUID:
+        if readDelegateUID or writeDelegateUID:
             readDelegateForDB = (
-                str(readDelegateGUID) if readDelegateGUID else ""
+                readDelegateUID.encode("utf-8") if readDelegateUID else ""
             )
             writeDelegateForDB = (
-                str(writeDelegateGUID) if writeDelegateGUID else ""
+                writeDelegateUID.encode("utf-8") if writeDelegateUID else ""
             )
             yield self._storeExternalDelegateGroupsPairQuery.on(
                 self,
@@ -2750,7 +2849,7 @@
                 returnValue(None)
 
             # Determine if the user is local or external
-            record = txn.directoryService().recordWithUID(uid)
+            record = yield txn.directoryService().recordWithUID(uid)
             if record is None:
                 raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
 
@@ -6751,7 +6850,7 @@
             created = False
         elif create:
             # Determine if the user is local or external
-            record = txn.directoryService().recordWithUID(uid)
+            record = yield txn.directoryService().recordWithUID(uid)
             if record is None:
                 raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -46,7 +46,7 @@
 from twisted.application.service import Service
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, inlineCallbacks
-from twisted.internet.defer import returnValue
+from twisted.internet.defer import returnValue, succeed
 from twisted.internet.task import deferLater
 from twisted.trial.unittest import TestCase
 
@@ -110,14 +110,14 @@
 
 
     def recordWithUID(self, uid):
-        return self.records.get(uid)
+        return succeed(self.records.get(uid))
 
 
     def recordWithGUID(self, guid):
         for record in self.records.itervalues():
             if record.guid == guid:
-                return record
-        return None
+                return succeed(record)
+        return succeed(None)
 
 
     def addRecord(self, record):

Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -15,35 +15,50 @@
 ##
 
 import cPickle as pickle
+import uuid
 
 from twext.python.log import Logger
 from twext.who.directory import DirectoryRecord as BaseDirectoryRecord
 from twext.who.directory import DirectoryService as BaseDirectoryService
-from twext.who.idirectory import RecordType
+from twext.who.idirectory import RecordType, IDirectoryService
 import twext.who.idirectory
 from twext.who.util import ConstantsContainer
+from twisted.cred.credentials import UsernamePassword
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.internet.protocol import ClientCreator
 from twisted.protocols import amp
+from twisted.python.constants import Names, NamedConstant
+from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
+from txdav.caldav.icalendardirectoryservice import ICalendarStoreDirectoryRecord
+from txdav.common.idirectoryservice import IStoreDirectoryService
 from txdav.dps.commands import (
     RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
     RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
+    MembersCommand, GroupsCommand,
     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand
 )
+import txdav.who.delegates
 import txdav.who.idirectory
+from txweb2.auth.digest import DigestedCredentials
 from zope.interface import implementer
 
-
 log = Logger()
 
-
 ##
 ## Client implementation of Directory Proxy Service
 ##
 
 
- at implementer(twext.who.idirectory.IDirectoryService)
+
+## MOVE2WHO TODOs:
+## augmented service
+## configuration of aggregate services
+## hooking up delegates
+## calverify needs deferreds, including:
+##    component.normalizeCalendarUserAddresses
+
+ at implementer(IDirectoryService, IStoreDirectoryService)
 class DirectoryService(BaseDirectoryService):
     """
     Client side of directory proxy
@@ -51,17 +66,57 @@
 
     recordType = ConstantsContainer(
         (twext.who.idirectory.RecordType,
-         txdav.who.idirectory.RecordType)
+         txdav.who.idirectory.RecordType,
+         txdav.who.delegates.RecordType)
     )
 
+    fieldName = ConstantsContainer(
+        (twext.who.idirectory.FieldName,
+         txdav.who.idirectory.FieldName)
+    )
 
+    # def __init__(self, fieldNames, recordTypes):
+    #     self.fieldName = fieldNames
+    #     self.recordType = recordTypes
+
+    # MOVE2WHO
+    def getGroups(self, guids=None):
+        return succeed(set())
+
+    # Must maintain the hack for a bit longer:
+    def setPrincipalCollection(self, principalCollection):
+        """
+        Set the principal service that the directory relies on for doing proxy tests.
+
+        @param principalService: the principal service.
+        @type principalService: L{DirectoryProvisioningResource}
+        """
+        self.principalCollection = principalCollection
+
+    guid = "1332A615-4D3A-41FE-B636-FBE25BFB982E"
+
+    # END MOVE2WHO
+
+
+
+
     def _dictToRecord(self, serializedFields):
         """
-        This to be replaced by something awesome
+        Turn a dictionary of fields sent from the server into a directory
+        record
         """
         if not serializedFields:
             return None
 
+        # print("FIELDS", serializedFields)
+
+        # MOVE2WHO -- existing code assumes record.emailAddresses always exists,
+        # so adding this here, but perhaps we should change the behavior in
+        # twext.who itself:
+        # Add default empty list of email addresses
+        if self.fieldName.emailAddresses.name not in serializedFields:
+            serializedFields[self.fieldName.emailAddresses.name] = []
+
         fields = {}
         for fieldName, value in serializedFields.iteritems():
             try:
@@ -70,8 +125,21 @@
                 # unknown field
                 pass
             else:
-                fields[field] = value
-        fields[self.fieldName.recordType] = self.recordType.user
+                valueType = self.fieldName.valueType(field)
+                if valueType in (unicode, bool):
+                    fields[field] = value
+                elif valueType is uuid.UUID:
+                    fields[field] = uuid.UUID(value)
+                elif issubclass(valueType, Names):
+                    if value is not None:
+                        fields[field] = field.valueType.lookupByName(value)
+                    else:
+                        fields[field] = None
+                elif issubclass(valueType, NamedConstant):
+                    if fieldName == "recordType":  # Is there a better way?
+                        fields[field] = self.recordType.lookupByName(value)
+
+        # print("AFTER:", fields)
         return DirectoryRecord(self, fields)
 
 
@@ -127,10 +195,14 @@
 
 
     def recordWithShortName(self, recordType, shortName):
+        # MOVE2WHO
+        # temporary hack until we can fix all callers not to pass strings:
+        if isinstance(recordType, (str, unicode)):
+            recordType = self.recordType.lookupByName(recordType)
         return self._call(
             RecordWithShortNameCommand,
             self._processSingleRecord,
-            recordType=recordType.description.encode("utf-8"),
+            recordType=recordType.name.encode("utf-8"),
             shortName=shortName.encode("utf-8")
         )
 
@@ -155,7 +227,7 @@
         return self._call(
             RecordsWithRecordTypeCommand,
             self._processMultipleRecords,
-            recordType=recordType.description.encode("utf-8")
+            recordType=recordType.name.encode("utf-8")
         )
 
 
@@ -167,9 +239,83 @@
         )
 
 
+    def listRecords(self, recordType):
+        # MOVE2WHO
+        return []
 
+
+    @inlineCallbacks
+    def recordWithCalendarUserAddress(self, address):
+        address = normalizeCUAddr(address)
+        record = None
+        if address.startswith("urn:uuid:"):
+            guid = address[9:]
+            record = yield self.recordWithGUID(guid)
+        elif address.startswith("mailto:"):
+            records = yield self.recordsWithEmailAddress(address[7:])
+            if records:
+                returnValue(records[0])
+            else:
+                returnValue(None)
+        elif address.startswith("/principals/"):
+            parts = address.split("/")
+            if len(parts) == 4:
+                if parts[2] == "__uids__":
+                    guid = parts[3]
+                    record = yield self.recordWithGUID(guid)
+                else:
+                    recordType = self.fieldName.lookupByName(parts[2])
+                    record = yield self.recordWithShortName(recordType, parts[3])
+
+        returnValue(record if record and record.hasCalendars else None)
+
+
+    @inlineCallbacks
+    def recordsMatchingTokens(self, tokens, context=None, limitResults=50,
+                              timeoutSeconds=10):
+        rec = yield self.recordWithShortName(
+            twext.who.idirectory.RecordType.user,
+            u"wsanchez"
+        )
+        returnValue([rec])
+
+
+
+
+ at implementer(ICalendarStoreDirectoryRecord)
 class DirectoryRecord(BaseDirectoryRecord):
 
+
+    @inlineCallbacks
+    def verifyCredentials(self, credentials):
+
+        # XYZZY REMOVE THIS, it bypasses all authentication!:
+        returnValue(True)
+
+        if isinstance(credentials, UsernamePassword):
+            log.debug("UsernamePassword")
+            returnValue(
+                (yield self.verifyPlaintextPassword(credentials.password))
+            )
+
+        elif isinstance(credentials, DigestedCredentials):
+            log.debug("DigestedCredentials")
+            returnValue(
+                (yield self.verifyHTTPDigest(
+                    self.shortNames[0],
+                    self.service.realmName,
+                    credentials.fields["uri"],
+                    credentials.fields["nonce"],
+                    credentials.fields.get("cnonce", ""),
+                    credentials.fields["algorithm"],
+                    credentials.fields.get("nc", ""),
+                    credentials.fields.get("qop", ""),
+                    credentials.fields["response"],
+                    credentials.method
+                ))
+            )
+
+
     def verifyPlaintextPassword(self, password):
         return self.service._call(
             VerifyPlaintextPasswordCommand,
@@ -200,7 +346,165 @@
         )
 
 
+    def members(self):
+        return self.service._call(
+            MembersCommand,
+            self.service._processMultipleRecords,
+            uid=self.uid.encode("utf-8")
+        )
 
+
+    def groups(self):
+        return self.service._call(
+            GroupsCommand,
+            self.service._processMultipleRecords,
+            uid=self.uid.encode("utf-8")
+        )
+
+
+    @property
+    def calendarUserAddresses(self):
+        if not self.hasCalendars:
+            return frozenset()
+
+        try:
+            cuas = set(
+                ["mailto:%s" % (emailAddress,)
+                 for emailAddress in self.emailAddresses]
+            )
+        except AttributeError:
+            cuas = set()
+
+        try:
+            if self.guid:
+                if isinstance(self.guid, uuid.UUID):
+                    guid = unicode(self.guid).upper()
+                else:
+                    guid = self.guid
+                cuas.add("urn:uuid:{guid}".format(guid=guid))
+        except AttributeError:
+            # No guid
+            pass
+        cuas.add("/principals/__uids__/{uid}/".format(uid=self.uid))
+        for shortName in self.shortNames:
+            cuas.add("/principals/{rt}/{sn}/".format(
+                rt=self.recordType.name + "s", sn=shortName)
+            )
+        return frozenset(cuas)
+
+
+    def getCUType(self):
+        # Mapping from directory record.recordType to RFC2445 CUTYPE values
+        self._cuTypes = {
+            self.service.recordType.user: 'INDIVIDUAL',
+            self.service.recordType.group: 'GROUP',
+            self.service.recordType.resource: 'RESOURCE',
+            self.service.recordType.location: 'ROOM',
+        }
+
+        return self._cuTypes.get(self.recordType, "UNKNOWN")
+
+
+    @property
+    def displayName(self):
+        return self.fullNames[0]
+
+
+    def cacheToken(self):
+        """
+        Generate a token that can be uniquely used to identify the state of this record for use
+        in a cache.
+        """
+        return hash((
+            self.__class__.__name__,
+            self.service.realmName,
+            self.recordType.name,
+            self.shortNames,
+            self.guid,
+            self.hasCalendars,
+        ))
+
+
+    def canonicalCalendarUserAddress(self):
+        """
+            Return a CUA for this record, preferring in this order:
+            urn:uuid: form
+            mailto: form
+            first in calendarUserAddresses list
+        """
+
+        cua = ""
+        for candidate in self.calendarUserAddresses:
+            # Pick the first one, but urn:uuid: and mailto: can override
+            if not cua:
+                cua = candidate
+            # But always immediately choose the urn:uuid: form
+            if candidate.startswith("urn:uuid:"):
+                cua = candidate
+                break
+            # Prefer mailto: if no urn:uuid:
+            elif candidate.startswith("mailto:"):
+                cua = candidate
+        return cua
+
+
+    def enabledAsOrganizer(self):
+        # MOVE2WHO FIXME TO LOOK AT CONFIG
+        if self.recordType == self.service.recordType.user:
+            return True
+        elif self.recordType == DirectoryService.recordType_groups:
+            return False  # config.Scheduling.Options.AllowGroupAsOrganizer
+        elif self.recordType == DirectoryService.recordType_locations:
+            return False  # config.Scheduling.Options.AllowLocationAsOrganizer
+        elif self.recordType == DirectoryService.recordType_resources:
+            return False  # config.Scheduling.Options.AllowResourceAsOrganizer
+        else:
+            return False
+
+
+    #MOVE2WHO
+    def thisServer(self):
+        return True
+
+
+    def isLoginEnabled(self):
+        return self.loginAllowed
+
+
+    #MOVE2WHO
+    def calendarsEnabled(self):
+        # In the old world, this *also* looked at config:
+        # return config.EnableCalDAV and self.enabledForCalendaring
+        return self.hasCalendars
+
+
+    def getAutoScheduleMode(self, organizer):
+        # MOVE2WHO Fix this to take organizer into account:
+        return self.autoScheduleMode
+
+
+    def canAutoSchedule(self, organizer=None):
+        # MOVE2WHO Fix this:
+        return True
+
+
+    # For scheduling/freebusy
+    # FIXME: doesn't this need to happen in the DPS?
+    @inlineCallbacks
+    def isProxyFor(self, other):
+        for recordType in (
+            txdav.who.delegates.RecordType.readDelegatorGroup,
+            txdav.who.delegates.RecordType.writeDelegatorGroup,
+        ):
+            delegatorGroup = yield self.service.recordWithShortName(
+                recordType, self.uid
+            )
+            if delegatorGroup:
+                if other in (yield delegatorGroup.members()):
+                    returnValue(True)
+
+
+
 # Test client:
 
 

Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -89,6 +89,25 @@
 
 
 
+class MembersCommand(amp.Command):
+    arguments = [
+        ('uid', amp.String()),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
+
+
+class GroupsCommand(amp.Command):
+    arguments = [
+        ('uid', amp.String()),
+    ]
+    response = [
+        ('fieldsList', amp.String()),
+    ]
+
+
+
 class VerifyPlaintextPasswordCommand(amp.Command):
     arguments = [
         ('uid', amp.String()),

Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -18,28 +18,35 @@
 import os
 import uuid
 
+from calendarserver.tap.util import getDBPool, storeFromConfig
 from twext.python.log import Logger
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
 from twext.who.idirectory import RecordType
+from twext.who.ldap import DirectoryService as LDAPDirectoryService
 from twisted.application import service
 from twisted.application.strports import service as strPortsService
+from twisted.cred.credentials import UsernamePassword
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.protocol import Factory
 from twisted.plugin import IPlugin
 from twisted.protocols import amp
+from twisted.python.constants import Names, NamedConstant
 from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedClass
 from twisted.python.usage import Options, UsageError
 from twistedcaldav.config import config
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 from txdav.dps.commands import (
     RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
     RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
+    MembersCommand, GroupsCommand,
     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand,
     # UpdateRecordsCommand, RemoveRecordsCommand
 )
-from twext.who.ldap import DirectoryService as LDAPDirectoryService
+from txdav.who.augment import AugmentedDirectoryService
+from txdav.who.delegates import DirectoryService as DelegateDirectoryService
 from txdav.who.xml import DirectoryService as XMLDirectoryService
 from zope.interface import implementer
-from twisted.cred.credentials import UsernamePassword
 
 log = Logger()
 
@@ -63,15 +70,21 @@
 
     def recordToDict(self, record):
         """
-        This to be replaced by something awesome
+        Turn a record in a dictionary of fields which can be reconstituted
+        within the client
         """
         fields = {}
         if record is not None:
             for field, value in record.fields.iteritems():
-                # print("%s: %s" % (field.name, value))
-                valueType = self._directory.fieldName.valueType(field)
-                if valueType is unicode:
+                valueType = record.service.fieldName.valueType(field)
+                # print("%s: %s (%s)" % (field.name, value, valueType))
+                if valueType in (unicode, bool):
                     fields[field.name] = value
+                elif valueType is uuid.UUID:
+                    fields[field.name] = str(value)
+                elif issubclass(valueType, (Names, NamedConstant)):
+                    fields[field.name] = value.name if value else None
+        # print("Server side fields", fields)
         return fields
 
 
@@ -82,7 +95,7 @@
         shortName = shortName.decode("utf-8")
         log.debug("RecordWithShortName: {r} {n}", r=recordType, n=shortName)
         record = (yield self._directory.recordWithShortName(
-            RecordType.lookupByName(recordType), shortName)
+            self._directory.recordType.lookupByName(recordType), shortName)
         )
         fields = self.recordToDict(record)
         response = {
@@ -158,6 +171,50 @@
         returnValue(response)
 
 
+
+    @MembersCommand.responder
+    @inlineCallbacks
+    def members(self, uid):
+        uid = uid.decode("utf-8")
+        log.debug("Members: {u}", u=uid)
+        try:
+            record = (yield self._directory.recordWithUID(uid))
+        except Exception as e:
+            log.error("Failed in members", error=e)
+            record = None
+
+        fieldsList = []
+        if record is not None:
+            for member in (yield record.members()):
+                fieldsList.append(self.recordToDict(member))
+        response = {
+            "fieldsList": pickle.dumps(fieldsList),
+        }
+        log.debug("Responding with: {response}", response=response)
+        returnValue(response)
+
+
+    @GroupsCommand.responder
+    @inlineCallbacks
+    def groups(self, uid):
+        uid = uid.decode("utf-8")
+        log.debug("Groups: {u}", u=uid)
+        try:
+            record = (yield self._directory.recordWithUID(uid))
+        except Exception as e:
+            log.error("Failed in groups", error=e)
+            record = None
+
+        fieldsList = []
+        for group in (yield record.groups()):
+            fieldsList.append(self.recordToDict(group))
+        response = {
+            "fieldsList": pickle.dumps(fieldsList),
+        }
+        log.debug("Responding with: {response}", response=response)
+        returnValue(response)
+
+
     @VerifyPlaintextPasswordCommand.responder
     @inlineCallbacks
     def verifyPlaintextPassword(self, uid, password):
@@ -322,7 +379,7 @@
     options = DirectoryProxyOptions
 
 
-    def makeService(self, options):
+    def makeService(self, options, store=None):
         """
         Return a service
         """
@@ -337,9 +394,14 @@
         args = config.DirectoryProxy.Arguments
         kwds = config.DirectoryProxy.Keywords
 
+        # FIXME: this needs to talk to its own separate database
+        if store is None:
+            pool, txnFactory = getDBPool(config)
+            store = storeFromConfig(config, txnFactory)
+
         if directoryType == "OD":
             from twext.who.opendirectory import DirectoryService as ODDirectoryService
-            directory = ODDirectoryService(*args, **kwds)
+            primaryDirectory = ODDirectoryService(*args, **kwds)
 
         elif directoryType == "LDAP":
             authDN = kwds.pop("authDN", "")
@@ -350,14 +412,16 @@
                 creds = None
             kwds["credentials"] = creds
             debug = kwds.pop("debug", "")
-            directory = LDAPDirectoryService(*args, _debug=debug, **kwds)
+            primaryDirectory = LDAPDirectoryService(
+                *args, _debug=debug, **kwds
+            )
 
         elif directoryType == "XML":
             path = kwds.pop("path", "")
             if not path or not os.path.exists(path):
                 log.error("Path not found for XML directory: {p}", p=path)
             fp = FilePath(path)
-            directory = XMLDirectoryService(fp, *args, **kwds)
+            primaryDirectory = XMLDirectoryService(fp, *args, **kwds)
 
         else:
             log.error("Invalid DirectoryType: {dt}", dt=directoryType)
@@ -365,4 +429,43 @@
         desc = "unix:{path}:mode=660".format(
             path=config.DirectoryProxy.SocketPath
         )
-        return strPortsService(desc, DirectoryProxyAMPFactory(directory))
+        #
+        # Setup the Augment Service
+        #
+        if config.AugmentService.type:
+            augmentClass = namedClass(config.AugmentService.type)
+            log.info(
+                "Configuring augment service of type: {augmentClass}",
+                augmentClass=augmentClass
+            )
+            try:
+                augmentService = augmentClass(**config.AugmentService.params)
+            except IOError:
+                log.error("Could not start augment service")
+                raise
+        else:
+            augmentService = None
+
+        delegateDirectory = DelegateDirectoryService(
+            primaryDirectory.realmName,
+            store
+        )
+
+        aggregateDirectory = AggregateDirectoryService(
+            primaryDirectory.realmName,
+            (primaryDirectory, delegateDirectory)
+        )
+        try:
+            augmented = AugmentedDirectoryService(
+                aggregateDirectory, store, augmentService
+            )
+
+            # The delegate directory needs a way to look up user/group records
+            # so hand it a reference to the augmented directory.
+            # FIXME: is there a better pattern to use here?
+            delegateDirectory.setMasterDirectory(augmented)
+
+        except Exception as e:
+            log.error("Could not create directory service", error=e)
+            raise
+        return strPortsService(desc, DirectoryProxyAMPFactory(augmented))

Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml	2014-03-05 18:24:32 UTC (rev 12819)
@@ -23,6 +23,7 @@
 
   <record type="user">
     <uid>__sagen__</uid>
+    <guid>B3B1158F-0564-4F5B-81E4-A89EA5FF81B0</guid>
     <short-name>sagen</short-name>
     <full-name>Morgen Sagen</full-name>
     <password>negas</password>

Added: CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -0,0 +1,255 @@
+# -*- test-case-name: txdav.who.test.test_augment -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Augmenting Directory Service
+"""
+
+from twext.who.idirectory import IDirectoryService, RecordType
+from twext.who.directory import DirectoryRecord
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.util import ConstantsContainer
+from txdav.who.idirectory import AutoScheduleMode, FieldName
+from txdav.who.idirectory import RecordType as CalRecordType
+from txdav.who.delegates import RecordType as DelegateRecordType
+from twisted.internet.defer import inlineCallbacks, returnValue
+from zope.interface import implementer
+
+from twext.python.log import Logger
+log = Logger()
+
+
+class AugmentedDirectoryRecord(DirectoryRecord):
+
+    def __init__(self, service, baseRecord, augmentedFields):
+        DirectoryRecord.__init__(self, service, augmentedFields)
+        self._baseRecord = baseRecord
+
+
+    @inlineCallbacks
+    def members(self):
+        augmented = []
+        records = yield self._baseRecord.members()
+        for record in records:
+            augmented.append((yield self.service.augment(record)))
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def groups(self):
+        augmented = []
+        txn = self.service._store.newTransaction()
+        groupUIDs = yield txn.groupsFor(self.uid)
+        print("XYZZY GROU UIDS", groupUIDs)
+        for groupUID in groupUIDs:
+            groupRecord = yield self.service.recordWithShortName(
+                RecordType.group, groupUID
+            )
+            if groupRecord:
+                augmented.append((yield self.service.augment(groupRecord)))
+        returnValue(augmented)
+
+
+ at implementer(IDirectoryService)
+class AugmentedDirectoryService(BaseDirectoryService):
+
+    fieldName = ConstantsContainer((
+        BaseDirectoryService.fieldName,
+        FieldName,
+    ))
+
+    recordType = ConstantsContainer((
+        RecordType.user,
+        RecordType.group,
+        CalRecordType.location,
+        CalRecordType.resource,
+        CalRecordType.address,
+        DelegateRecordType.readDelegateGroup,
+        DelegateRecordType.writeDelegateGroup,
+        DelegateRecordType.readDelegatorGroup,
+        DelegateRecordType.writeDelegatorGroup,
+    ))
+
+
+    def __init__(self, directory, store, augmentDB):
+        BaseDirectoryService.__init__(self, directory.realmName)
+        self._directory = directory
+        self._store = store
+        self._augmentDB = augmentDB
+
+
+    def recordTypes(self):
+        return self._directory.recordTypes()
+
+
+    @inlineCallbacks
+    def recordsFromExpression(self, expression):
+        records = yield self._directory.recordsFromExpression(expression)
+        augmented = []
+        for record in records:
+            record = yield self.augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordsWithFieldValue(self, fieldName, value):
+        records = yield self._directory.recordsWithFieldValue(
+            fieldName, value
+        )
+        augmented = []
+        for record in records:
+            record = yield self.augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordWithUID(self, uid):
+        record = yield self._directory.recordWithUID(uid)
+        record = yield self.augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordWithGUID(self, guid):
+        record = yield self._directory.recordWithGUID(guid)
+        record = yield self.augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordsWithRecordType(self, recordType):
+        records = yield self._directory.recordsWithRecordType(recordType)
+        augmented = []
+        for record in records:
+            record = yield self.augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def recordWithShortName(self, recordType, shortName):
+        record = yield self._directory.recordWithShortName(recordType, shortName)
+        record = yield self.augment(record)
+        returnValue(record)
+
+
+    @inlineCallbacks
+    def recordsWithEmailAddress(self, emailAddress):
+        records = yield self._directory.recordsWithEmailAddress(emailAddress)
+        augmented = []
+        for record in records:
+            record = yield self.augment(record)
+            augmented.append(record)
+        returnValue(augmented)
+
+
+    @inlineCallbacks
+    def updateRecords(self, records, create=False):
+        return self._directory.updateRecords(records, create=create)
+
+
+    @inlineCallbacks
+    def removeRecords(self, uids):
+        return self._directory.removeRecords(uids)
+
+
+    def assignToField(self, fields, name, value):
+        field = self.fieldName.lookupByName(name)
+        fields[field] = value
+
+
+    @inlineCallbacks
+    def augment(self, record):
+        if record is None:
+            returnValue(None)
+
+        # MOVE2WHO
+        # FIXME: hacked by appending an "s" -- need a mapping
+        try:
+            augmentRecord = yield self._augmentDB.getAugmentRecord(
+                record.uid,
+                record.recordType.name + "s"
+            )
+        except KeyError:
+            # Augments does not know about this record type, so return
+            # the original record
+            returnValue(record)
+
+        fields = record.fields.copy()
+
+        # print("Got augment record", augmentRecord)
+
+        if augmentRecord:
+            # record.enabled = augmentRecord.enabled
+            # record.serverID = augmentRecord.serverID
+            self.assignToField(
+                fields, "hasCalendars",
+                augmentRecord.enabledForCalendaring
+            )
+            self.assignToField(
+                fields, "hasContacts",
+                augmentRecord.enabledForAddressBooks
+            )
+            autoScheduleMode = {
+                "none": AutoScheduleMode.none,
+                "accept-always": AutoScheduleMode.accept,
+                "decline-always": AutoScheduleMode.decline,
+                "accept-if-free": AutoScheduleMode.acceptIfFree,
+                "decline-if-busy": AutoScheduleMode.declineIfBusy,
+                "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+            }.get(augmentRecord.autoScheduleMode, None)
+            self.assignToField(
+                fields, "autoScheduleMode",
+                autoScheduleMode
+            )
+            self.assignToField(
+                fields, "autoAcceptGroup",
+                unicode(augmentRecord.autoAcceptGroup)
+            )
+            self.assignToField(
+                fields, "loginAllowed",
+                augmentRecord.enabledForLogin
+            )
+
+            if (
+                (
+                    fields.get(self.fieldName.lookupByName("hasCalendars"), False) or
+                    fields.get(self.fieldName.lookupByName("hasContacts"), False)
+                ) and record.recordType == RecordType.group
+            ):
+                self.assignToField(fields, "hasCalendars", False)
+                self.assignToField(fields, "hasContacts", False)
+
+                # For augment records cloned from the Default augment record,
+                # don't emit this message:
+                if not augmentRecord.clonedFromDefault:
+                    log.error("Group '%s(%s)' cannot be enabled for calendaring or address books" % (record.guid, record.shortNames[0],))
+
+        else:
+            # Groups are by default always enabled
+            # record.enabled = (record.recordType == record.service.recordType_groups)
+            # record.serverID = ""
+            self.assignToField(fields, "hasCalendars", False)
+            self.assignToField(fields, "hasContacts", False)
+            self.assignToField(fields, "loginAllowed", False)
+
+        # print("Augmented fields", fields)
+
+        # Clone to a new record with the augmented fields
+        returnValue(AugmentedDirectoryRecord(self, record, fields))

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twext.who.test.test_delegates -*-
+# -*- test-case-name: txdav.who.test.test_delegates -*-
 ##
 # Copyright (c) 2013 Apple Inc. All rights reserved.
 #
@@ -19,13 +19,173 @@
 Delegate assignments
 """
 
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twext.who.idirectory import RecordType
+from twext.python.log import Logger
+from twext.who.directory import (
+    DirectoryService as BaseDirectoryService,
+    DirectoryRecord as BaseDirectoryRecord
+)
+from twext.who.expression import MatchExpression, MatchType
+from twext.who.idirectory import RecordType as BaseRecordType, FieldName
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.python.constants import Names, NamedConstant
 
-from twext.python.log import Logger
+
 log = Logger()
 
 
+
+class RecordType(Names):
+    """
+    Constants for read-only delegates and read-write delegate groups
+    """
+
+    readDelegateGroup = NamedConstant()
+    readDelegateGroup.description = u"read-delegate-group"
+
+    writeDelegateGroup = NamedConstant()
+    writeDelegateGroup.description = u"write-delegate-group"
+
+    readDelegatorGroup = NamedConstant()
+    readDelegatorGroup.description = u"read-delegator-group"
+
+    writeDelegatorGroup = NamedConstant()
+    writeDelegatorGroup.description = u"write-delegator-group"
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+
+
+    @inlineCallbacks
+    def members(self, expanded=False):
+        """
+        If this is a readDelegateGroup or writeDelegateGroup, the members
+        will consist of the records who are delegates *of* this record.
+        If this is a readDelegatorGroup or writeDelegatorGroup,
+        the members will consist of the records who have delegated *to*
+        this record.
+        """
+        # Here are some delegate assignments to test with:
+        # txn = self.service._store.newTransaction()
+        # yield txn.addDelegate(u"E415DBA7-40B5-49F5-A7CC-ACC81E4DEC79", u"494E462A-B16A-4A90-B77F-B9019DD73DAA", True)
+        # yield txn.addDelegate(u"__wsanchez__", u"__cdaboo__", True)
+        # yield txn.addDelegate(u"__wsanchez__", u"__dre__", False)
+        # groupID, name, membershipHash = (
+        #     yield txn.groupByUID(u"494E462A-B16A-4A90-B77F-B9019DD73DAA")
+        # )
+        # print("XYZZY", groupID, name, membershipHash)
+        # yield txn.addDelegateGroup(u"E415DBA7-40B5-49F5-A7CC-ACC81E4DEC79", groupID, False)
+        # yield txn.commit()
+        ###
+
+        parentUID, proxyType = self.uid.split("#")
+        txn = self.service._store.newTransaction()
+
+        if self.recordType in (
+            RecordType.readDelegateGroup, RecordType.writeDelegateGroup
+        ):  # Members are delegates of this record
+            readWrite = (self.recordType is RecordType.writeDelegateGroup)
+            delegateUIDs = (
+                yield txn.delegates(parentUID, readWrite, expanded=expanded)
+            )
+
+        else:  # Members have delegated to this record
+            readWrite = (self.recordType is RecordType.writeDelegatorGroup)
+            delegateUIDs = (
+                yield txn.delegators(parentUID, readWrite)
+            )
+
+        records = []
+        for uid in delegateUIDs:
+            if uid != parentUID:
+                record = (yield self.service._masterDirectory.recordWithUID(uid))
+                if record is not None:
+                    records.append(record)
+        yield txn.commit()
+
+        returnValue(records)
+
+
+def recordTypeToProxyType(recordType):
+    return {
+        RecordType.readDelegateGroup: "calendar-proxy-read",
+        RecordType.writeDelegateGroup: "calendar-proxy-write",
+        RecordType.readDelegatorGroup: "calendar-proxy-read-for",
+        RecordType.writeDelegatorGroup: "calendar-proxy-write-for",
+    }.get(recordType, None)
+
+
+def proxyTypeToRecordType(proxyType):
+    return {
+        "calendar-proxy-read": RecordType.readDelegateGroup,
+        "calendar-proxy-write": RecordType.writeDelegateGroup,
+        "calendar-proxy-read-for": RecordType.readDelegatorGroup,
+        "calendar-proxy-write-for": RecordType.writeDelegatorGroup,
+    }.get(proxyType, None)
+
+
+
+class DirectoryService(BaseDirectoryService):
+    """
+    Delegate directory service
+    """
+
+    recordType = RecordType
+
+
+    def __init__(self, realmName, store):
+        BaseDirectoryService.__init__(self, realmName)
+        self._store = store
+        self._masterDirectory = None
+
+
+    def setMasterDirectory(self, masterDirectory):
+        self._masterDirectory = masterDirectory
+
+
+    def recordWithShortName(self, recordType, shortName):
+        uid = shortName + "#" + recordTypeToProxyType(recordType)
+
+        record = DirectoryRecord(self, {
+            FieldName.uid: uid,
+            FieldName.recordType: recordType,
+            FieldName.shortNames: (uid,),
+        })
+        return succeed(record)
+
+
+    def recordWithUID(self, uid):
+        if "#" not in uid:  # Not a delegate group uid
+            return succeed(None)
+        uid, proxyType = uid.split("#")
+        recordType = proxyTypeToRecordType(proxyType)
+        if recordType is None:
+            return succeed(None)
+        return self.recordWithShortName(recordType, uid)
+
+
+    @inlineCallbacks
+    def recordsFromExpression(self, expression, records=None):
+        """
+        It's only ever appropriate to look up delegate group record by
+        shortName or uid.  When wrapped by an aggregate directory, looking up
+        by shortName will already go directly to recordWithShortName.  However
+        when looking up by UID, it won't.  Inspect the expression to see if
+        it's one we can handle.
+        """
+        if isinstance(expression, MatchExpression):
+            if(
+                (expression.fieldName is FieldName.uid) and
+                (expression.matchType is MatchType.equals) and
+                ("#" in expression.fieldValue)
+            ):
+                record = yield self.recordWithUID(expression.fieldValue)
+                if record is not None:
+                    returnValue((record,))
+
+        returnValue(())
+
+
+
 @inlineCallbacks
 def addDelegate(txn, delegator, delegate, readWrite):
     """
@@ -39,12 +199,12 @@
     @param readWrite: if True, read and write access is granted; read-only
         access otherwise
     """
-    if delegate.recordType == RecordType.group:
+    if delegate.recordType == BaseRecordType.group:
         # find the groupID
-        groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
-        yield txn.addDelegateGroup(delegator.guid, groupID, readWrite)
+        groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+        yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
     else:
-        yield txn.addDelegate(delegator.guid, delegate.guid, readWrite)
+        yield txn.addDelegate(delegator.uid, delegate.uid, readWrite)
 
 
 @inlineCallbacks
@@ -60,16 +220,16 @@
     @param readWrite: if True, read and write access is revoked; read-only
         access otherwise
     """
-    if delegate.recordType == RecordType.group:
+    if delegate.recordType == BaseRecordType.group:
         # find the groupID
-        groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
-        yield txn.removeDelegateGroup(delegator.guid, groupID, readWrite)
+        groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+        yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)
     else:
-        yield txn.removeDelegate(delegator.guid, delegate.guid, readWrite)
+        yield txn.removeDelegate(delegator.uid, delegate.uid, readWrite)
 
 
 @inlineCallbacks
-def delegatesOf(txn, delegator, readWrite):
+def delegatesOf(txn, delegator, readWrite, expanded=False):
     """
     Return the records of the delegates of "delegator".  The type of access
     is specified by the "readWrite" parameter.
@@ -83,10 +243,12 @@
     """
     records = []
     directory = delegator.service
-    delegateGUIDs = (yield txn.delegates(delegator.guid, readWrite))
-    for guid in delegateGUIDs:
-        if guid != delegator.guid:
-            record = (yield directory.recordWithGUID(guid))
+    delegateUIDs = (
+        yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
+    )
+    for uid in delegateUIDs:
+        if uid != delegator.uid:
+            record = (yield directory.recordWithUID(uid))
             if record is not None:
                 records.append(record)
     returnValue(records)
@@ -107,10 +269,10 @@
     """
     records = []
     directory = delegate.service
-    delegatorGUIDs = (yield txn.delegators(delegate.guid, readWrite))
-    for guid in delegatorGUIDs:
-        if guid != delegate.guid:
-            record = (yield directory.recordWithGUID(guid))
+    delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
+    for uid in delegatorUIDs:
+        if uid != delegate.uid:
+            record = (yield directory.recordWithUID(uid))
             if record is not None:
                 records.append(record)
     returnValue(records)
@@ -118,7 +280,7 @@
 
 def allGroupDelegates(txn):
     """
-    @return: the GUIDs of all groups which are currently delegated to
-    @rtype: a Deferred which fires with a set() of GUID strings
+    @return: the UIDs of all groups which are currently delegated to
+    @rtype: a Deferred which fires with a set() of UIDs C{unicode}
     """
     return txn.allGroupDelegates()

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twext.who.test.test_groups -*-
+# -*- test-case-name: txdav.who.test.test_groups -*-
 ##
 # Copyright (c) 2013 Apple Inc. All rights reserved.
 #
@@ -68,7 +68,7 @@
 
             # New implmementation
             try:
-                newGroupCacher.update(self.transaction)
+                yield newGroupCacher.update(self.transaction)
             except Exception, e:
                 log.error(
                     "Failed to update new group membership cache ({error})",
@@ -76,13 +76,13 @@
                 )
 
             # Old implmementation
-            try:
-                oldGroupCacher.updateCache()
-            except Exception, e:
-                log.error(
-                    "Failed to update old group membership cache ({error})",
-                    error=e
-                )
+            # try:
+            #     oldGroupCacher.updateCache()
+            # except Exception, e:
+            #     log.error(
+            #         "Failed to update old group membership cache ({error})",
+            #         error=e
+            #     )
 
         else:
             notBefore = (
@@ -126,25 +126,29 @@
 
 class GroupRefreshWork(WorkItem, fromTable(schema.GROUP_REFRESH_WORK)):
 
-    group = property(lambda self: self.groupGUID)
+    # Note, the schema has "groupGuid", but really it's a UID.  At some point
+    # we should change the column name.
+    group = property(lambda self: self.groupGuid)
 
     @inlineCallbacks
     def doWork(self):
-
+        print("XYZZY IN GRW doWork", )
         # Delete all other work items for this group
         yield Delete(
-            From=self.table, Where=(self.table.GROUP_GUID == self.groupGUID)
+            From=self.table, Where=(self.table.GROUP_GUID == self.groupGuid)
         ).on(self.transaction)
 
-        groupCacher = getattr(self.transaction, "_groupCacher", None)
-        if groupCacher is not None:
+        newGroupCacher = getattr(self.transaction, "_newGroupCacher", None)
+        if newGroupCacher is not None:
 
             try:
-                groupCacher.refreshGroup(self.transaction, self.groupGUID)
+                yield newGroupCacher.refreshGroup(
+                    self.transaction, self.groupGuid
+                )
             except Exception, e:
                 log.error(
                     "Failed to refresh group {group} {err}",
-                    group=self.groupGUID, err=e
+                    group=self.groupGuid, err=e
                 )
 
         else:
@@ -154,11 +158,11 @@
             )
             log.debug(
                 "Rescheduling group refresh for {group}: {when}",
-                group=self.groupGUID, when=notBefore
+                group=self.groupGuid, when=notBefore
             )
             yield self.transaction.enqueue(
                 GroupRefreshWork,
-                groupGUID=self.groupGUID, notBefore=notBefore
+                groupGuid=self.groupGuid, notBefore=notBefore
             )
 
 
@@ -182,6 +186,7 @@
             )
         ).on(self.transaction)
 
+    # MOVE2WHO
     # TODO: Pull this over from groupcacher branch
 
 
@@ -201,6 +206,7 @@
         records.add(record)
         for member in (yield record.members()):
             if member not in records:
+                #MOVE2WHO
                 #TODO:  HACK for old-style XML. FIX
                 if (
                     member.recordType != RecordType.group and
@@ -219,14 +225,14 @@
     of two lists -- one for added/updated assignments, and one for removed
     assignments.
 
-    @param old: dictionary of delegator: (readGroupGUID, writeGroupGUID)
+    @param old: dictionary of delegator: (readGroupUID, writeGroupUID)
     @type old: C{dict}
 
-    @param new: dictionary of delegator: (readGroupGUID, writeGroupGUID)
+    @param new: dictionary of delegator: (readGroupUID, writeGroupUID)
     @type new: C{dict}
 
     @return: Tuple of two lists; the first list contains tuples of (delegator,
-        (readGroupGUID, writeGroupGUID)), and represents all the new or updated
+        (readGroupUID, writeGroupUID)), and represents all the new or updated
         assignments.  The second list contains all the delegators which used to
         have a delegate but don't anymore.
     """
@@ -269,19 +275,21 @@
         # yield self.applyExternalAssignments(txn, externalAssignments)
 
         # Figure out which groups matter
-        groupGUIDs = yield self.groupsToRefresh(txn)
+        groupUIDs = yield self.groupsToRefresh(txn)
         self.log.debug(
-            "Number of groups to refresh: {num}", num=len(groupGUIDs)
+            "Number of groups to refresh: {num}", num=len(groupUIDs)
         )
         # For each of those groups, create a per-group refresh work item
-        for groupGUID in groupGUIDs:
+        for groupUID in groupUIDs:
             notBefore = (
                 datetime.datetime.utcnow() +
                 datetime.timedelta(seconds=1)
             )
+            self.log.debug("Enqueuing group refresh for {u}", u=groupUID)
             yield txn.enqueue(
-                GroupRefreshWork, groupGUID=groupGUID, notBefore=notBefore
+                GroupRefreshWork, groupGuid=groupUID, notBefore=notBefore
             )
+            self.log.debug("Enqueued group refresh for {u}", u=groupUID)
 
 
     @inlineCallbacks
@@ -290,84 +298,91 @@
         oldAssignments = (yield txn.externalDelegates())
 
         # external assignments is of the form:
-        # { delegatorGUID: (readDelegateGroupGUID, writeDelegateGroupGUID),
+        # { delegatorUID: (readDelegateGroupUID, writeDelegateGroupUID),
         # }
 
         changed, removed = diffAssignments(oldAssignments, newAssignments)
         if changed:
             for (
-                delegatorGUID, (readDelegateGUID, writeDelegateGUID)
+                delegatorUID, (readDelegateUID, writeDelegateUID)
             ) in changed:
                 readDelegateGroupID = writeDelegateGroupID = None
-                if readDelegateGUID:
+                if readDelegateUID:
                     readDelegateGroupID, _ignore_name, hash = (
-                        yield txn.groupByGUID(readDelegateGUID)
+                        yield txn.groupByUID(readDelegateUID)
                     )
-                if writeDelegateGUID:
+                if writeDelegateUID:
                     writeDelegateGroupID, _ignore_name, hash = (
-                        yield txn.groupByGUID(writeDelegateGUID)
+                        yield txn.groupByUID(writeDelegateUID)
                     )
                 yield txn.assignExternalDelegates(
-                    delegatorGUID, readDelegateGroupID, writeDelegateGroupID,
-                    readDelegateGUID, writeDelegateGUID
+                    delegatorUID, readDelegateGroupID, writeDelegateGroupID,
+                    readDelegateUID, writeDelegateUID
                 )
         if removed:
-            for delegatorGUID in removed:
+            for delegatorUID in removed:
                 yield txn.assignExternalDelegates(
-                    delegatorGUID, None, None, None, None
+                    delegatorUID, None, None, None, None
                 )
 
 
     @inlineCallbacks
-    def refreshGroup(self, txn, groupGUID):
+    def refreshGroup(self, txn, groupUID):
         # Does the work of a per-group refresh work item
-        # Faults in the flattened membership of a group, as GUIDs
+        # Faults in the flattened membership of a group, as UIDs
         # and updates the GROUP_MEMBERSHIP table
-        record = (yield self.directory.recordWithGUID(groupGUID))
-        membershipHashContent = hashlib.md5()
-        members = (yield expandedMembers(record))
-        members = list(members)
-        members.sort(cmp=lambda x, y: cmp(x.guid, y.guid))
-        for member in members:
-            membershipHashContent.update(str(member.guid))
-        membershipHash = membershipHashContent.hexdigest()
-        groupID, _ignore_cachedName, cachedMembershipHash = (
-            yield txn.groupByGUID(groupGUID)
-        )
-
-        if cachedMembershipHash != membershipHash:
-            membershipChanged = True
-            self.log.debug(
-                "Group '{group}' changed", group=record.fullNames[0]
+        self.log.debug("Faulting in group: {g}", g=groupUID)
+        record = (yield self.directory.recordWithUID(groupUID))
+        if record is None:
+            # FIXME: the group has disappeared from the directory.
+            # How do we want to handle this?
+            self.log.info("Group has disappeared: {g}", g=groupUID)
+        else:
+            self.log.debug("Got group record: {u}", u=record.uid)
+            membershipHashContent = hashlib.md5()
+            members = (yield expandedMembers(record))
+            members = list(members)
+            members.sort(cmp=lambda x, y: cmp(x.uid, y.uid))
+            for member in members:
+                membershipHashContent.update(str(member.uid))
+            membershipHash = membershipHashContent.hexdigest()
+            groupID, _ignore_cachedName, cachedMembershipHash = (
+                yield txn.groupByUID(groupUID)
             )
-        else:
-            membershipChanged = False
 
-        yield txn.updateGroup(groupGUID, record.fullNames[0], membershipHash)
+            if cachedMembershipHash != membershipHash:
+                membershipChanged = True
+                self.log.debug(
+                    "Group '{group}' changed", group=record.fullNames[0]
+                )
+            else:
+                membershipChanged = False
 
-        if membershipChanged:
-            newMemberGUIDs = set()
-            for member in members:
-                newMemberGUIDs.add(member.guid)
-            yield self.synchronizeMembers(txn, groupID, newMemberGUIDs)
+            yield txn.updateGroup(groupUID, record.fullNames[0], membershipHash)
 
-        yield self.scheduleEventReconciliations(txn, groupID, groupGUID)
+            if membershipChanged:
+                newMemberUIDs = set()
+                for member in members:
+                    newMemberUIDs.add(member.uid)
+                yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
 
+            yield self.scheduleEventReconciliations(txn, groupID, groupUID)
 
+
     @inlineCallbacks
-    def synchronizeMembers(self, txn, groupID, newMemberGUIDs):
+    def synchronizeMembers(self, txn, groupID, newMemberUIDs):
         numRemoved = numAdded = 0
-        cachedMemberGUIDs = (yield txn.membersOfGroup(groupID))
+        cachedMemberUIDs = (yield txn.membersOfGroup(groupID))
 
-        for memberGUID in cachedMemberGUIDs:
-            if memberGUID not in newMemberGUIDs:
+        for memberUID in cachedMemberUIDs:
+            if memberUID not in newMemberUIDs:
                 numRemoved += 1
-                yield txn.removeMemberFromGroup(memberGUID, groupID)
+                yield txn.removeMemberFromGroup(memberUID, groupID)
 
-        for memberGUID in newMemberGUIDs:
-            if memberGUID not in cachedMemberGUIDs:
+        for memberUID in newMemberUIDs:
+            if memberUID not in cachedMemberUIDs:
                 numAdded += 1
-                yield txn.addMemberToGroup(memberGUID, groupID)
+                yield txn.addMemberToGroup(memberUID, groupID)
 
         returnValue((numAdded, numRemoved))
 
@@ -378,23 +393,23 @@
         The members of the given group as recorded in the db
         """
         members = set()
-        memberGUIDs = (yield txn.membersOfGroup(groupID))
-        for guid in memberGUIDs:
-            record = (yield self.directory.recordWithGUID(guid))
+        memberUIDs = (yield txn.membersOfGroup(groupID))
+        for uid in memberUIDs:
+            record = (yield self.directory.recordWithUID(uid))
             if record is not None:
                 members.add(record)
         returnValue(members)
 
 
-    def cachedGroupsFor(self, txn, guid):
+    def cachedGroupsFor(self, txn, uid):
         """
-        The IDs of the groups the guid is a member of
+        The UIDs of the groups the uid is a member of
         """
-        return txn.groupsFor(guid)
+        return txn.groupsFor(uid)
 
 
     @inlineCallbacks
-    def scheduleEventReconciliations(self, txn, groupID, groupGUID):
+    def scheduleEventReconciliations(self, txn, groupID, groupUID):
         """
         Find all events who have this groupID as an attendee and create
         work items for them.
@@ -415,29 +430,29 @@
             )
             log.debug(
                 "scheduling group reconciliation for "
-                "({eventID}, {groupID}, {groupGUID}): {when}",
+                "({eventID}, {groupID}, {groupUID}): {when}",
                 eventID=eventID,
                 groupID=groupID,
-                groupGUID=groupGUID,
+                groupUID=groupUID,
                 when=notBefore)
 
             yield txn.enqueue(
                 GroupAttendeeReconciliationWork,
                 eventID=eventID,
                 groupID=groupID,
-                groupGUID=groupGUID,
+                groupGuid=groupUID,
                 notBefore=notBefore
             )
 
 
     @inlineCallbacks
     def groupsToRefresh(self, txn):
-        delegatedGUIDs = set((yield allGroupDelegates(txn)))
+        delegatedUIDs = set((yield allGroupDelegates(txn)))
         self.log.info(
-            "There are {count} group delegates", count=len(delegatedGUIDs)
+            "There are {count} group delegates", count=len(delegatedUIDs)
         )
 
-        attendeeGroupGUIDs = set()
+        attendeeGroupUIDs = set()
 
         # get all groups from events
         groupAttendee = schema.GROUP_ATTENDEE
@@ -447,7 +462,7 @@
         ).on(txn)
         groupIDs = set([row[0] for row in rows])
 
-        # get groupGUIDs
+        # get groupUIDs
         if groupIDs:
             gr = schema.GROUPS
             rows = yield Select(
@@ -455,6 +470,6 @@
                 From=gr,
                 Where=gr.GROUP_ID.In(groupIDs)
             ).on(txn)
-            attendeeGroupGUIDs = set([row[0] for row in rows])
+            attendeeGroupUIDs = set([row[0] for row in rows])
 
-        returnValue(delegatedGUIDs.union(attendeeGroupGUIDs))
+        returnValue(delegatedUIDs.union(attendeeGroupUIDs))

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml	2014-03-05 18:24:32 UTC (rev 12819)
@@ -43,6 +43,7 @@
     <name>Wilfredo Sanchez Vega</name>
     <first-name>Wilfredo</first-name>
     <last-name>Sanchez Vega</last-name>
+    <auto-schedule-mode><accept-if-free-decline-if-busy /></auto-schedule-mode>
   </user>
   <user>
     <uid>cdaboo</uid>

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -92,15 +92,21 @@
         # Add group delegate, but before the group membership has been
         # pulled in
         yield addDelegate(txn, delegator, group1, True)
-        delegates = (yield delegatesOf(txn, delegator, True))
+        # Passing expanded=False will return the group
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=False))
+        self.assertEquals(1, len(delegates))
+        self.assertEquals(delegates[0].uid, u"__top_group_1__")
+        # Passing expanded=True will return not the group -- it only returns
+        # non-groups
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(0, len(delegates))
 
         # Now refresh the group and there will be 3 delegates (contained
         # within 2 nested groups)
         # guid = "49b350c69611477b94d95516b13856ab"
-        yield self.groupCacher.refreshGroup(txn, group1.guid)
-        yield self.groupCacher.refreshGroup(txn, group2.guid)
-        delegates = (yield delegatesOf(txn, delegator, True))
+        yield self.groupCacher.refreshGroup(txn, group1.uid)
+        yield self.groupCacher.refreshGroup(txn, group2.uid)
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(
             set(["sagen", "cdaboo", "glyph"]),
             set([d.shortNames[0] for d in delegates])
@@ -112,14 +118,12 @@
         yield addDelegate(txn, delegator, group2, True)
         groups = (yield allGroupDelegates(txn))
         self.assertEquals(
-            set([
-                UUID("49b350c69611477b94d95516b13856ab"),
-                UUID("86144f73345a409782f1b782672087c7")
-                ]), set(groups))
+            set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
+        )
 
         # Delegate to a user who is already indirectly delegated-to
         yield addDelegate(txn, delegator, delegate1, True)
-        delegates = (yield delegatesOf(txn, delegator, True))
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(
             set(["sagen", "cdaboo", "glyph"]),
             set([d.shortNames[0] for d in delegates])
@@ -131,12 +135,12 @@
             record = (
                 yield self.xmlService.recordWithShortName(RecordType.user, name)
             )
-            newSet.add(record.guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(group1.guid))
+            newSet.add(record.uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(group1.uid))
         numAdded, numRemoved = (
             yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
         )
-        delegates = (yield delegatesOf(txn, delegator, True))
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(
             set(["sagen", "cdaboo", "glyph", "dre"]),
             set([d.shortNames[0] for d in delegates])
@@ -144,7 +148,7 @@
 
         # Remove delegate access from the top group
         yield removeDelegate(txn, delegator, group1, True)
-        delegates = (yield delegatesOf(txn, delegator, True))
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(
             set(["sagen", "cdaboo"]),
             set([d.shortNames[0] for d in delegates])
@@ -152,7 +156,7 @@
 
         # Remove delegate access from the sub group
         yield removeDelegate(txn, delegator, group2, True)
-        delegates = (yield delegatesOf(txn, delegator, True))
+        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
         self.assertEquals(
             set(["sagen"]),
             set([d.shortNames[0] for d in delegates])

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -24,7 +24,6 @@
 from twisted.internet.defer import inlineCallbacks
 from twistedcaldav.test.util import StoreTestCase
 from txdav.common.icommondatastore import NotFoundError
-from uuid import UUID
 
 
 class GroupCacherTest(StoreTestCase):
@@ -69,22 +68,20 @@
         txn = store.newTransaction()
 
         record = yield self.xmlService.recordWithUID(u"__top_group_1__")
-        yield self.groupCacher.refreshGroup(txn, record.guid)
+        yield self.groupCacher.refreshGroup(txn, record.uid)
 
-        groupID, name, membershipHash = (yield txn.groupByGUID(record.guid))
-        self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
+        groupID, name, membershipHash = (yield txn.groupByUID(record.uid))
 
-        groupGUID, name, membershipHash = (yield txn.groupByID(groupID))
-        self.assertEquals(groupGUID, record.guid)
-        self.assertEquals(name, "Top Group 1")
-        self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
+        self.assertEquals(membershipHash, "f380860ff5e02c2433fbd4b5ed3e090c")
 
+        groupUID, name, membershipHash = (yield txn.groupByID(groupID))
+        self.assertEquals(groupUID, record.uid)
+        self.assertEquals(name, u"Top Group 1")
+        self.assertEquals(membershipHash, "f380860ff5e02c2433fbd4b5ed3e090c")
+
         members = (yield txn.membersOfGroup(groupID))
         self.assertEquals(
-            set([UUID("9064df911dbc4e079c2b6839b0953876"),
-                 UUID("4ad155cbae9b475f986ce08a7537893e"),
-                 UUID("3bdcb95484d54f6d8035eac19a6d6e1f"),
-                 UUID("7d45cb10479e456bb54d528958c5734b")]),
+            set([u'__cdaboo__', u'__glyph__', u'__sagen__', u'__wsanchez__']),
             members
         )
 
@@ -97,8 +94,8 @@
         # sagen is in the top group, even though it's actually one level
         # removed
         record = yield self.xmlService.recordWithUID(u"__sagen__")
-        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.guid))
-        self.assertEquals(set([groupID]), groups)
+        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
+        self.assertEquals(set([u"__top_group_1__"]), groups)
 
 
     @inlineCallbacks
@@ -113,9 +110,9 @@
         txn = store.newTransaction()
 
         # Refresh the group so it's assigned a group_id
-        guid = UUID("49b350c69611477b94d95516b13856ab")
-        yield self.groupCacher.refreshGroup(txn, guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(guid))
+        uid = u"__top_group_1__"
+        yield self.groupCacher.refreshGroup(txn, uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(uid))
 
         # Remove two members, and add one member
         newSet = set()
@@ -126,7 +123,7 @@
                     name
                 )
             )
-            newSet.add(record.guid)
+            newSet.add(record.uid)
         numAdded, numRemoved = (
             yield self.groupCacher.synchronizeMembers(
                 txn, groupID, newSet
@@ -159,12 +156,12 @@
         # Non-existent groupID
         self.failUnlessFailure(txn.groupByID(42), NotFoundError)
 
-        guid = UUID("49b350c69611477b94d95516b13856ab")
-        hash = "4b0e162f2937f0f3daa6d10e5a6a6c33"
-        yield self.groupCacher.refreshGroup(txn, guid)
-        groupID, name, membershipHash = (yield txn.groupByGUID(guid))
+        uid = u"__top_group_1__"
+        hash = "f380860ff5e02c2433fbd4b5ed3e090c"
+        yield self.groupCacher.refreshGroup(txn, uid)
+        groupID, name, membershipHash = (yield txn.groupByUID(uid))
         results = (yield txn.groupByID(groupID))
-        self.assertEquals([guid, "Top Group 1", hash], results)
+        self.assertEquals((uid, u"Top Group 1", hash), results)
 
 
     @inlineCallbacks
@@ -177,32 +174,31 @@
         self.assertEquals(oldExternalAssignments, {})
 
         newAssignments = {
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
-            (None, UUID("49B350C6-9611-477B-94D9-5516B13856AB"))
+            u"__wsanchez__": (None, u"__top_group_1__")
         }
         yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
         oldExternalAssignments = (yield txn.externalDelegates())
         self.assertEquals(
             oldExternalAssignments,
             {
-                UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+                u"__wsanchez__":
                 (
                     None,
-                    UUID("49B350C6-9611-477B-94D9-5516B13856AB")
+                    u"__top_group_1__"
                 )
             }
         )
 
         newAssignments = {
-            UUID("7D45CB10-479E-456B-B54D-528958C5734B"):
+            u"__cdaboo__":
             (
-                UUID("86144F73-345A-4097-82F1-B782672087C7"),
+                u"__sub_group_1__",
                 None
             ),
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+            u"__wsanchez__":
             (
-                UUID("86144F73-345A-4097-82F1-B782672087C7"),
-                UUID("49B350C6-9611-477B-94D9-5516B13856AB")
+                u"__sub_group_1__",
+                u"__top_group_1__"
             ),
         }
         yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
@@ -210,14 +206,14 @@
         self.assertEquals(
             oldExternalAssignments,
             {
-                UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
+                u"__wsanchez__":
                 (
-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
-                    UUID('49b350c6-9611-477b-94d9-5516b13856ab')
+                    u"__sub_group_1__",
+                    u"__top_group_1__"
                 ),
-                UUID('7d45cb10-479e-456b-b54d-528958c5734b'):
+                u"__cdaboo__":
                 (
-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
+                    u"__sub_group_1__",
                     None
                 )
             }
@@ -228,44 +224,44 @@
             allGroupDelegates,
             set(
                 [
-                    UUID('49b350c6-9611-477b-94d9-5516b13856ab'),
-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
+                    u"__top_group_1__",
+                    u"__sub_group_1__"
                 ]
             )
         )
 
         # Fault in the read-only group
-        yield self.groupCacher.refreshGroup(txn, UUID('86144f73-345a-4097-82f1-b782672087c7'))
+        yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")
 
         # Wilfredo should have Sagen and Daboo as read-only delegates
         delegates = (yield txn.delegates(
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
+            u"__wsanchez__", False, expanded=True)
         )
         self.assertEquals(
             delegates,
             set(
                 [
-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b')
+                    u"__sagen__",
+                    u"__cdaboo__"
                 ]
             )
         )
 
         # Fault in the read-write group
-        yield self.groupCacher.refreshGroup(txn, UUID('49b350c6-9611-477b-94d9-5516b13856ab'))
+        yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")
 
         # Wilfredo should have 4 users as read-write delegates
         delegates = (yield txn.delegates(
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
+            u"__wsanchez__", True, expanded=True)
         )
         self.assertEquals(
             delegates,
             set(
                 [
-                    UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'),
-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b'),
-                    UUID('9064df91-1dbc-4e07-9c2b-6839b0953876')
+                    u"__wsanchez__",
+                    u"__sagen__",
+                    u"__cdaboo__",
+                    u"__glyph__"
                 ]
             )
         )
@@ -275,9 +271,9 @@
         # Now, remove some external assignments
         #
         newAssignments = {
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+            u"__wsanchez__":
             (
-                UUID("86144F73-345A-4097-82F1-B782672087C7"),
+                u"__sub_group_1__",
                 None
             ),
         }
@@ -286,9 +282,9 @@
         self.assertEquals(
             oldExternalAssignments,
             {
-                UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
+                u"__wsanchez__":
                 (
-                    UUID('86144f73-345a-4097-82f1-b782672087c7'),
+                    u"__sub_group_1__",
                     None
                 ),
             }
@@ -299,28 +295,28 @@
             allGroupDelegates,
             set(
                 [
-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
+                    u"__sub_group_1__"
                 ]
             )
         )
 
         # Wilfredo should have Sagen and Daboo as read-only delegates
         delegates = (yield txn.delegates(
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
+            u"__wsanchez__", False, expanded=True)
         )
         self.assertEquals(
             delegates,
             set(
                 [
-                    UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
-                    UUID('7d45cb10-479e-456b-b54d-528958c5734b')
+                    u"__sagen__",
+                    u"__cdaboo__"
                 ]
             )
         )
 
         # Wilfredo should have no read-write delegates
         delegates = (yield txn.delegates(
-            UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
+            u"__wsanchez__", True, expanded=True)
         )
         self.assertEquals(
             delegates,
@@ -333,7 +329,7 @@
             allGroupDelegates,
             set(
                 [
-                    UUID('86144f73-345a-4097-82f1-b782672087c7')
+                    u"__sub_group_1__"
                 ]
             )
         )
@@ -424,7 +420,6 @@
 
   <record type="user">
     <uid>__wsanchez__</uid>
-    <guid>3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F</guid>
     <short-name>wsanchez</short-name>
     <short-name>wilfredo_sanchez</short-name>
     <full-name>Wilfredo Sanchez</full-name>
@@ -435,7 +430,6 @@
 
   <record type="user">
     <uid>__glyph__</uid>
-    <guid>9064DF91-1DBC-4E07-9C2B-6839B0953876</guid>
     <short-name>glyph</short-name>
     <full-name>Glyph Lefkowitz</full-name>
     <password>hpylg</password>
@@ -445,7 +439,6 @@
 
   <record type="user">
     <uid>__sagen__</uid>
-    <guid>4AD155CB-AE9B-475F-986C-E08A7537893E</guid>
     <short-name>sagen</short-name>
     <full-name>Morgen Sagen</full-name>
     <password>negas</password>
@@ -455,7 +448,6 @@
 
   <record type="user">
     <uid>__cdaboo__</uid>
-    <guid>7D45CB10-479E-456B-B54D-528958C5734B</guid>
     <short-name>cdaboo</short-name>
     <full-name>Cyrus Daboo</full-name>
     <password>suryc</password>
@@ -464,7 +456,6 @@
 
   <record type="user">
     <uid>__dre__</uid>
-    <guid>CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B</guid>
     <short-name>dre</short-name>
     <full-name>Andre LaBranche</full-name>
     <password>erd</password>
@@ -474,7 +465,6 @@
 
   <record type="group">
     <uid>__top_group_1__</uid>
-    <guid>49B350C6-9611-477B-94D9-5516B13856AB</guid>
     <short-name>top-group-1</short-name>
     <full-name>Top Group 1</full-name>
     <email>topgroup1 at example.com</email>
@@ -485,7 +475,6 @@
 
   <record type="group">
     <uid>__sub_group_1__</uid>
-    <guid>86144F73-345A-4097-82F1-B782672087C7</guid>
     <short-name>sub-group-1</short-name>
     <full-name>Sub Group 1</full-name>
     <email>subgroup1 at example.com</email>

Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -143,10 +143,14 @@
         (BaseDirectoryService.recordType, RecordType)
     )
 
-    fieldName = ConstantsContainer(
-        (BaseDirectoryService.fieldName, FieldName)
-    )
+    # MOVE2WHO: Wilfredo had added augment fields into xml, which does make
+    # some sense, but for backwards compatibility right now I will take those
+    # out, and rely on a separate augment service
 
+    # fieldName = ConstantsContainer(
+    #     (BaseDirectoryService.fieldName, FieldName)
+    # )
+
     # XML schema constants
 
     element = ConstantsContainer(

Modified: CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py	2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py	2014-03-05 18:24:32 UTC (rev 12819)
@@ -997,6 +997,7 @@
             if the authentication scheme is unsupported, or the
             credentials provided by the request are not valid.
         """
+
         # Bypass normal authentication if its already been done (by SACL check)
         if (
             hasattr(request, "authnUser") and
@@ -1134,7 +1135,7 @@
         # The default behaviour is no ACL; we should inherit from the parent
         # collection.
         #
-        return element.ACL()
+        return succeed(element.ACL())
 
 
     def setAccessControlList(self, acl):
@@ -1360,6 +1361,7 @@
         @return: a L{Deferred} that callbacks with C{None} or errbacks
             with an L{AccessDeniedError}
         """
+
         if principal is None:
             principal = self.currentPrincipal(request)
 
@@ -1509,7 +1511,7 @@
                 # If we get to the root without any ACLs, then use the default.
                 acl = self.defaultRootAccessControlList()
             else:
-                acl = self.defaultAccessControlList()
+                acl = yield self.defaultAccessControlList()
 
         # Dynamically update privileges for those ace's that are inherited.
         if inheritance:
@@ -1618,6 +1620,7 @@
         return []
 
 
+    @inlineCallbacks
     def principalsForAuthID(self, request, authid):
         """
         Return authentication and authorization principal identifiers
@@ -1637,16 +1640,16 @@
             HTTPError(responsecode.FORBIDDEN) if the principal isn't
             found.
         """
-        authnPrincipal = self.findPrincipalForAuthID(authid)
+        authnPrincipal = yield self.findPrincipalForAuthID(authid)
 
         if authnPrincipal is None:
-            return succeed((None, None))
+            returnValue((None, None))
 
-        d = self.authorizationPrincipal(request, authid, authnPrincipal)
-        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
-        return d
+        authzPrincipal = yield self.authorizationPrincipal(request, authid, authnPrincipal)
+        returnValue((authnPrincipal, authzPrincipal))
 
 
+    @inlineCallbacks
     def findPrincipalForAuthID(self, authid):
         """
         Return authentication and authorization principal identifiers
@@ -1662,10 +1665,10 @@
             found return None.
         """
         for collection in self.principalCollections():
-            principal = collection.principalForUser(authid)
+            principal = yield collection.principalForUser(authid)
             if principal is not None:
-                return principal
-        return None
+                returnValue(principal)
+        returnValue(None)
 
 
     def authorizationPrincipal(self, request, authid, authnPrincipal):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140305/cf0f47a9/attachment-0001.html>


More information about the calendarserver-changes mailing list