Beispiel #1
0
    def setUp(self):
        yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()

        # The "local" directory service
        self.client = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = self.directory

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.client, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.client._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.client, "_sendCommand", newCall)
Beispiel #2
0
    def setUp(self):

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        if testMode == "xml":
            # Need a copy as it might change
            path = FilePath(os.path.join(os.path.dirname(__file__),
                                         "test.xml"))
            copy = FilePath(self.mktemp())
            path.copyTo(copy)
            remoteDirectory = CalendarXMLDirectoryService(copy)
        elif testMode == "od":
            remoteDirectory = CalendarODDirectoryService()

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_sendCommand", newCall)
    def setUp(self):

        self.numUsers = 1000

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = CalendarInMemoryDirectoryService(None)

        # Add users
        records = []
        fieldName = remoteDirectory.fieldName
        for i in xrange(self.numUsers):
            records.append(
                TestRecord(
                    remoteDirectory, {
                        fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
                        fieldName.shortNames:
                        (u"foo{ctr:05d}".format(ctr=i), ),
                        fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i), ),
                        fieldName.recordType: RecordType.user,
                    }))

        # Add a big group
        records.append(
            TestRecord(
                remoteDirectory, {
                    fieldName.uid: u"bigGroup",
                    fieldName.recordType: RecordType.group,
                }))

        yield remoteDirectory.updateRecords(records, create=True)

        group = yield remoteDirectory.recordWithUID(u"bigGroup")
        members = yield remoteDirectory.recordsWithRecordType(RecordType.user)
        yield group.setMembers(members)

        # Connect the two services directly via an IOPump
        client = AMP()
        self.server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(self.server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _call method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._call

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_call", newCall)
    def setUp(self):

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        if testMode == "xml":
            # Need a copy as it might change
            path = FilePath(os.path.join(os.path.dirname(__file__), "test.xml"))
            copy = FilePath(self.mktemp())
            path.copyTo(copy)
            remoteDirectory = CalendarXMLDirectoryService(copy)
        elif testMode == "od":
            remoteDirectory = CalendarODDirectoryService()

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_sendCommand", newCall)
    def setUp(self):
        yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()

        # The "local" directory service
        self.client = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = self.directory

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.client, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.client._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.client, "_sendCommand", newCall)
class DPSClientLargeResultsTest(unittest.TestCase):
    """
    Tests the client against a single directory service (as opposed to the
    augmented, aggregated structure you get from directoryFromConfig(), which
    is tested in the class below)
    """
    @inlineCallbacks
    def setUp(self):

        self.numUsers = 1000

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = CalendarInMemoryDirectoryService(None)

        # Add users
        records = []
        fieldName = remoteDirectory.fieldName
        for i in xrange(self.numUsers):
            records.append(
                TestRecord(
                    remoteDirectory, {
                        fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
                        fieldName.shortNames:
                        (u"foo{ctr:05d}".format(ctr=i), ),
                        fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i), ),
                        fieldName.recordType: RecordType.user,
                    }))

        # Add a big group
        records.append(
            TestRecord(
                remoteDirectory, {
                    fieldName.uid: u"bigGroup",
                    fieldName.recordType: RecordType.group,
                }))

        yield remoteDirectory.updateRecords(records, create=True)

        group = yield remoteDirectory.recordWithUID(u"bigGroup")
        members = yield remoteDirectory.recordsWithRecordType(RecordType.user)
        yield group.setMembers(members)

        # Connect the two services directly via an IOPump
        client = AMP()
        self.server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(self.server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _call method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._call

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_call", newCall)

    @inlineCallbacks
    def test_tooBigResults(self):
        """
        The AMP protocol limits values to 65,535 bytes, so the DPS server
        breaks up the responses to fit.  This test uses 1000 records to verify
        the various methods work seamlessly in the face of large results.
        Normally only a couple hundred records would fit in a single response.
        """

        # recordsMatchingTokens
        records = yield self.directory.recordsMatchingTokens([u"foo"])
        self.assertEquals(len(records), self.numUsers)

        # recordsMatchingFields
        fields = ((u"fullNames", "foo", MatchFlags.caseInsensitive,
                   MatchType.contains), )
        records = yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user)
        self.assertEquals(len(records), self.numUsers)

        # recordsWithRecordType
        records = yield self.directory.recordsWithRecordType(RecordType.user)
        self.assertEquals(len(records), self.numUsers)

        # members()
        group = yield self.directory.recordWithUID(u"bigGroup")
        members = yield group.members()
        self.assertEquals(len(members), self.numUsers)

        # force the limit small so continuations happen
        self.server._maxSize = 500
        # expandedMemberUIDs
        memberUIDs = yield group.expandedMemberUIDs()
        self.assertEquals(len(memberUIDs), self.numUsers)
