[CalendarServer-changes] [12897] CalendarServer/branches/users/sagen/move2who-2

source_changes at macosforge.org source_changes at macosforge.org
Thu Mar 13 13:33:25 PDT 2014


Revision: 12897
          http://trac.calendarserver.org//changeset/12897
Author:   sagen at apple.com
Date:     2014-03-13 13:33:25 -0700 (Thu, 13 Mar 2014)
Log Message:
-----------
Move directoryFromConfig to its own module.

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py
    CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/test/test_util.py
    CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/test/test_principals.py
    CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/caldav/datastore/test/test_attachments.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/dps/server.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/accounts.xml
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/resources.xml

Added Paths:
-----------
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/augments.xml
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/test_util.py
    CalendarServer/branches/users/sagen/move2who-2/txdav/who/util.py

Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/caldav.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -86,7 +86,7 @@
 from txdav.common.datastore.work.revision_cleanup import (
     scheduleFirstFindMinRevision
 )
-from txdav.dps.server import directoryFromConfig
+from txdav.who.util import directoryFromConfig
 from txdav.dps.client import DirectoryService as DirectoryProxyClientService
 from txdav.who.groups import GroupCacher
 

Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/test/test_util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/test/test_util.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tap/test/test_util.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -21,7 +21,7 @@
 from twistedcaldav.directory.augment import AugmentXMLDB
 from twisted.internet.task import Clock
 from twisted.internet.defer import succeed, inlineCallbacks
-from txdav.dps.server import directoryFromConfig
+from txdav.who.util import directoryFromConfig
 
 class ProcessCountTestCase(TestCase):
 

Modified: CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/test/test_principals.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/calendarserver/tools/test/test_principals.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -30,7 +30,7 @@
 from twistedcaldav.test.util import (
     TestCase, CapturingProcessProtocol, ErrorOutput
 )
-from txdav.dps.server import directoryFromConfig
+from txdav.who.util import directoryFromConfig
 
 
 

Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/stdconfig.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -374,6 +374,7 @@
     #    users, groups, locations and resources) to the server.
     #
     "DirectoryService": {
+        "Enabled": True,
         "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
         "params": DEFAULT_SERVICE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
     },
@@ -385,7 +386,7 @@
     #    and resources.
     #
     "ResourceService": {
-        "Enabled" : True,
+        "Enabled": True,
         "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
         "params": DEFAULT_RESOURCE_PARAMS["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
     },

Modified: CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/twistedcaldav/test/util.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -40,7 +40,7 @@
 from txdav.caldav.datastore.test.util import buildCalendarStore
 from txdav.common.datastore.file import CommonDataStore
 from txdav.common.datastore.test.util import deriveQuota, CommonCommonTests
-from txdav.dps.server import directoryFromConfig
+from txdav.who.util import directoryFromConfig
 from txdav.xml import element as davxml, element
 from txweb2.dav.test.util import SimpleRequest
 import txweb2.dav.test.util

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/caldav/datastore/test/test_attachments.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/caldav/datastore/test/test_attachments.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/caldav/datastore/test/test_attachments.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from txdav.dps.server import directoryFromConfig
+from txdav.who.util import directoryFromConfig
 
 from pycalendar.datetime import DateTime
 from pycalendar.value import Value

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/dps/client.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -226,7 +226,9 @@
             recordType=recordType.name.encode("utf-8")
         )
 
+    listRecords = recordsWithRecordType
 
+
     def recordsWithEmailAddress(self, emailAddress):
         return self._call(
             RecordsWithEmailAddressCommand,

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/dps/server.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/dps/server.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/dps/server.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -15,28 +15,20 @@
 ##
 
 import cPickle as pickle
-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.expression import MatchType, MatchFlags, Operand
-from twext.who.idirectory import RecordType, DirectoryConfigurationError
-from twext.who.ldap import DirectoryService as LDAPDirectoryService
-from twext.who.util import ConstantsContainer
+from twext.who.idirectory import RecordType
 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, fullServerPath
+from twistedcaldav.config import config
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 from txdav.dps.commands import (
     RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
@@ -46,12 +38,10 @@
     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand,
     # UpdateRecordsCommand, RemoveRecordsCommand
 )
-from txdav.who.augment import AugmentedDirectoryService
-from txdav.who.delegates import DirectoryService as DelegateDirectoryService
-from txdav.who.idirectory import RecordType as CalRecordType
-from txdav.who.xml import DirectoryService as XMLDirectoryService
+from txdav.who.util import directoryFromConfig
 from zope.interface import implementer
 
+
 log = Logger()
 
 
@@ -449,130 +439,6 @@
 
 
 
-def directoryFromConfig(config, store=None):
-    """
-    Return a directory service based on the config.  If you want to go through
-    AMP to talk to one of these as a client, instantiate
-    txdav.dps.client.DirectoryService
-    """
-
-    # MOVE2WHO FIXME: this needs to talk to its own separate database.  In fact,
-    # don't pass store=None if you already have called storeFromConfig()
-    # within this process.  Pass the existing store in here.
-    pool, txnFactory = getDBPool(config)
-    if store is None:
-        store = storeFromConfig(config, txnFactory, None)
-
-    aggregatedServices = []
-
-    for serviceKey in ("DirectoryService", "ResourceService"):
-        serviceValue = config.get(serviceKey, None)
-        directoryType = serviceValue.type.lower()
-        params = serviceValue.params
-
-        if "xml" in directoryType:
-            xmlFile = params.xmlFile
-            xmlFile = fullServerPath(config.DataRoot, xmlFile)
-            # path = kwds.pop("path", "")
-            if not xmlFile or not os.path.exists(xmlFile):
-                log.error("Path not found for XML directory: {p}", p=xmlFile)
-            fp = FilePath(xmlFile)
-            directory = XMLDirectoryService(fp)
-
-        elif "opendirectory" in directoryType:
-            from twext.who.opendirectory import DirectoryService as ODDirectoryService
-            directory = ODDirectoryService()
-
-        elif "ldap" in directoryType:
-            if params.credentials.dn and params.credentials.password:
-                creds = UsernamePassword(params.credentials.dn,
-                                         params.credentials.password)
-            else:
-                creds = None
-            directory = LDAPDirectoryService(
-                params.uri,
-                params.rdnSchema.base,
-                creds=creds
-            )
-
-        else:
-            log.error("Invalid DirectoryType: {dt}", dt=directoryType)
-            raise DirectoryConfigurationError
-
-        # Set the appropriate record types on each service
-        types = []
-        for recordTypeName in params.recordTypes:
-            recordType = {
-                "users": RecordType.user,
-                "groups": RecordType.group,
-                "locations": CalRecordType.location,
-                "resources": CalRecordType.resource,
-                "addresses": CalRecordType.address,
-            }.get(recordTypeName, None)
-            if recordType is None:
-                log.error("Invalid Record Type: {rt}", rt=recordTypeName)
-                raise DirectoryConfigurationError
-            if recordType in types:
-                log.error("Duplicate Record Type: {rt}", rt=recordTypeName)
-                raise DirectoryConfigurationError
-            types.append(recordType)
-
-        directory.recordType = ConstantsContainer(types)
-        aggregatedServices.append(directory)
-
-    #
-    # Setup the Augment Service
-    #
-    if config.AugmentService.type:
-        augmentClass = namedClass(config.AugmentService.type)
-        log.info(
-            "Configuring augment service of type: {augmentClass}",
-            augmentClass=augmentClass
-        )
-        try:
-            augmentService = augmentClass(**config.AugmentService.params)
-        except IOError:
-            log.error("Could not start augment service")
-            raise
-    else:
-        augmentService = None
-
-    userDirectory = None
-    for directory in aggregatedServices:
-        if RecordType.user in directory.recordTypes():
-            userDirectory = directory
-            break
-    else:
-        log.error("No directory service set up for users")
-        raise DirectoryConfigurationError
-
-    delegateDirectory = DelegateDirectoryService(
-        userDirectory.realmName,
-        store
-    )
-    aggregatedServices.append(delegateDirectory)
-
-    aggregateDirectory = AggregateDirectoryService(
-        userDirectory.realmName, aggregatedServices
-    )
-    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 augmented
-
-
-
 @implementer(IPlugin, service.IServiceMaker)
 class DirectoryProxyServiceMaker(object):
 

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/directory.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -28,11 +28,9 @@
 from twext.who.idirectory import RecordType as BaseRecordType
 from twisted.cred.credentials import UsernamePassword
 from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
 from txdav.who.idirectory import RecordType as DAVRecordType
 from txweb2.auth.digest import DigestedCredentials
 
-
 log = Logger()
 
 
@@ -42,6 +40,7 @@
 ]
 
 
+
 class CalendarDirectoryServiceMixin(object):
 
     guid = "1332A615-4D3A-41FE-B636-FBE25BFB982E"
@@ -59,6 +58,8 @@
 
     @inlineCallbacks
     def recordWithCalendarUserAddress(self, address):
+        # FIXME: moved this here to avoid circular import problems
+        from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
         address = normalizeCUAddr(address)
         record = None
         if address.startswith("urn:uuid:"):

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/accounts.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/accounts.xml	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/accounts.xml	2014-03-13 20:33:25 UTC (rev 12897)
@@ -18,175 +18,339 @@
 
 <!DOCTYPE accounts SYSTEM "accounts.dtd">
 
-<accounts realm="Test Realm">
-  <user>
+<directory realm="Test Realm">
+  <record type="user">
     <uid>admin</uid>
-    <guid>admin</guid>
+    <short-name>admin</short-name>
     <password>admin</password>
-    <name>Super User</name>
-    <first-name>Super</first-name>
-    <last-name>User</last-name>
-  </user>
-  <user>
+    <full-name>Super User</full-name>
+  </record>
+  <record type="user">
     <uid>apprentice</uid>