class DPSClientSingleDirectoryTest(unittest.TestCase):
    """
    Tests the client against a single directory service (as opposed to the
    augmented, aggregated structure you get from directoryFromConfig(), which
    is tested in the class below)
    """
    def setUp(self):

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        if testMode == "xml":
            # Need a copy as it might change
            path = FilePath(os.path.join(os.path.dirname(__file__),
                                         "test.xml"))
            copy = FilePath(self.mktemp())
            path.copyTo(copy)
            remoteDirectory = CalendarXMLDirectoryService(copy)
        elif testMode == "od":
            remoteDirectory = CalendarODDirectoryService()

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_sendCommand", newCall)

    @inlineCallbacks
    def test_uid(self):
        record = (yield self.directory.recordWithUID(testUID))
        self.assertTrue(testShortName in record.shortNames)

    @inlineCallbacks
    def test_shortName(self):
        record = (yield
                  self.directory.recordWithShortName(RecordType.user,
                                                     testShortName))
        self.assertEquals(record.uid, testUID)

    @inlineCallbacks
    def test_guid(self):
        if testMode == "od":
            record = (yield self.directory.recordWithGUID(testUID))
            self.assertTrue(testShortName in record.shortNames)

    @inlineCallbacks
    def test_recordType(self):
        if testMode != "od":
            records = (yield
                       self.directory.recordsWithRecordType(RecordType.user))
            self.assertEquals(len(records), 9)

    @inlineCallbacks
    def test_emailAddress(self):
        if testMode == "xml":
            records = (yield self.directory.recordsWithEmailAddress(
                u"*****@*****.**"))
            self.assertEquals(len(records), 1)
            self.assertEquals(records[0].shortNames, [u"cdaboo"])

    @inlineCallbacks
    def test_recordsMatchingTokens(self):
        records = (yield self.directory.recordsMatchingTokens([u"anche"]))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_anyType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive,
             MatchType.contains),
            (u"fullNames", "morgen", MatchFlags.caseInsensitive,
             MatchType.contains),
        )
        records = (yield
                   self.directory.recordsMatchingFields(fields,
                                                        operand=Operand.OR,
                                                        recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_oneType(self):
        fields = ((u"fullNames", "anche", MatchFlags.caseInsensitive,
                   MatchType.contains), )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        # This location should *not* appear in the results
        self.assertFalse("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_unsupportedField(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive,
             MatchType.contains),
            # This should be ignored:
            (u"foo", "bar", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield
                   self.directory.recordsMatchingFields(fields,
                                                        operand=Operand.OR,
                                                        recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_nonUnicode(self):
        fields = ((u"guid", uuid.UUID("A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"),
                   MatchFlags.caseInsensitive, MatchType.equals), )
        records = (yield
                   self.directory.recordsMatchingFields(fields,
                                                        operand=Operand.OR,
                                                        recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertFalse("wsanchez" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_not(self):
        fields = ((u"fullNames", "anche",
                   MatchFlags.NOT | MatchFlags.caseInsensitive,
                   MatchType.contains), )
        records = (yield
                   self.directory.recordsMatchingFields(fields,
                                                        operand=Operand.OR,
                                                        recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" not in matchingShortNames)
        self.assertTrue("wsanchez" not in matchingShortNames)
        self.assertTrue("sanchezoffice" not in matchingShortNames)

    @inlineCallbacks
    def test_recordsFromMatchExpression(self):
        expression = MatchExpression(FieldName.uid, testUID, MatchType.equals,
                                     MatchFlags.none)
        records = yield self.directory.recordsFromExpression(expression)
        self.assertEquals(len(records), 1)

    test_recordsFromMatchExpression.todo = "Won't work until we can serialize expressions"

    @inlineCallbacks
    def test_members(self):
        group = yield self.directory.recordWithUID(u"__calendar-dev__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

    @inlineCallbacks
    def test_groups(self):
        # No need to use group cacher as the XML service directly supports
        # groups()
        record = yield self.directory.recordWithUID(u"__sagen__")
        groups = yield record.groups()
        self.assertEquals(len(groups), 1)

    @inlineCallbacks
    def test_group_changes(self):
        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

        # Add new member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.addMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 6)

        # Add existing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__wsanchez__")
        yield group.addMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 6)

        # Remove existing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.removeMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

        # Remove missing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.removeMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

    @inlineCallbacks
    def test_verifyPlaintextPassword(self):
        expectations = (
            (testPassword, True),  # Correct
            ("wrong", False),  # Incorrect
            ("wrong\xc3\xa5", False),  # Incorrect
        )
        record = (yield
                  self.directory.recordWithShortName(RecordType.user,
                                                     testShortName))

        for password, answer in expectations:
            authenticated = (yield record.verifyPlaintextPassword(password))
            self.assertEquals(authenticated, answer)

    @inlineCallbacks
    def test_verifyHTTPDigest(self):
        expectations = (
            (testPassword, True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (yield
                  self.directory.recordWithShortName(RecordType.user,
                                                     testShortName))

        realm = "host.example.com"
        nonce = "128446648710842461101646794502"
        algorithm = "md5"
        uri = "http://host.example.com"
        method = "GET"

        for password, answer in expectations:
            for qop, nc, cnonce in (
                ("", "", ""),
                ("auth", "00000001",
                 "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
            ):
                response = calcResponse(
                    calcHA1(algorithm, testShortName, realm, password,
                            nonce, cnonce),
                    calcHA2(algorithm, method, uri, qop, None), algorithm,
                    nonce, nc, cnonce, qop)

                authenticated = (yield record.verifyHTTPDigest(
                    testShortName, realm, uri, nonce, cnonce, algorithm, nc,
                    qop, response, method))
                self.assertEquals(authenticated, answer)
class DPSClientAugmentedAggregateDirectoryTest(StoreTestCase):
    """
    Similar to the above tests, but in the context of the directory structure
    that directoryFromConfig() returns
    """

    wsanchezUID = u"6423F94A-6B76-4A3A-815B-D52CFD77935D"

    @inlineCallbacks
    def setUp(self):
        yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()

        # The "local" directory service
        self.client = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = self.directory

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.client, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.client._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.client, "_sendCommand", newCall)

    def configure(self):
        """
        Override configuration hook to turn on wiki.
        """
        super(DPSClientAugmentedAggregateDirectoryTest, self).configure()
        self.patch(config.Authentication.Wiki, "Enabled", True)

    @inlineCallbacks
    def test_setAutoScheduleMode(self):
        """
        Verify setAutoSchedule works across DPS
        """
        record = yield self.client.recordWithUID(
            u"75EA36BE-F71B-40F9-81F9-CF59BF40CA8F")
        # Defaults to automatic
        self.assertEquals(record.autoScheduleMode,
                          AutoScheduleMode.acceptIfFreeDeclineIfBusy)
        # Change it to accept-if-busy
        yield record.setAutoScheduleMode(AutoScheduleMode.acceptIfFree)
        # Refetch it
        record = yield self.client.recordWithUID(
            u"75EA36BE-F71B-40F9-81F9-CF59BF40CA8F")
        # Verify it's changed
        self.assertEquals(record.autoScheduleMode,
                          AutoScheduleMode.acceptIfFree)

    @inlineCallbacks
    def test_uid(self):
        record = (yield self.client.recordWithUID(self.wsanchezUID))
        self.assertTrue(u"wsanchez" in record.shortNames)

    @inlineCallbacks
    def test_shortName(self):
        record = (yield
                  self.client.recordWithShortName(RecordType.user,
                                                  u"wsanchez"))
        self.assertEquals(record.uid, self.wsanchezUID)

    @inlineCallbacks
    def test_guid(self):
        record = yield self.client.recordWithGUID(self.wsanchezUID)
        self.assertTrue(u"wsanchez" in record.shortNames)

    @inlineCallbacks
    def test_recordType(self):
        records = (yield self.client.recordsWithRecordType(RecordType.user))
        self.assertEquals(len(records), 244)

    @inlineCallbacks
    def test_emailAddress(self):
        records = (
            yield self.client.recordsWithEmailAddress(u"*****@*****.**"))
        self.assertEquals(len(records), 1)
        self.assertEquals(records[0].shortNames, [u"wsanchez"])

    @inlineCallbacks
    def test_recordsMatchingTokens(self):
        records = (yield self.client.recordsMatchingTokens([u"anche"]))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingTokensWithContext(self):

        testData = [
            # (
            #     tokens,
            #     context,
            #     short names expected to be in results,
            #     short names not expected to be in results
            # ),
            (
                [u"an"],
                "user",  # just users
                (u"dre", u"wsanchez"),
                (u"transporter", u"managers"),
            ),
            (
                [u"an"],
                "group",  # just groups
                (u"managers", ),
                (u"dre", u"wsanchez", u"transporter"),
            ),
            (
                [u"an"],
                "location",  # just locations
                (u"sanchezoffice", ),
                (u"dre", u"wsanchez", u"transporter", u"managers"),
            ),
            (
                [u"an"],
                "resource",  # just resources
                (u"transporter", u"ftlcpu"),
                (u"dre", u"wsanchez", u"managers", u"sanchezoffice"),
            ),
            (
                [u"an"],
                "attendee",  # just users, groups, resources
                (u"dre", u"wsanchez", u"managers", u"transporter", u"ftlcpu"),
                (u"sanchezoffice", ),
            ),
            (
                [u"an"],
                None,  # any type
                (u"dre", u"wsanchez", u"managers", u"sanchezoffice",
                 u"transporter", u"ftlcpu"),
                (),
            ),
        ]

        for tokens, context, expected, unexpected in testData:
            # print("Tokens", tokens, "context", context)
            records = yield self.directory.recordsMatchingTokens(
                tokens, context)
            matchingShortNames = set()
            for r in records:
                for shortName in r.shortNames:
                    matchingShortNames.add(shortName)
            for name in expected:
                self.assertTrue(name in matchingShortNames)
            for name in unexpected:
                self.assertFalse(name in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_anyType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive,
             MatchType.contains),
            (u"fullNames", "morgen", MatchFlags.caseInsensitive,
             MatchType.contains),
        )
        records = (yield self.client.recordsMatchingFields(fields,
                                                           operand=Operand.OR,
                                                           recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_oneType(self):
        fields = ((u"fullNames", "anche", MatchFlags.caseInsensitive,
                   MatchType.contains), )
        records = (yield self.client.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        # This location should *not* appear in the results
        self.assertFalse("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsMatchingFields_unsupportedField(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive,
             MatchType.contains),
            # This should be ignored:
            (u"foo", "bar", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.client.recordsMatchingFields(fields,
                                                           operand=Operand.OR,
                                                           recordType=None))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)

    @inlineCallbacks
    def test_recordsFromMatchExpression(self):
        expression = MatchExpression(FieldName.uid, u"wsanchez",
                                     MatchType.equals, MatchFlags.none)
        records = yield self.client.recordsFromExpression(expression)
        self.assertEquals(len(records), 1)

    test_recordsFromMatchExpression.todo = "Won't work until we can serialize expressions"

    @inlineCallbacks
    def test_members(self):
        group = yield self.client.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        group = yield self.client.recordWithUID(u"emptygroup")
        members = yield group.members()
        self.assertEquals(len(members), 0)

    @inlineCallbacks
    def test_expandedMemberUIDs(self):
        group = yield self.client.recordWithUID(u"__top_group_1__")
        memberUIDs = yield group.expandedMemberUIDs()
        self.assertEquals(
            set(memberUIDs),
            set([
                u'__wsanchez1__', u'__cdaboo1__', u'__glyph1__', u'__sagen1__'
            ]))

    @inlineCallbacks
    def test_groups(self):

        # A group must first be "refreshed" into the DB otherwise we won't
        # consider it for group memberships
        txn = self.store.newTransaction()
        groupCacher = GroupCacher(self.directory)
        yield groupCacher.refreshGroup(txn, u"__sub_group_1__")
        yield txn.commit()

        # record = yield self.client.recordWithUID(u"__sagen1__")
        # FIXME: this call hangs during unit tests, but not in a real server:
        # groups = yield record.groups()
        # self.assertEquals(len(groups), 1)

    test_groups.todo = "Figure out why this hangs"

    @inlineCallbacks
    def test_group_changes(self):
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        # Add new member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.addMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 4)

        # Add existing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__wsanchez1__")
        yield group.addMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 4)

        # Remove existing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.removeMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        # Remove missing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.removeMembers((user, ))
        yield self.directory.updateRecords((group, ), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

    @inlineCallbacks
    def test_verifyPlaintextPassword(self):
        expectations = (
            (u"zehcnasw", True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (yield
                  self.client.recordWithShortName(RecordType.user,
                                                  u"wsanchez"))

        for password, answer in expectations:
            authenticated = (yield record.verifyPlaintextPassword(password))
            self.assertEquals(authenticated, answer)

    @inlineCallbacks
    def test_verifyHTTPDigest(self):
        expectations = (
            (u"zehcnasw", True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (yield
                  self.client.recordWithShortName(RecordType.user,
                                                  u"wsanchez"))

        realm = "host.example.com"
        nonce = "128446648710842461101646794502"
        algorithm = "md5"
        uri = "http://host.example.com"
        method = "GET"

        for password, answer in expectations:
            for qop, nc, cnonce in (
                ("", "", ""),
                ("auth", "00000001",
                 "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
            ):
                response = calcResponse(
                    calcHA1(algorithm, u"wsanchez", realm, password,
                            nonce, cnonce),
                    calcHA2(algorithm, method, uri, qop, None), algorithm,
                    nonce, nc, cnonce, qop)

                authenticated = (yield record.verifyHTTPDigest(
                    u"wsanchez", realm, uri, nonce, cnonce, algorithm, nc, qop,
                    response, method))
                self.assertEquals(authenticated, answer)
    def setUp(self):

        self.numUsers = 1000

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = CalendarInMemoryDirectoryService(None)

        # Add users
        records = []
        fieldName = remoteDirectory.fieldName
        for i in xrange(self.numUsers):
            records.append(
                TestRecord(
                    remoteDirectory,
                    {
                        fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
                        fieldName.shortNames: (u"foo{ctr:05d}".format(ctr=i),),
                        fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i),),
                        fieldName.recordType: RecordType.user,
                    }
                )
            )

        # Add a big group
        records.append(
            TestRecord(
                remoteDirectory,
                {
                    fieldName.uid: u"bigGroup",
                    fieldName.recordType: RecordType.group,
                }
            )
        )

        yield remoteDirectory.updateRecords(records, create=True)

        group = yield remoteDirectory.recordWithUID(u"bigGroup")
        members = yield remoteDirectory.recordsWithRecordType(RecordType.user)
        yield group.setMembers(members)

        # Connect the two services directly via an IOPump
        client = AMP()
        self.server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(self.server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _call method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._call

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_call", newCall)
Beispiel #10
0
class DPSClientLargeResultsTest(unittest.TestCase):
    """
    Tests the client against a single directory service (as opposed to the
    augmented, aggregated structure you get from directoryFromConfig(), which
    is tested in the class below)
    """

    @inlineCallbacks
    def setUp(self):

        self.numUsers = 1000

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = CalendarInMemoryDirectoryService(None)

        # Add users
        records = []
        fieldName = remoteDirectory.fieldName
        for i in xrange(self.numUsers):
            records.append(
                TestRecord(
                    remoteDirectory,
                    {
                        fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
                        fieldName.shortNames: (u"foo{ctr:05d}".format(ctr=i),),
                        fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i),),
                        fieldName.recordType: RecordType.user,
                    }
                )
            )

        # Add a big group
        records.append(
            TestRecord(
                remoteDirectory,
                {
                    fieldName.uid: u"bigGroup",
                    fieldName.recordType: RecordType.group,
                }
            )
        )

        yield remoteDirectory.updateRecords(records, create=True)

        group = yield remoteDirectory.recordWithUID(u"bigGroup")
        members = yield remoteDirectory.recordsWithRecordType(RecordType.user)
        yield group.setMembers(members)

        # Connect the two services directly via an IOPump
        client = AMP()
        self.server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(self.server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _call method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._call

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_call", newCall)


    @inlineCallbacks
    def test_tooBigResults(self):
        """
        The AMP protocol limits values to 65,535 bytes, so the DPS server
        breaks up the responses to fit.  This test uses 1000 records to verify
        the various methods work seamlessly in the face of large results.
        Normally only a couple hundred records would fit in a single response.
        """

        # recordsMatchingTokens
        records = yield self.directory.recordsMatchingTokens([u"foo"])
        self.assertEquals(len(records), self.numUsers)

        # recordsMatchingFields
        fields = (
            (u"fullNames", "foo", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user
        )
        self.assertEquals(len(records), self.numUsers)

        # recordsWithRecordType
        records = yield self.directory.recordsWithRecordType(
            RecordType.user
        )
        self.assertEquals(len(records), self.numUsers)

        # members()
        group = yield self.directory.recordWithUID(u"bigGroup")
        members = yield group.members()
        self.assertEquals(len(members), self.numUsers)

        # force the limit small so continuations happen
        self.server._maxSize = 500
        # expandedMemberUIDs
        memberUIDs = yield group.expandedMemberUIDs()
        self.assertEquals(len(memberUIDs), self.numUsers)
Beispiel #11
0
class DPSClientSingleDirectoryTest(unittest.TestCase):
    """
    Tests the client against a single directory service (as opposed to the
    augmented, aggregated structure you get from directoryFromConfig(), which
    is tested in the class below)
    """

    def setUp(self):

        # The "local" directory service
        self.directory = DirectoryService(None)

        # The "remote" directory service
        if testMode == "xml":
            # Need a copy as it might change
            path = FilePath(os.path.join(os.path.dirname(__file__), "test.xml"))
            copy = FilePath(self.mktemp())
            path.copyTo(copy)
            remoteDirectory = CalendarXMLDirectoryService(copy)
        elif testMode == "od":
            remoteDirectory = CalendarODDirectoryService()

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.directory, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.directory._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.directory, "_sendCommand", newCall)


    @inlineCallbacks
    def test_uid(self):
        record = (yield self.directory.recordWithUID(testUID))
        self.assertTrue(testShortName in record.shortNames)


    @inlineCallbacks
    def test_shortName(self):
        record = (yield self.directory.recordWithShortName(
            RecordType.user,
            testShortName
        ))
        self.assertEquals(record.uid, testUID)


    @inlineCallbacks
    def test_guid(self):
        if testMode == "od":
            record = (yield self.directory.recordWithGUID(testUID))
            self.assertTrue(testShortName in record.shortNames)


    @inlineCallbacks
    def test_recordType(self):
        if testMode != "od":
            records = (yield self.directory.recordsWithRecordType(
                RecordType.user
            ))
            self.assertEquals(len(records), 9)


    @inlineCallbacks
    def test_emailAddress(self):
        if testMode == "xml":
            records = (yield self.directory.recordsWithEmailAddress(
                u"*****@*****.**"
            ))
            self.assertEquals(len(records), 1)
            self.assertEquals(records[0].shortNames, [u"cdaboo"])


    @inlineCallbacks
    def test_recordsMatchingTokens(self):
        records = (yield self.directory.recordsMatchingTokens(
            [u"anche"]
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_anyType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
            (u"fullNames", "morgen", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_oneType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        # This location should *not* appear in the results
        self.assertFalse("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_unsupportedField(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
            # This should be ignored:
            (u"foo", "bar", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_nonUnicode(self):
        fields = (
            (u"guid", uuid.UUID("A3B1158F-0564-4F5B-81E4-A89EA5FF81B0"),
                MatchFlags.caseInsensitive, MatchType.equals),
        )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertFalse("wsanchez" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_not(self):
        fields = (
            (
                u"fullNames", "anche",
                MatchFlags.NOT | MatchFlags.caseInsensitive,
                MatchType.contains
            ),
        )
        records = (yield self.directory.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" not in matchingShortNames)
        self.assertTrue("wsanchez" not in matchingShortNames)
        self.assertTrue("sanchezoffice" not in matchingShortNames)


    @inlineCallbacks
    def test_recordsFromMatchExpression(self):
        expression = MatchExpression(
            FieldName.uid,
            testUID,
            MatchType.equals,
            MatchFlags.none
        )
        records = yield self.directory.recordsFromExpression(expression)
        self.assertEquals(len(records), 1)

    test_recordsFromMatchExpression.todo = "Won't work until we can serialize expressions"


    @inlineCallbacks
    def test_members(self):
        group = yield self.directory.recordWithUID(u"__calendar-dev__")
        members = yield group.members()
        self.assertEquals(len(members), 5)


    @inlineCallbacks
    def test_groups(self):
        # No need to use group cacher as the XML service directly supports
        # groups()
        record = yield self.directory.recordWithUID(u"__sagen__")
        groups = yield record.groups()
        self.assertEquals(len(groups), 1)


    @inlineCallbacks
    def test_group_changes(self):
        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

        # Add new member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.addMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 6)

        # Add existing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__wsanchez__")
        yield group.addMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 6)

        # Remove existing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.removeMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)

        # Remove missing member
        group = yield self.directory.recordWithUID(u"__twisted__")
        user = yield self.directory.recordWithUID(u"__cdaboo__")
        yield group.removeMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__twisted__")
        members = yield group.members()
        self.assertEquals(len(members), 5)


    @inlineCallbacks
    def test_verifyPlaintextPassword(self):
        expectations = (
            (testPassword, True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (
            yield self.directory.recordWithShortName(
                RecordType.user,
                testShortName
            )
        )

        for password, answer in expectations:
            authenticated = (yield record.verifyPlaintextPassword(password))
            self.assertEquals(authenticated, answer)


    @inlineCallbacks
    def test_verifyHTTPDigest(self):
        expectations = (
            (testPassword, True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (
            yield self.directory.recordWithShortName(
                RecordType.user,
                testShortName
            )
        )

        realm = "host.example.com"
        nonce = "128446648710842461101646794502"
        algorithm = "md5"
        uri = "http://host.example.com"
        method = "GET"

        for password, answer in expectations:
            for qop, nc, cnonce in (
                ("", "", ""),
                ("auth", "00000001", "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
            ):
                response = calcResponse(
                    calcHA1(algorithm, testShortName, realm, password, nonce, cnonce),
                    calcHA2(algorithm, method, uri, qop, None),
                    algorithm, nonce, nc, cnonce, qop)

                authenticated = (
                    yield record.verifyHTTPDigest(
                        testShortName, realm, uri, nonce, cnonce, algorithm, nc, qop,
                        response, method
                    )
                )
                self.assertEquals(authenticated, answer)
Beispiel #12
0
class DPSClientAugmentedAggregateDirectoryTest(StoreTestCase):
    """
    Similar to the above tests, but in the context of the directory structure
    that directoryFromConfig() returns
    """

    wsanchezUID = u"6423F94A-6B76-4A3A-815B-D52CFD77935D"

    @inlineCallbacks
    def setUp(self):
        yield super(DPSClientAugmentedAggregateDirectoryTest, self).setUp()

        # The "local" directory service
        self.client = DirectoryService(None)

        # The "remote" directory service
        remoteDirectory = self.directory

        # Connect the two services directly via an IOPump
        client = AMP()
        server = DirectoryProxyAMPProtocol(remoteDirectory)
        pump = returnConnected(server, client)

        # Replace the normal _getConnection method with one that bypasses any
        # actual networking
        self.patch(self.client, "_getConnection", lambda: succeed(client))

        # Wrap the normal _sendCommand method with one that flushes the IOPump
        # afterwards
        origCall = self.client._sendCommand

        def newCall(*args, **kwds):
            d = origCall(*args, **kwds)
            pump.flush()
            return d

        self.patch(self.client, "_sendCommand", newCall)


    def configure(self):
        """
        Override configuration hook to turn on wiki.
        """
        super(DPSClientAugmentedAggregateDirectoryTest, self).configure()
        self.patch(config.Authentication.Wiki, "Enabled", True)


    @inlineCallbacks
    def test_uid(self):
        record = (yield self.client.recordWithUID(self.wsanchezUID))
        self.assertTrue(u"wsanchez" in record.shortNames)


    @inlineCallbacks
    def test_shortName(self):
        record = (yield self.client.recordWithShortName(
            RecordType.user,
            u"wsanchez"
        ))
        self.assertEquals(record.uid, self.wsanchezUID)


    @inlineCallbacks
    def test_guid(self):
        record = yield self.client.recordWithGUID(self.wsanchezUID)
        self.assertTrue(u"wsanchez" in record.shortNames)


    @inlineCallbacks
    def test_recordType(self):
        records = (yield self.client.recordsWithRecordType(
            RecordType.user
        ))
        self.assertEquals(len(records), 244)


    @inlineCallbacks
    def test_emailAddress(self):
        records = (yield self.client.recordsWithEmailAddress(
            u"*****@*****.**"
        ))
        self.assertEquals(len(records), 1)
        self.assertEquals(records[0].shortNames, [u"wsanchez"])


    @inlineCallbacks
    def test_recordsMatchingTokens(self):
        records = (yield self.client.recordsMatchingTokens(
            [u"anche"]
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingTokensWithContext(self):

        testData = [
            # (
            #     tokens,
            #     context,
            #     short names expected to be in results,
            #     short names not expected to be in results
            # ),
            (
                [u"an"],
                "user",  # just users
                (u"dre", u"wsanchez"),
                (u"transporter", u"managers"),
            ),
            (
                [u"an"],
                "group",  # just groups
                (u"managers",),
                (u"dre", u"wsanchez", u"transporter"),
            ),
            (
                [u"an"],
                "location",  # just locations
                (u"sanchezoffice",),
                (u"dre", u"wsanchez", u"transporter", u"managers"),
            ),
            (
                [u"an"],
                "resource",  # just resources
                (u"transporter", u"ftlcpu"),
                (u"dre", u"wsanchez", u"managers", u"sanchezoffice"),
            ),
            (
                [u"an"],
                "attendee",  # just users, groups, resources
                (
                    u"dre", u"wsanchez", u"managers",
                    u"transporter", u"ftlcpu"
                ),
                (u"sanchezoffice",),
            ),
            (
                [u"an"],
                None,   # any type
                (
                    u"dre", u"wsanchez", u"managers", u"sanchezoffice",
                    u"transporter", u"ftlcpu"
                ),
                (),
            ),
        ]

        for tokens, context, expected, unexpected in testData:
            # print("Tokens", tokens, "context", context)
            records = yield self.directory.recordsMatchingTokens(
                tokens, context
            )
            matchingShortNames = set()
            for r in records:
                for shortName in r.shortNames:
                    matchingShortNames.add(shortName)
            for name in expected:
                self.assertTrue(name in matchingShortNames)
            for name in unexpected:
                self.assertFalse(name in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_anyType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
            (u"fullNames", "morgen", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.client.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("sagen" in matchingShortNames)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_oneType(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.client.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=RecordType.user
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        # This location should *not* appear in the results
        self.assertFalse("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsMatchingFields_unsupportedField(self):
        fields = (
            (u"fullNames", "anche", MatchFlags.caseInsensitive, MatchType.contains),
            # This should be ignored:
            (u"foo", "bar", MatchFlags.caseInsensitive, MatchType.contains),
        )
        records = (yield self.client.recordsMatchingFields(
            fields, operand=Operand.OR, recordType=None
        ))
        matchingShortNames = set()
        for r in records:
            for shortName in r.shortNames:
                matchingShortNames.add(shortName)
        self.assertTrue("dre" in matchingShortNames)
        self.assertTrue("wsanchez" in matchingShortNames)
        self.assertTrue("sanchezoffice" in matchingShortNames)


    @inlineCallbacks
    def test_recordsFromMatchExpression(self):
        expression = MatchExpression(
            FieldName.uid,
            u"wsanchez",
            MatchType.equals,
            MatchFlags.none
        )
        records = yield self.client.recordsFromExpression(expression)
        self.assertEquals(len(records), 1)

    test_recordsFromMatchExpression.todo = "Won't work until we can serialize expressions"


    @inlineCallbacks
    def test_members(self):
        group = yield self.client.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        group = yield self.client.recordWithUID(u"emptygroup")
        members = yield group.members()
        self.assertEquals(len(members), 0)


    @inlineCallbacks
    def test_expandedMemberUIDs(self):
        group = yield self.client.recordWithUID(u"__top_group_1__")
        memberUIDs = yield group.expandedMemberUIDs()
        self.assertEquals(
            set(memberUIDs),
            set(
                [u'__wsanchez1__', u'__cdaboo1__', u'__glyph1__', u'__sagen1__']
            )
        )


    @inlineCallbacks
    def test_groups(self):

        # A group must first be "refreshed" into the DB otherwise we won't
        # consider it for group memberships
        txn = self.store.newTransaction()
        groupCacher = GroupCacher(self.directory)
        yield groupCacher.refreshGroup(txn, u"__sub_group_1__")
        yield txn.commit()

        # record = yield self.client.recordWithUID(u"__sagen1__")
        # FIXME: this call hangs during unit tests, but not in a real server:
        # groups = yield record.groups()
        # self.assertEquals(len(groups), 1)

    test_groups.todo = "Figure out why this hangs"


    @inlineCallbacks
    def test_group_changes(self):
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        # Add new member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.addMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 4)

        # Add existing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__wsanchez1__")
        yield group.addMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 4)

        # Remove existing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.removeMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)

        # Remove missing member
        group = yield self.directory.recordWithUID(u"__top_group_1__")
        user = yield self.directory.recordWithUID(u"__cdaboo1__")
        yield group.removeMembers((user,))
        yield self.directory.updateRecords((group,), False)

        group = yield self.directory.recordWithUID(u"__top_group_1__")
        members = yield group.members()
        self.assertEquals(len(members), 3)


    @inlineCallbacks
    def test_verifyPlaintextPassword(self):
        expectations = (
            (u"zehcnasw", True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (
            yield self.client.recordWithShortName(
                RecordType.user,
                u"wsanchez"
            )
        )

        for password, answer in expectations:
            authenticated = (yield record.verifyPlaintextPassword(password))
            self.assertEquals(authenticated, answer)


    @inlineCallbacks
    def test_verifyHTTPDigest(self):
        expectations = (
            (u"zehcnasw", True),  # Correct
            ("wrong", False)  # Incorrect
        )
        record = (
            yield self.client.recordWithShortName(
                RecordType.user,
                u"wsanchez"
            )
        )

        realm = "host.example.com"
        nonce = "128446648710842461101646794502"
        algorithm = "md5"
        uri = "http://host.example.com"
        method = "GET"

        for password, answer in expectations:
            for qop, nc, cnonce in (
                ("", "", ""),
                ("auth", "00000001", "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="),
            ):
                response = calcResponse(
                    calcHA1(algorithm, u"wsanchez", realm, password, nonce, cnonce),
                    calcHA2(algorithm, method, uri, qop, None),
                    algorithm, nonce, nc, cnonce, qop)

                authenticated = (
                    yield record.verifyHTTPDigest(
                        u"wsanchez", realm, uri, nonce, cnonce, algorithm, nc, qop,
                        response, method
                    )
                )
                self.assertEquals(authenticated, answer)