-    <guid>apprentice</guid>
+    <short-name>apprentice</short-name>
     <password>apprentice</password>
-    <name>Apprentice Super User</name>
-    <first-name>Apprentice</first-name>
-    <last-name>Super User</last-name>
-  </user>
-  <user>
+    <full-name>Apprentice Super User</full-name>
+  </record>
+  <record type="user">
     <uid>wsanchez</uid>
-    <guid>wsanchez</guid>
-    <email-address>wsanchez at example.com</email-address>
+    <short-name>wsanchez</short-name>
+    <email>wsanchez at example.com</email>
     <password>test</password>
-    <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>
+    <full-name>Wilfredo Sanchez Vega</full-name>
+  </record>
+  <record type="user">
     <uid>cdaboo</uid>
-    <guid>cdaboo</guid>
-    <email-address>cdaboo at example.com</email-address>
+    <short-name>cdaboo</short-name>
+    <email>cdaboo at example.com</email>
     <password>test</password>
-    <name>Cyrus Daboo</name>
-    <first-name>Cyrus</first-name>
-    <last-name>Daboo</last-name>
-  </user>
-  <user>
+    <full-name>cyrus Daboo</full-name>
+  </record>
+  <record type="user">
     <uid>sagen</uid>
-    <guid>sagen</guid>
-    <email-address>sagen at example.com</email-address>
+    <short-name>sagen</short-name>
+    <email>sagen at example.com</email>
     <password>test</password>
-    <name>Morgen Sagen</name>
-    <first-name>Morgen</first-name>
-    <last-name>Sagen</last-name>
-  </user>
-  <user>
+    <full-name>Morgen Sagen</full-name>
+  </record>
+  <record type="user">
     <uid>dre</uid>
-    <guid>andre</guid>
-    <email-address>dre at example.com</email-address>
+    <short-name>andre</short-name>
+    <email>dre at example.com</email>
     <password>test</password>
-    <name>Andre LaBranche</name>
-    <first-name>Andre</first-name>
-    <last-name>LaBranche</last-name>
-  </user>
-  <user>
+    <full-name>Andre LaBranche</full-name>
+  </record>
+  <record type="user">
     <uid>glyph</uid>
-    <guid>glyph</guid>
-    <email-address>glyph at example.com</email-address>
+    <short-name>glyph</short-name>
+    <email>glyph at example.com</email>
     <password>test</password>
-    <name>Glyph Lefkowitz</name>
-    <first-name>Glyph</first-name>
-    <last-name>Lefkowitz</last-name>
-  </user>
-  <user>
+    <full-name>Glyph Lefkowitz</full-name>
+  </record>
+  <record type="user">
     <uid>i18nuser</uid>
-    <guid>i18nuser</guid>
-    <email-address>i18nuser at example.com</email-address>
+    <short-name>i18nuser</short-name>
+    <email>i18nuser at example.com</email>
     <password>i18nuser</password>
-    <name>まだ</name>
-    <first-name>ま</first-name>
-    <last-name>だ</last-name>
-  </user>
+    <full-name>まだ</full-name>
+  </record>
+
+  <!-- twext.who xml doesn't (yet) support repeat
   <user repeat="101">
     <uid>user%02d</uid>
     <uid>User %02d</uid>
-    <guid>user%02d</guid>
+    <short-name>user%02d</short-name>
     <password>user%02d</password>
-    <name>User %02d</name>
-    <first-name>User</first-name>
-    <last-name>%02d</last-name>
-    <email-address>user%02d at example.com</email-address>
-  </user>
+    <full-name>User %02d</full-name>
+    <email>user%02d at example.com</email>
+  </record>
   <user repeat="10">
     <uid>public%02d</uid>
-    <guid>public%02d</guid>
+    <short-name>public%02d</short-name>
     <password>public%02d</password>
-    <name>Public %02d</name>
-    <first-name>Public</first-name>
-    <last-name>%02d</last-name>
-  </user>
-  <group>
+    <full-name>Public %02d</full-name>
+  </record>
+  -->
+  <record type="user">
+    <short-name>user01</short-name>
+    <uid>user01</uid>
+    <password>user01</password>
+    <full-name>User 01</full-name>
+    <email>user01 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user02</short-name>
+    <uid>user02</uid>
+    <password>user02</password>
+    <full-name>User 02</full-name>
+    <email>user02 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user03</short-name>
+    <uid>user03</uid>
+    <password>user03</password>
+    <full-name>User 03</full-name>
+    <email>user03 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user04</short-name>
+    <uid>user04</uid>
+    <password>user04</password>
+    <full-name>User 04</full-name>
+    <email>user04 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user05</short-name>
+    <uid>user05</uid>
+    <password>user05</password>
+    <full-name>User 05</full-name>
+    <email>user05 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user06</short-name>
+    <uid>user06</uid>
+    <password>user06</password>
+    <full-name>User 06</full-name>
+    <email>user06 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user07</short-name>
+    <uid>user07</uid>
+    <password>user07</password>
+    <full-name>User 07</full-name>
+    <email>user07 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user08</short-name>
+    <uid>user08</uid>
+    <password>user08</password>
+    <full-name>User 08</full-name>
+    <email>user08 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user09</short-name>
+    <uid>user09</uid>
+    <password>user09</password>
+    <full-name>User 09</full-name>
+    <email>user09 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user10</short-name>
+    <uid>user10</uid>
+    <password>user10</password>
+    <full-name>User 10</full-name>
+    <email>user10 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user11</short-name>
+    <uid>user11</uid>
+    <password>user11</password>
+    <full-name>User 11</full-name>
+    <email>user11 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user12</short-name>
+    <uid>user12</uid>
+    <password>user12</password>
+    <full-name>User 12</full-name>
+    <email>user12 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user13</short-name>
+    <uid>user13</uid>
+    <password>user13</password>
+    <full-name>User 13</full-name>
+    <email>user13 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user14</short-name>
+    <uid>user14</uid>
+    <password>user14</password>
+    <full-name>User 14</full-name>
+    <email>user14 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user15</short-name>
+    <uid>user15</uid>
+    <password>user15</password>
+    <full-name>User 15</full-name>
+    <email>user15 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user16</short-name>
+    <uid>user16</uid>
+    <password>user16</password>
+    <full-name>User 16</full-name>
+    <email>user16 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user17</short-name>
+    <uid>user17</uid>
+    <password>user17</password>
+    <full-name>User 17</full-name>
+    <email>user17 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user18</short-name>
+    <uid>user18</uid>
+    <password>user18</password>
+    <full-name>User 18</full-name>
+    <email>user18 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user19</short-name>
+    <uid>user19</uid>
+    <password>user19</password>
+    <full-name>User 19</full-name>
+    <email>user19 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user20</short-name>
+    <uid>user20</uid>
+    <password>user20</password>
+    <full-name>User 20</full-name>
+    <email>user20 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user21</short-name>
+    <uid>user21</uid>
+    <password>user21</password>
+    <full-name>User 21</full-name>
+    <email>user21 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user22</short-name>
+    <uid>user22</uid>
+    <password>user22</password>
+    <full-name>User 22</full-name>
+    <email>user22 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user23</short-name>
+    <uid>user23</uid>
+    <password>user23</password>
+    <full-name>User 23</full-name>
+    <email>user23 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user24</short-name>
+    <uid>user24</uid>
+    <password>user24</password>
+    <full-name>User 24</full-name>
+    <email>user24 at example.com</email>
+  </record>
+
+  <record type="user">
+    <short-name>user25</short-name>
+    <uid>user25</uid>
+    <password>user25</password>
+    <full-name>User 25</full-name>
+    <email>user25 at example.com</email>
+  </record>
+
+  <record type="group">
     <uid>group01</uid>
-    <guid>group01</guid>
+    <short-name>group01</short-name>
     <password>group01</password>
-    <name>Group 01</name>
-    <email-address>group01 at example.com</email-address>
-    <members>
-      <member type="users">user01</member>
-    </members>
-  </group>
-  <group>
+    <full-name>Group 01</full-name>
+      <member-uid type="users">user01</member-uid>
+  </record>
+  <record type="group">
     <uid>group02</uid>
-    <guid>group02</guid>
+    <short-name>group02</short-name>
     <password>group02</password>
-    <name>Group 02</name>
-    <email-address>group02 at example.com</email-address>
-    <members>
-      <member type="users">user06</member>
-      <member type="users">user07</member>
-    </members>
-  </group>
-  <group>
+    <full-name>Group 02</full-name>
+      <member-uid type="users">user06</member-uid>
+      <member-uid type="users">user07</member-uid>
+  </record>
+  <record type="group">
     <uid>group03</uid>
-    <guid>group03</guid>
+    <short-name>group03</short-name>
     <password>group03</password>
-    <name>Group 03</name>
-    <members>
-      <member type="users">user08</member>
-      <member type="users">user09</member>
-    </members>
-  </group>
-  <group>
+    <full-name>Group 03</full-name>
+      <member-uid type="users">user08</member-uid>
+      <member-uid type="users">user09</member-uid>
+  </record>
+  <record type="group">
     <uid>group04</uid>
-    <guid>group04</guid>
+    <short-name>group04</short-name>
     <password>group04</password>
-    <name>Group 04</name>
-    <members>
-      <member type="groups">group02</member>
-      <member type="groups">group03</member>
-      <member type="users">user10</member>
-    </members>
-  </group>
-  <group> <!-- delegategroup -->
+    <full-name>Group 04</full-name>
+      <member-uid type="groups">group02</member-uid>
+      <member-uid type="groups">group03</member-uid>
+      <member-uid type="users">user10</member-uid>
+  </record>
+  <record type="group"> <!-- delegategroup -->
     <uid>group05</uid>
-    <guid>group05</guid>
+    <short-name>group05</short-name>
     <password>group05</password>
-    <name>Group 05</name>
-    <members>
-      <member type="groups">group06</member>
-      <member type="users">user20</member>
-    </members>
-  </group>
-  <group> <!-- delegatesubgroup -->
+    <full-name>Group 05</full-name>
+      <member-uid type="groups">group06</member-uid>
+      <member-uid type="users">user20</member-uid>
+  </record>
+  <record type="group"> <!-- delegatesubgroup -->
     <uid>group06</uid>
-    <guid>group06</guid>
+    <short-name>group06</short-name>
     <password>group06</password>
-    <name>Group 06</name>
-    <members>
-      <member type="users">user21</member>
-    </members>
-  </group>
-  <group> <!-- readonlydelegategroup -->
+    <full-name>Group 06</full-name>
+      <member-uid type="users">user21</member-uid>
+  </record>
+  <record type="group"> <!-- readonlydelegategroup -->
     <uid>group07</uid>
-    <guid>group07</guid>
+    <short-name>group07</short-name>
     <password>group07</password>
-    <name>Group 07</name>
-    <members>
-      <member type="users">user22</member>
-      <member type="users">user23</member>
-      <member type="users">user24</member>
-    </members>
-  </group>
-  <group>
+    <full-name>Group 07</full-name>
+      <member-uid type="users">user22</member-uid>
+      <member-uid type="users">user23</member-uid>
+      <member-uid type="users">user24</member-uid>
+  </record>
+  <record type="group">
     <uid>disabledgroup</uid>
-    <guid>disabledgroup</guid>
+    <short-name>disabledgroup</short-name>
     <password>disabledgroup</password>
-    <name>Disabled Group</name>
-    <members>
-      <member type="users">user01</member>
-    </members>
-  </group>
-</accounts>
+    <full-name>Disabled Group</full-name>
+      <member-uid type="users">user01</member-uid>
+  </record>
+</directory>

Added: CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/augments.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/augments.xml	                        (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/augments.xml	2014-03-13 20:33:25 UTC (rev 12897)
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE augments SYSTEM "augments.dtd">
+
+<augments>
+  <record>
+    <uid>Default</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record repeat="10">
+    <uid>location%02d</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record repeat="4">
+    <uid>resource%02d</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record>
+    <uid>resource05</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>none</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource06</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>accept-always</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource07</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>decline-always</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource08</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>accept-if-free</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource09</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>decline-if-busy</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource10</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>automatic</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>resource11</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>decline-always</auto-schedule-mode>
+    <auto-accept-group>group01</auto-accept-group>
+  </record>
+  <record repeat="10">
+    <uid>group%02d</uid>
+    <enable>true</enable>
+  </record>
+  <record>
+    <uid>disabledgroup</uid>
+    <enable>false</enable>
+  </record>
+  <record>
+    <uid>delegatedroom</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>false</enable-addressbook>
+    <auto-schedule>false</auto-schedule>
+  </record>
+  <record>
+    <uid>03DFF660-8BCC-4198-8588-DD77F776F518</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record>
+    <uid>80689D41-DAF8-4189-909C-DB017B271892</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+    <auto-accept-group>group01</auto-accept-group>
+  </record>
+  <record>
+    <uid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record>
+    <uid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+  </record>
+  <record>
+    <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>false</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>false</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>false</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+  <record>
+    <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+    <enable-login>true</enable-login>
+    <auto-schedule>true</auto-schedule>
+    <auto-schedule-mode>default</auto-schedule-mode>
+  </record>
+</augments>

Modified: CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/resources.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/resources.xml	2014-03-13 19:14:42 UTC (rev 12896)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/accounts/resources.xml	2014-03-13 20:33:25 UTC (rev 12897)
@@ -1,34 +1,273 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006-2014 Apple Inc. All rights reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- -->
-
-<!DOCTYPE accounts SYSTEM "accounts.dtd">
-
-<accounts realm="Test Realm">
-  <location repeat="10">
-    <uid>location%02d</uid>
-    <guid>location%02d</guid>
-    <password>location%02d</password>
-    <name>Room %02d</name>
-  </location>
-  <resource repeat="10">
-    <uid>resource%02d</uid>
-    <guid>resource%02d</guid>
-    <password>resource%02d</password>
-    <name>Resource %02d</name>
-  </resource>
-</accounts>
+<directory realm="Test Realm">
+  <record type="location">
+    <short-name>fantastic</short-name>
+    <uid>4D66A20A-1437-437D-8069-2F14E8322234</uid>
+    <full-name>Fantastic Conference Room</full-name>
+    <extras>
+      <associatedAddress>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</associatedAddress>
+    </extras>
+  </record>
+  <record type="location">
+    <short-name>jupiter</short-name>
+    <uid>jupiter</uid>
+    <full-name>Jupiter Conference Room, Building 2, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>uranus</short-name>
+    <uid>uranus</uid>
+    <full-name>Uranus Conference Room, Building 3, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>morgensroom</short-name>
+    <uid>03DFF660-8BCC-4198-8588-DD77F776F518</uid>
+    <full-name>Morgen's Room</full-name>
+  </record>
+  <record type="location">
+    <short-name>mercury</short-name>
+    <uid>mercury</uid>
+    <full-name>Mercury Conference Room, Building 1, 2nd Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>location09</short-name>
+    <uid>location09</uid>
+    <full-name>Room 09</full-name>
+  </record>
+  <record type="location">
+    <short-name>location08</short-name>
+    <uid>location08</uid>
+    <full-name>Room 08</full-name>
+  </record>
+  <record type="location">
+    <short-name>location07</short-name>
+    <uid>location07</uid>
+    <full-name>Room 07</full-name>
+  </record>
+  <record type="location">
+    <short-name>location06</short-name>
+    <uid>location06</uid>
+    <full-name>Room 06</full-name>
+  </record>
+  <record type="location">
+    <short-name>location05</short-name>
+    <uid>location05</uid>
+    <full-name>Room 05</full-name>
+  </record>
+  <record type="location">
+    <short-name>location04</short-name>
+    <uid>location04</uid>
+    <full-name>Room 04</full-name>
+  </record>
+  <record type="location">
+    <short-name>location03</short-name>
+    <uid>location03</uid>
+    <full-name>Room 03</full-name>
+  </record>
+  <record type="location">
+    <short-name>location02</short-name>
+    <uid>location02</uid>
+    <full-name>Room 02</full-name>
+  </record>
+  <record type="location">
+    <short-name>location01</short-name>
+    <uid>location01</uid>
+    <full-name>Room 01</full-name>
+  </record>
+  <record type="location">
+    <short-name>delegatedroom</short-name>
+    <uid>delegatedroom</uid>
+    <full-name>Delegated Conference Room</full-name>
+  </record>
+  <record type="location">
+    <short-name>mars</short-name>
+    <uid>redplanet</uid>
+    <full-name>Mars Conference Room, Building 1, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>sharissroom</short-name>
+    <uid>80689D41-DAF8-4189-909C-DB017B271892</uid>
+    <full-name>Shari's Room</full-name>
+    <extras>
+      <associatedAddress>6F9EE33B-78F6-481B-9289-3D0812FF0D64</associatedAddress>
+    </extras>
+  </record>
+  <record type="location">
+    <short-name>pluto</short-name>
+    <uid>pluto</uid>
+    <full-name>Pluto Conference Room, Building 2, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>saturn</short-name>
+    <uid>saturn</uid>
+    <full-name>Saturn Conference Room, Building 2, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>location10</short-name>
+    <uid>location10</uid>
+    <full-name>Room 10</full-name>
+  </record>
+  <record type="location">
+    <short-name>pretend</short-name>
+    <uid>06E3BDCB-9C19-485A-B14E-F146A80ADDC6</uid>
+    <full-name>Pretend Conference Room</full-name>
+    <extras>
+      <associatedAddress>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</associatedAddress>
+    </extras>
+  </record>
+  <record type="location">
+    <short-name>neptune</short-name>
+    <uid>neptune</uid>
+    <full-name>Neptune Conference Room, Building 2, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>Earth</short-name>
+    <uid>Earth</uid>
+    <full-name>Earth Conference Room, Building 1, 1st Floor</full-name>
+  </record>
+  <record type="location">
+    <short-name>venus</short-name>
+    <uid>venus</uid>
+    <full-name>Venus Conference Room, Building 1, 2nd Floor</full-name>
+  </record>
+  <record type="resource">
+    <short-name>sharisotherresource</short-name>
+    <uid>CCE95217-A57B-481A-AC3D-FEC9AB6CE3A9</uid>
+    <full-name>Shari's Other Resource</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource15</short-name>
+    <uid>resource15</uid>
+    <full-name>Resource 15</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource14</short-name>
+    <uid>resource14</uid>
+    <full-name>Resource 14</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource17</short-name>
+    <uid>resource17</uid>
+    <full-name>Resource 17</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource16</short-name>
+    <uid>resource16</uid>
+    <full-name>Resource 16</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource11</short-name>
+    <uid>resource11</uid>
+    <full-name>Resource 11</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource10</short-name>
+    <uid>resource10</uid>
+    <full-name>Resource 10</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource13</short-name>
+    <uid>resource13</uid>
+    <full-name>Resource 13</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource12</short-name>
+    <uid>resource12</uid>
+    <full-name>Resource 12</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource19</short-name>
+    <uid>resource19</uid>
+    <full-name>Resource 19</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource18</short-name>
+    <uid>resource18</uid>
+    <full-name>Resource 18</full-name>
+  </record>
+  <record type="resource">
+    <short-name>sharisresource</short-name>
+    <uid>C38BEE7A-36EE-478C-9DCB-CBF4612AFE65</uid>
+    <full-name>Shari's Resource</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource20</short-name>
+    <uid>resource20</uid>
+    <full-name>Resource 20</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource06</short-name>
+    <uid>resource06</uid>
+    <full-name>Resource 06</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource07</short-name>
+    <uid>resource07</uid>
+    <full-name>Resource 07</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource04</short-name>
+    <uid>resource04</uid>
+    <full-name>Resource 04</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource05</short-name>
+    <uid>resource05</uid>
+    <full-name>Resource 05</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource02</short-name>
+    <uid>resource02</uid>
+    <full-name>Resource 02</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource03</short-name>
+    <uid>resource03</uid>
+    <full-name>Resource 03</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource01</short-name>
+    <uid>resource01</uid>
+    <full-name>Resource 01</full-name>
+  </record>
+  <record type="resource">
+    <short-name>sharisotherresource1</short-name>
+    <uid>0CE0BF31-5F9E-4801-A489-8C70CF287F5F</uid>
+    <full-name>Shari's Other Resource1</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource08</short-name>
+    <uid>resource08</uid>
+    <full-name>Resource 08</full-name>
+  </record>
+  <record type="resource">
+    <short-name>resource09</short-name>
+    <uid>resource09</uid>
+    <full-name>Resource 09</full-name>
+  </record>
+  <record type="address">
+    <short-name>testaddress1</short-name>
+    <uid>6F9EE33B-78F6-481B-9289-3D0812FF0D64</uid>
+    <full-name>Test Address One</full-name>
+    <extras>
+      <streetAddress>20300 Stevens Creek Blvd, Cupertino, CA 95014</streetAddress>
+      <geo>37.322281,-122.028345</geo>
+    </extras>
+  </record>
+  <record type="address">
+    <short-name>il2</short-name>
+    <uid>63A2F949-2D8D-4C8D-B8A5-DCF2A94610F3</uid>
+    <full-name>IL2</full-name>
+    <extras>
+      <streetAddress>2 Infinite Loop, Cupertino, CA 95014</streetAddress>
+      <geo>37.332633,-122.030502</geo>
+    </extras>
+  </record>
+  <record type="address">
+    <short-name>il1</short-name>
+    <uid>76E7ECA6-08BC-4AE7-930D-F2E7453993A5</uid>
+    <full-name>IL1</full-name>
+    <extras>
+      <streetAddress>1 Infinite Loop, Cupertino, CA 95014</streetAddress>
+      <geo>37.331741,-122.030333</geo>
+    </extras>
+  </record>
+</directory>

Added: CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/test_util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/test_util.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/test/test_util.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -0,0 +1,96 @@
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+txdav.who.util tests
+"""
+
+import os
+
+from txdav.who.util import directoryFromConfig
+from twisted.trial.unittest import TestCase
+from twistedcaldav.config import ConfigDict
+from twisted.python.filepath import FilePath
+from txdav.who.augment import AugmentedDirectoryService
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
+
+
+class StubStore(object):
+    pass
+
+
+
+class UtilTest(TestCase):
+
+    def setUp(self):
+        sourceDir = FilePath(__file__).parent().child("accounts")
+        self.serverRoot = os.path.abspath(self.mktemp())
+        os.mkdir(self.serverRoot)
+        self.dataRoot = os.path.join(self.serverRoot, "data")
+        if not os.path.exists(self.dataRoot):
+            os.makedirs(self.dataRoot)
+        destDir = FilePath(self.dataRoot)
+
+        accounts = destDir.child("accounts.xml")
+        sourceAccounts = sourceDir.child("accounts.xml")
+        accounts.setContent(sourceAccounts.getContent())
+
+        resources = destDir.child("resources.xml")
+        sourceResources = sourceDir.child("resources.xml")
+        resources.setContent(sourceResources.getContent())
+
+        augments = destDir.child("augments.xml")
+        sourceAugments = sourceDir.child("augments.xml")
+        augments.setContent(sourceAugments.getContent())
+
+
+    def test_directoryFromConfig(self):
+
+        config = ConfigDict(
+            {
+                "DataRoot": self.dataRoot,
+                "DirectoryService": {
+                    "Enabled": True,
+                    "type": "XML",
+                    "params": {
+                        "xmlFile": "accounts.xml",
+                        "recordTypes": ["users", "groups"],
+                    },
+                },
+                "ResourceService": {
+                    "Enabled": True,
+                    "type": "XML",
+                    "params": {
+                        "xmlFile": "resources.xml",
+                        "recordTypes": ["locations", "resources"],
+                    },
+                },
+                "AugmentService": {
+                    "Enabled": True,
+                    # FIXME: This still uses an actual class name:
+                    "type": "twistedcaldav.directory.augment.AugmentXMLDB",
+                    "params": {
+                        "xmlFiles": ["augments.xml"],
+                    },
+                },
+            }
+        )
+
+        store = StubStore()
+        service = directoryFromConfig(config, store=store)
+        self.assertTrue(isinstance(service, AugmentedDirectoryService))
+        self.assertTrue(isinstance(service._directory, AggregateDirectoryService))
+        self.assertEquals(len(service._directory.services), 3)

Added: CalendarServer/branches/users/sagen/move2who-2/txdav/who/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who-2/txdav/who/util.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/move2who-2/txdav/who/util.py	2014-03-13 20:33:25 UTC (rev 12897)
@@ -0,0 +1,162 @@
+##
+# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+import os
+from twext.python.log import Logger
+from twisted.cred.credentials import UsernamePassword
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
+from txdav.who.augment import AugmentedDirectoryService
+
+from calendarserver.tap.util import getDBPool, storeFromConfig
+from twext.who.idirectory import RecordType, DirectoryConfigurationError
+from twext.who.ldap import DirectoryService as LDAPDirectoryService
+from twext.who.util import ConstantsContainer
+from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedClass
+from twistedcaldav.config import fullServerPath
+from txdav.who.delegates import DirectoryService as DelegateDirectoryService
+from txdav.who.idirectory import RecordType as CalRecordType
+from txdav.who.xml import DirectoryService as XMLDirectoryService
+
+log = Logger()
+
+
+def directoryFromConfig(config, store=None):
+    """
+    Return a directory service based on the config.  If you want to go through
+    AMP to talk to one of these as a client, instantiate
+    txdav.dps.client.DirectoryService
+    """
+
+    # MOVE2WHO FIXME: this needs to talk to its own separate database.  In fact,
+    # don't pass store=None if you already have called storeFromConfig()
+    # within this process.  Pass the existing store in here.
+    if store is None:
+        pool, txnFactory = getDBPool(config)
+        store = storeFromConfig(config, txnFactory, None)
+
+    aggregatedServices = []
+
+
+    for serviceKey in ("DirectoryService", "ResourceService"):
+        serviceValue = config.get(serviceKey, None)
+
+        if not serviceValue.Enabled:
+            continue
+
+        directoryType = serviceValue.type.lower()
+        params = serviceValue.params
+
+        if "xml" in directoryType:
+            xmlFile = params.xmlFile
+            xmlFile = fullServerPath(config.DataRoot, xmlFile)
+            if not xmlFile or not os.path.exists(xmlFile):
+                log.error("Path not found for XML directory: {p}", p=xmlFile)
+            fp = FilePath(xmlFile)
+            directory = XMLDirectoryService(fp)
+
+        elif "opendirectory" in directoryType:
+            from twext.who.opendirectory import DirectoryService as ODDirectoryService
+            directory = ODDirectoryService()
+
+        elif "ldap" in directoryType:
+            if params.credentials.dn and params.credentials.password:
+                creds = UsernamePassword(params.credentials.dn,
+                                         params.credentials.password)
+            else:
+                creds = None
+            directory = LDAPDirectoryService(
+                params.uri,
+                params.rdnSchema.base,
+                creds=creds
+            )
+
+        else:
+            log.error("Invalid DirectoryType: {dt}", dt=directoryType)
+            raise DirectoryConfigurationError
+
+        # Set the appropriate record types on each service
+        types = []
+        for recordTypeName in params.recordTypes:
+            recordType = {
+                "users": RecordType.user,
+                "groups": RecordType.group,
+                "locations": CalRecordType.location,
+                "resources": CalRecordType.resource,
+                "addresses": CalRecordType.address,
+            }.get(recordTypeName, None)
+            if recordType is None:
+                log.error("Invalid Record Type: {rt}", rt=recordTypeName)
+                raise DirectoryConfigurationError
+            if recordType in types:
+                log.error("Duplicate Record Type: {rt}", rt=recordTypeName)
+                raise DirectoryConfigurationError
+            types.append(recordType)
+
+        directory.recordType = ConstantsContainer(types)
+        aggregatedServices.append(directory)
+
+    #
+    # Setup the Augment Service
+    #
+    if config.AugmentService.type:
+        augmentClass = namedClass(config.AugmentService.type)
+        log.info(
+            "Configuring augment service of type: {augmentClass}",
+            augmentClass=augmentClass
+        )
+        try:
+            augmentService = augmentClass(**config.AugmentService.params)
+        except IOError:
+            log.error("Could not start augment service")
+            raise
+    else:
+        augmentService = None
+
+    userDirectory = None
+    for directory in aggregatedServices:
+        if RecordType.user in directory.recordTypes():
+            userDirectory = directory
+            break
+    else:
+        log.error("No directory service set up for users")
+        raise DirectoryConfigurationError
+
+    delegateDirectory = DelegateDirectoryService(
+        userDirectory.realmName,
+        store
+    )
+    aggregatedServices.append(delegateDirectory)
+
+    aggregateDirectory = AggregateDirectoryService(
+        userDirectory.realmName, aggregatedServices
+    )
+    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 augmented
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140313/48c35bc1/attachment-0001.html>


More information about the calendarserver-changes mailing list