Ejemplo n.º 1
0
class GroupCacherTest(StoreTestCase):
    @inlineCallbacks
    def setUp(self):
        yield super(GroupCacherTest, self).setUp()
        self.groupCacher = GroupCacher(self.directory)

    @inlineCallbacks
    def test_multipleCalls(self):
        """
        Ensure multiple calls to groupByUID() don't raise an exception
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield txn.groupByUID(record.uid)
        yield txn.groupByUID(record.uid)

        yield txn.commit()

    @inlineCallbacks
    def test_refreshGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield self.groupCacher.refreshGroup(txn, record.uid)

        group = (yield txn.groupByUID(record.uid))

        self.assertEquals(group.extant, True)
        self.assertEquals(group.membershipHash,
                          "553eb54e3bbb26582198ee04541dbee4")

        group = yield txn.groupByID(group.groupID)
        self.assertEquals(group.groupUID, record.uid)
        self.assertEquals(group.name, u"Top Group 1")
        self.assertEquals(group.membershipHash,
                          "553eb54e3bbb26582198ee04541dbee4")
        self.assertEquals(group.extant, True)

        members = (yield txn.groupMemberUIDs(group.groupID))
        self.assertEquals(
            set([
                u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'
            ]), members)

        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(
            set([r.uid for r in records]),
            set([
                u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'
            ]))

        # sagen is in the top group, even though it's actually one level
        # removed
        record = yield self.directory.recordWithUID(u"__sagen1__")
        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
        self.assertEquals(set([u"__top_group_1__"]), groups)

        yield txn.commit()

    @inlineCallbacks
    def test_synchronizeMembers(self):
        """
        After loading in a group via refreshGroup(), pass new member sets to
        synchronizeMembers() and verify members are added and removed as
        expected
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Refresh the group so it's assigned a group_id
        uid = u"__top_group_1__"
        yield self.groupCacher.refreshGroup(txn, uid)
        group = yield txn.groupByUID(uid)

        # Remove two members, and add one member
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"dre1"):
            record = (yield self.directory.recordWithShortName(
                RecordType.user, name))
            newSet.add(record.uid)
        added, removed = (yield self.groupCacher.synchronizeMembers(
            txn, group.groupID, newSet))
        self.assertEquals(added, set([
            "__dre1__",
        ]))
        self.assertEquals(removed, set([
            "__glyph1__",
            "__sagen1__",
        ]))
        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(set([r.shortNames[0] for r in records]),
                          set(["wsanchez1", "cdaboo1", "dre1"]))

        # Remove all members
        added, removed = (yield self.groupCacher.synchronizeMembers(
            txn, group.groupID, set()))
        self.assertEquals(added, set())
        self.assertEquals(removed,
                          set([
                              "__wsanchez1__",
                              "__cdaboo1__",
                              "__dre1__",
                          ]))
        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(len(records), 0)

        yield txn.commit()

    @inlineCallbacks
    def test_groupByID(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Non-existent groupID
        yield self.failUnlessFailure(txn.groupByID(42), NotFoundError)

        uid = u"__top_group_1__"
        hash = "553eb54e3bbb26582198ee04541dbee4"
        yield self.groupCacher.refreshGroup(txn, uid)
        group = yield txn.groupByUID(uid)
        group = yield txn.groupByID(group.groupID)
        self.assertEqual(group.groupUID, uid)
        self.assertEqual(group.name, u"Top Group 1")
        self.assertEqual(group.membershipHash, hash)
        self.assertEqual(group.extant, True)

        yield txn.commit()

    @inlineCallbacks
    def test_externalAssignments(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {})

        newAssignments = {u"__wsanchez1__": (None, u"__top_group_1__")}
        yield self.groupCacher.scheduleExternalAssignments(txn,
                                                           newAssignments,
                                                           immediately=True)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments,
                          {u"__wsanchez1__": (None, u"__top_group_1__")})

        newAssignments = {
            u"__cdaboo1__": (u"__sub_group_1__", None),
            u"__wsanchez1__": (u"__sub_group_1__", u"__top_group_1__"),
        }

        yield self.groupCacher.scheduleExternalAssignments(txn,
                                                           newAssignments,
                                                           immediately=True)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments, {
                u"__wsanchez1__": (u"__sub_group_1__", u"__top_group_1__"),
                u"__cdaboo1__": (u"__sub_group_1__", None)
            })

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates,
                          set([u"__top_group_1__", u"__sub_group_1__"]))

        # Fault in the read-only group
        yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(u"__wsanchez1__",
                                         False,
                                         expanded=True))
        self.assertEquals(delegates, set([u"__sagen1__", u"__cdaboo1__"]))

        # Fault in the read-write group
        yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")

        # Wilfredo should have 4 users as read-write delegates
        delegates = (yield txn.delegates(u"__wsanchez1__", True,
                                         expanded=True))
        self.assertEquals(delegates,
                          set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]))

        #
        # Now, remove some external assignments
        #
        newAssignments = {
            u"__wsanchez1__": (u"__sub_group_1__", None),
        }
        yield self.groupCacher.scheduleExternalAssignments(txn,
                                                           newAssignments,
                                                           immediately=True)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {
            u"__wsanchez1__": (u"__sub_group_1__", None),
        })

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates, set([u"__sub_group_1__"]))

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(u"__wsanchez1__",
                                         False,
                                         expanded=True))
        self.assertEquals(delegates, set([u"__sagen1__", u"__cdaboo1__"]))

        # Wilfredo should have no read-write delegates
        delegates = (yield txn.delegates(u"__wsanchez1__", True,
                                         expanded=True))
        self.assertEquals(delegates, set([]))

        # Only 1 group as delegate now:
        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates, set([u"__sub_group_1__"]))

        #
        # Say somebody messed up and stuck a non-existent group UID in
        # as a delegate
        #
        newAssignments = {
            u"__wsanchez1__": (
                u"__sub_group_1__",
                u"__non_existent_group__",
            ),
        }
        yield self.groupCacher.scheduleExternalAssignments(txn,
                                                           newAssignments,
                                                           immediately=True)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__": (
                    u"__sub_group_1__",
                    None  # <--- (not __non_existent_group__)
                ),
            })

        yield txn.commit()

    def test_diffAssignments(self):
        """
        Ensure external proxy assignment diffing works
        """

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {}))

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {"B": ("1", "2")},
                # new
                {"B": ("1", "2")},
            ))

        self.assertEquals(
            map(
                set,
                (
                    # changed
                    [("A", ("1", "2")), ("B", ("3", "4"))],
                    # removed
                    [],
                )),
            map(
                set,
                diffAssignments(
                    # old
                    {},
                    # new
                    {
                        "A": ("1", "2"),
                        "B": ("3", "4")
                    })))

        self.assertEquals(
            map(
                set,
                (
                    # changed
                    [],
                    # removed
                    ["A", "B"],
                )),
            map(
                set,
                diffAssignments(
                    # old
                    {
                        "A": ("1", "2"),
                        "B": ("3", "4")
                    },
                    # new
                    {},
                )))

        self.assertEquals(
            map(
                set,
                (
                    # changed
                    [('C', ('4', '5')), ('D', ('7', '8'))],
                    # removed
                    ["B"],
                )),
            map(
                set,
                diffAssignments(
                    # old
                    {
                        "A": ("1", "2"),
                        "B": ("3", "4"),
                        "C": ("5", "6")
                    },
                    # new
                    {
                        "D": ("7", "8"),
                        "C": ("4", "5"),
                        "A": ("1", "2")
                    },
                )))

    @inlineCallbacks
    def test_recursiveGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"recursive1_coasts")
        members = yield record.expandedMembers()
        self.assertEquals(
            set([r.uid for r in members]),
            set([
                u'6423F94A-6B76-4A3A-815B-D52CFD77935D',
                u'5A985493-EE2C-4665-94CF-4DFEA3A89500'
            ]))

        yield txn.commit()

    @inlineCallbacks
    def test_groupChangeCacheNotificationRefreshGroup(self):
        """
        Verify refreshGroup() triggers a cache notification for the group and all
        members that are added or removed
        """
        class TestNotifier(object):
            changedTokens = []

            def changed(self, token):
                self.changedTokens.append(token)

        self.groupCacher.cacheNotifier = TestNotifier()

        # No change
        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [])

        # Add member to group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.addMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove member from group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.removeMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Add member to sub-group
        record = yield self.directory.recordWithUID(u"__sub_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.addMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__sub_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove member from sub-group
        record = yield self.directory.recordWithUID(u"__sub_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.removeMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__sub_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove sub-group member from group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
        yield record.removeMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(
            set(TestNotifier.changedTokens),
            set([
                "__top_group_1__",
                "__sagen1__",
                "__cdaboo1__",
            ]))
        TestNotifier.changedTokens = []

        # Add sub-group member to group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
        yield record.addMembers([
            addrecord,
        ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(),
                                            "__top_group_1__")
        yield self.commit()

        self.assertEqual(
            set(TestNotifier.changedTokens),
            set([
                "__top_group_1__",
                "__sagen1__",
                "__cdaboo1__",
            ]))
        TestNotifier.changedTokens = []

    @inlineCallbacks
    def test_groupChangeCacheNotificationApplyExternalAssignments(self):
        """
        Verify applyExternalAssignments() triggers a cache notification for the
        delegator and delegates
        """
        class TestNotifier(object):
            changedTokens = []

            def changed(self, token):
                self.changedTokens.append(token)

        self.groupCacher.cacheNotifier = TestNotifier()

        yield self.groupCacher.applyExternalAssignments(
            self.transactionUnderTest(), "__dre1__", None, None)
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, ["__dre1__"])
        TestNotifier.changedTokens = []

        yield self.groupCacher.applyExternalAssignments(
            self.transactionUnderTest(), "__dre1__", "__top_group_1__",
            "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens,
                         ["__dre1__", "__top_group_1__", "__sub_group_1__"])
        TestNotifier.changedTokens = []
Ejemplo n.º 2
0
class DelegationTest(StoreTestCase):

    @inlineCallbacks
    def setUp(self):
        yield super(DelegationTest, self).setUp()
        self.store = self.storeUnderTest()
        self.groupCacher = GroupCacher(self.directory)


    @inlineCallbacks
    def test_directDelegation(self):
        txn = self.store.newTransaction(label="test_directDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        delegate2 = yield self.directory.recordWithUID(u"__cdaboo1__")

        # Add 1 delegate
        yield addDelegate(txn, delegator, delegate1, True)
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals([u"__sagen1__"], [d.uid for d in delegates])
        delegators = (yield delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        yield txn.commit()  # So delegateService will see the changes
        txn = self.store.newTransaction(label="test_directDelegation")

        # The "proxy-write" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-write")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__sagen1__"]
        )
        # The "proxy-read" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-read")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )
        # The "proxy-write-for" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-write-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__wsanchez1__"]
        )
        # The "proxy-read-for" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-read-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )

        # Add another delegate
        yield addDelegate(txn, delegator, delegate2, True)
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield delegatedTo(txn, delegate2, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Remove 1 delegate
        yield removeDelegate(txn, delegator, delegate1, True)
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals([u"__cdaboo1__"], [d.uid for d in delegates])
        delegators = (yield delegatedTo(txn, delegate1, True))
        self.assertEquals(0, len(delegators))

        # Remove the other delegate
        yield removeDelegate(txn, delegator, delegate2, True)
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals(0, len(delegates))
        delegators = (yield delegatedTo(txn, delegate2, True))
        self.assertEquals(0, len(delegators))

        yield txn.commit()  # So delegateService will see the changes

        # Now set delegate assignments by using pseudoGroup.setMembers()
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        yield pseudoGroup.setMembers([delegate1, delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()

        # Set a different group of assignments:
        yield pseudoGroup.setMembers([delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()


    @inlineCallbacks
    def test_indirectDelegation(self):
        txn = self.store.newTransaction(label="test_indirectDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")
        group2 = yield self.directory.recordWithUID(u"__sub_group_1__")

        # Add group delegate, but before the group membership has been
        # pulled in
        yield addDelegate(txn, delegator, group1, True)
        # Passing expanded=False will return the group
        delegates = (yield delegatesOf(txn, delegator, True, expanded=False))
        self.assertEquals(1, len(delegates))
        self.assertEquals(delegates[0].uid, u"__top_group_1__")
        # Passing expanded=True will return not the group -- it only returns
        # non-groups
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(0, len(delegates))

        # Now refresh the group and there will be 3 delegates (contained
        # within 2 nested groups)
        # guid = "49b350c69611477b94d95516b13856ab"
        yield self.groupCacher.refreshGroup(txn, group1.uid)
        yield self.groupCacher.refreshGroup(txn, group2.uid)
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Verify we can ask for all delegated-to groups
        yield addDelegate(txn, delegator, group2, True)
        groups = (yield txn.allGroupDelegates())
        self.assertEquals(
            set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
        )

        # Delegate to a user who is already indirectly delegated-to
        yield addDelegate(txn, delegator, delegate1, True)
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )

        # Add a member to the group; they become a delegate
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"sagen1", u"glyph1", u"dre1"):
            record = (
                yield self.directory.recordWithShortName(RecordType.user, name)
            )
            newSet.add(record.uid)
        (
            groupID, name, _ignore_membershipHash, _ignore_modified,
            _ignore_extant
        ) = (yield txn.groupByUID(group1.uid))
        _ignore_numAdded, _ignore_numRemoved = (
            yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
        )
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__", u"__dre1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the top group
        yield removeDelegate(txn, delegator, group1, True)
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the sub group
        yield removeDelegate(txn, delegator, group2, True)
        delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()


    @inlineCallbacks
    def test_noDuplication(self):
        """
        Make sure addDelegate( ) is idempotent
        """
        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")

        # Delegate users:
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = (
            yield txn._selectDelegatesQuery.on(
                txn,
                delegator=delegator.uid.encode("utf-8"),
                readWrite=1
            )
        )
        yield txn.commit()
        self.assertEquals([["__sagen1__"]], results)

        # Delegate groups:
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = (
            yield txn._selectDelegateGroupsQuery.on(
                txn,
                delegator=delegator.uid.encode("utf-8"),
                readWrite=1
            )
        )
        yield txn.commit()
        self.assertEquals([["__top_group_1__"]], results)
Ejemplo n.º 3
0
class GroupCacherTest(StoreTestCase):
    @inlineCallbacks
    def setUp(self):
        yield super(GroupCacherTest, self).setUp()
        self.groupCacher = GroupCacher(self.directory)

    @inlineCallbacks
    def test_multipleCalls(self):
        """
        Ensure multiple calls to groupByUID() don't raise an exception
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield txn.groupByUID(record.uid)
        yield txn.groupByUID(record.uid)

        yield txn.commit()

    @inlineCallbacks
    def test_refreshGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield self.groupCacher.refreshGroup(txn, record.uid)

        (groupID, _ignore_name, membershipHash, _ignore_modified,
         extant) = (yield txn.groupByUID(record.uid))

        self.assertEquals(extant, True)
        self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")

        groupUID, name, membershipHash, extant = (yield txn.groupByID(groupID))
        self.assertEquals(groupUID, record.uid)
        self.assertEquals(name, u"Top Group 1")
        self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
        self.assertEquals(extant, True)

        members = (yield txn.membersOfGroup(groupID))
        self.assertEquals(
            set([
                u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'
            ]), members)

        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(
            set([r.uid for r in records]),
            set([
                u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'
            ]))

        # sagen is in the top group, even though it's actually one level
        # removed
        record = yield self.directory.recordWithUID(u"__sagen1__")
        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
        self.assertEquals(set([u"__top_group_1__"]), groups)

        yield txn.commit()

    @inlineCallbacks
    def test_synchronizeMembers(self):
        """
        After loading in a group via refreshGroup(), pass new member sets to
        synchronizeMembers() and verify members are added and removed as
        expected
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Refresh the group so it's assigned a group_id
        uid = u"__top_group_1__"
        yield self.groupCacher.refreshGroup(txn, uid)
        (groupID, name, _ignore_membershipHash, _ignore_modified,
         _ignore_extant) = yield txn.groupByUID(uid)

        # Remove two members, and add one member
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"dre1"):
            record = (yield self.directory.recordWithShortName(
                RecordType.user, name))
            newSet.add(record.uid)
        numAdded, numRemoved = (yield self.groupCacher.synchronizeMembers(
            txn, groupID, newSet))
        self.assertEquals(numAdded, 1)
        self.assertEquals(numRemoved, 2)
        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(set([r.shortNames[0] for r in records]),
                          set(["wsanchez1", "cdaboo1", "dre1"]))

        # Remove all members
        numAdded, numRemoved = (yield self.groupCacher.synchronizeMembers(
            txn, groupID, set()))
        self.assertEquals(numAdded, 0)
        self.assertEquals(numRemoved, 3)
        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(len(records), 0)

        yield txn.commit()

    @inlineCallbacks
    def test_groupByID(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Non-existent groupID
        self.failUnlessFailure(txn.groupByID(42), NotFoundError)

        uid = u"__top_group_1__"
        hash = "553eb54e3bbb26582198ee04541dbee4"
        yield self.groupCacher.refreshGroup(txn, uid)
        (groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
         extant) = yield txn.groupByUID(uid)
        results = yield txn.groupByID(groupID)
        self.assertEquals((uid, u"Top Group 1", hash, True), results)

        yield txn.commit()

    @inlineCallbacks
    def test_externalAssignments(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {})

        newAssignments = {u"__wsanchez1__": (None, u"__top_group_1__")}
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments,
                          {u"__wsanchez1__": (None, u"__top_group_1__")})

        newAssignments = {
            u"__cdaboo1__": (u"__sub_group_1__", None),
            u"__wsanchez1__": (u"__sub_group_1__", u"__top_group_1__"),
        }
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments, {
                u"__wsanchez1__": (u"__sub_group_1__", u"__top_group_1__"),
                u"__cdaboo1__": (u"__sub_group_1__", None)
            })

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates,
                          set([u"__top_group_1__", u"__sub_group_1__"]))

        # Fault in the read-only group
        yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(u"__wsanchez1__",
                                         False,
                                         expanded=True))
        self.assertEquals(delegates, set([u"__sagen1__", u"__cdaboo1__"]))

        # Fault in the read-write group
        yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")

        # Wilfredo should have 4 users as read-write delegates
        delegates = (yield txn.delegates(u"__wsanchez1__", True,
                                         expanded=True))
        self.assertEquals(
            delegates,
            set([
                u"__wsanchez1__", u"__sagen1__", u"__cdaboo1__", u"__glyph1__"
            ]))

        #
        # Now, remove some external assignments
        #
        newAssignments = {
            u"__wsanchez1__": (u"__sub_group_1__", None),
        }
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {
            u"__wsanchez1__": (u"__sub_group_1__", None),
        })

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates, set([u"__sub_group_1__"]))

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(u"__wsanchez1__",
                                         False,
                                         expanded=True))
        self.assertEquals(delegates, set([u"__sagen1__", u"__cdaboo1__"]))

        # Wilfredo should have no read-write delegates
        delegates = (yield txn.delegates(u"__wsanchez1__", True,
                                         expanded=True))
        self.assertEquals(delegates, set([]))

        # Only 1 group as delegate now:
        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(allGroupDelegates, set([u"__sub_group_1__"]))

        yield txn.commit()

    def test_diffAssignments(self):
        """
        Ensure external proxy assignment diffing works
        """

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {}))

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {"B": ("1", "2")},
                # new
                {"B": ("1", "2")},
            ))

        self.assertEquals(
            (
                # changed
                [("A", ("1", "2")), ("B", ("3", "4"))],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {
                    "A": ("1", "2"),
                    "B": ("3", "4")
                }))

        self.assertEquals(
            (
                # changed
                [],
                # removed
                ["A", "B"],
            ),
            diffAssignments(
                # old
                {
                    "A": ("1", "2"),
                    "B": ("3", "4")
                },
                # new
                {},
            ))

        self.assertEquals(
            (
                # changed
                [('C', ('4', '5')), ('D', ('7', '8'))],
                # removed
                ["B"],
            ),
            diffAssignments(
                # old
                {
                    "A": ("1", "2"),
                    "B": ("3", "4"),
                    "C": ("5", "6")
                },
                # new
                {
                    "D": ("7", "8"),
                    "C": ("4", "5"),
                    "A": ("1", "2")
                },
            ))
Ejemplo n.º 4
0
class DelegationTest(StoreTestCase):

    @inlineCallbacks
    def setUp(self):
        yield super(DelegationTest, self).setUp()
        self.store = self.storeUnderTest()
        self.groupCacher = GroupCacher(self.directory)

        yield Delegates._memcacher.flushAll()

    @inlineCallbacks
    def test_recordCreation(self):
        """
        Verify the record we get back from recordWithShortName has a shortName
        that matches the one we looked up.
        """
        record = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegateGroup,
            "foo"
        )
        self.assertEquals(record.shortNames[0], "foo")

    @inlineCallbacks
    def test_directDelegation(self):
        txn = self.store.newTransaction(label="test_directDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        delegate2 = yield self.directory.recordWithUID(u"__cdaboo1__")

        # Add 1 delegate
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals([u"__sagen1__"], [d.uid for d in delegates])
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        yield txn.commit()  # So delegateService will see the changes
        txn = self.store.newTransaction(label="test_directDelegation")

        # The "proxy-write" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-write")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__sagen1__"]
        )
        # The "proxy-read" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-read")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )
        # The "proxy-write-for" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-write-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__wsanchez1__"]
        )
        # The "proxy-read-for" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-read-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )

        # Add another delegate
        yield Delegates.addDelegate(txn, delegator, delegate2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield Delegates.delegatedTo(txn, delegate2, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Remove 1 delegate
        yield Delegates.removeDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals([u"__cdaboo1__"], [d.uid for d in delegates])
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals(0, len(delegators))

        # Remove the other delegate
        yield Delegates.removeDelegate(txn, delegator, delegate2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(0, len(delegates))
        delegators = (yield Delegates.delegatedTo(txn, delegate2, True))
        self.assertEquals(0, len(delegators))

        yield txn.commit()  # So delegateService will see the changes

        # Now set delegate assignments by using pseudoGroup.setMembers()
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        yield pseudoGroup.setMembers([delegate1, delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()

        # Set a different group of assignments:
        yield pseudoGroup.setMembers([delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()

    @inlineCallbacks
    def test_indirectDelegation(self):
        txn = self.store.newTransaction(label="test_indirectDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")
        group2 = yield self.directory.recordWithUID(u"__sub_group_1__")

        # Add group delegate
        yield Delegates.addDelegate(txn, delegator, group1, True)
        # Passing expanded=False will return the group
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=False))
        self.assertEquals(1, len(delegates))
        self.assertEquals(delegates[0].uid, u"__top_group_1__")
        # Passing expanded=True will return not the group -- it only returns
        # non-groups
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Verify we can ask for all delegated-to groups
        yield Delegates.addDelegate(txn, delegator, group2, True)
        groups = (yield txn.allGroupDelegates())
        self.assertEquals(
            set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
        )

        # Delegate to a user who is already indirectly delegated-to
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )

        # Add a member to the group; they become a delegate
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"sagen1", u"glyph1", u"dre1"):
            record = (
                yield self.directory.recordWithShortName(RecordType.user, name)
            )
            newSet.add(record.uid)
        group = yield txn.groupByUID(group1.uid)
        _ignore_added, _ignore_removed = (
            yield self.groupCacher.synchronizeMembers(txn, group.groupID, newSet)
        )
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__", u"__dre1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the top group
        yield Delegates.removeDelegate(txn, delegator, group1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the sub group
        yield Delegates.removeDelegate(txn, delegator, group2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()

    @inlineCallbacks
    def test_noDuplication(self):
        """
        Make sure addDelegate( ) is idempotent
        """
        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")

        # Delegate users:
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = yield DelegateRecord.query(
            txn,
            (DelegateRecord.delegator == delegator.uid.encode("utf-8")).And(
                DelegateRecord.readWrite == 1
            )
        )
        yield txn.commit()
        self.assertEquals(["__sagen1__", ], [record.delegate for record in results])

        # Delegate groups:
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = yield DelegateGroupsRecord.delegateGroups(
            txn,
            delegator.uid,
            True,
        )
        yield txn.commit()
        self.assertEquals(["__top_group_1__", ], [record.groupUID for record in results])
Ejemplo n.º 5
0
class GroupCacherTest(StoreTestCase):

    @inlineCallbacks
    def setUp(self):
        yield super(GroupCacherTest, self).setUp()
        self.groupCacher = GroupCacher(self.directory)


    @inlineCallbacks
    def test_multipleCalls(self):
        """
        Ensure multiple calls to groupByUID() don't raise an exception
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield txn.groupByUID(record.uid)
        yield txn.groupByUID(record.uid)

        yield txn.commit()


    @inlineCallbacks
    def test_refreshGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield self.groupCacher.refreshGroup(txn, record.uid)

        (
            groupID, _ignore_name, membershipHash, _ignore_modified,
            extant
        ) = (yield txn.groupByUID(record.uid))

        self.assertEquals(extant, True)
        self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")

        groupUID, name, membershipHash, extant = (yield txn.groupByID(groupID))
        self.assertEquals(groupUID, record.uid)
        self.assertEquals(name, u"Top Group 1")
        self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
        self.assertEquals(extant, True)

        members = (yield txn.membersOfGroup(groupID))
        self.assertEquals(
            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__']),
            members
        )

        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(
            set([r.uid for r in records]),
            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'])
        )

        # sagen is in the top group, even though it's actually one level
        # removed
        record = yield self.directory.recordWithUID(u"__sagen1__")
        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
        self.assertEquals(set([u"__top_group_1__"]), groups)

        yield txn.commit()


    @inlineCallbacks
    def test_synchronizeMembers(self):
        """
        After loading in a group via refreshGroup(), pass new member sets to
        synchronizeMembers() and verify members are added and removed as
        expected
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Refresh the group so it's assigned a group_id
        uid = u"__top_group_1__"
        yield self.groupCacher.refreshGroup(txn, uid)
        (
            groupID, name, _ignore_membershipHash, _ignore_modified,
            _ignore_extant
        ) = yield txn.groupByUID(uid)

        # Remove two members, and add one member
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"dre1"):
            record = (
                yield self.directory.recordWithShortName(
                    RecordType.user,
                    name
                )
            )
            newSet.add(record.uid)
        numAdded, numRemoved = (
            yield self.groupCacher.synchronizeMembers(
                txn, groupID, newSet
            )
        )
        self.assertEquals(numAdded, 1)
        self.assertEquals(numRemoved, 2)
        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(
            set([r.shortNames[0] for r in records]),
            set(["wsanchez1", "cdaboo1", "dre1"])
        )

        # Remove all members
        numAdded, numRemoved = (
            yield self.groupCacher.synchronizeMembers(txn, groupID, set())
        )
        self.assertEquals(numAdded, 0)
        self.assertEquals(numRemoved, 3)
        records = (yield self.groupCacher.cachedMembers(txn, groupID))
        self.assertEquals(len(records), 0)

        yield txn.commit()


    @inlineCallbacks
    def test_groupByID(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Non-existent groupID
        self.failUnlessFailure(txn.groupByID(42), NotFoundError)

        uid = u"__top_group_1__"
        hash = "553eb54e3bbb26582198ee04541dbee4"
        yield self.groupCacher.refreshGroup(txn, uid)
        (
            groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
            extant
        ) = yield txn.groupByUID(uid)
        results = yield txn.groupByID(groupID)
        self.assertEquals((uid, u"Top Group 1", hash, True), results)

        yield txn.commit()


    @inlineCallbacks
    def test_externalAssignments(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {})

        newAssignments = {
            u"__wsanchez1__": (None, u"__top_group_1__")
        }
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    None,
                    u"__top_group_1__"
                )
            }
        )

        newAssignments = {
            u"__cdaboo1__":
            (
                u"__sub_group_1__",
                None
            ),
            u"__wsanchez1__":
            (
                u"__sub_group_1__",
                u"__top_group_1__"
            ),
        }
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    u"__sub_group_1__",
                    u"__top_group_1__"
                ),
                u"__cdaboo1__":
                (
                    u"__sub_group_1__",
                    None
                )
            }
        )

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__top_group_1__",
                    u"__sub_group_1__"
                ]
            )
        )

        # Fault in the read-only group
        yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", False, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__sagen1__",
                    u"__cdaboo1__"
                ]
            )
        )

        # Fault in the read-write group
        yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")

        # Wilfredo should have 4 users as read-write delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", True, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__wsanchez1__",
                    u"__sagen1__",
                    u"__cdaboo1__",
                    u"__glyph1__"
                ]
            )
        )

        #
        # Now, remove some external assignments
        #
        newAssignments = {
            u"__wsanchez1__":
            (
                u"__sub_group_1__",
                None
            ),
        }
        yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    u"__sub_group_1__",
                    None
                ),
            }
        )

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__sub_group_1__"
                ]
            )
        )

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", False, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__sagen1__",
                    u"__cdaboo1__"
                ]
            )
        )

        # Wilfredo should have no read-write delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", True, expanded=True)
        )
        self.assertEquals(
            delegates,
            set([])
        )

        # Only 1 group as delegate now:
        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__sub_group_1__"
                ]
            )
        )

        yield txn.commit()


    def test_diffAssignments(self):
        """
        Ensure external proxy assignment diffing works
        """

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {}
            )
        )

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {"B": ("1", "2")},
                # new
                {"B": ("1", "2")},
            )
        )

        self.assertEquals(
            (
                # changed
                [("A", ("1", "2")), ("B", ("3", "4"))],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {"A": ("1", "2"), "B": ("3", "4")}
            )
        )

        self.assertEquals(
            (
                # changed
                [],
                # removed
                ["A", "B"],
            ),
            diffAssignments(
                # old
                {"A": ("1", "2"), "B": ("3", "4")},
                # new
                {},
            )
        )

        self.assertEquals(
            (
                # changed
                [('C', ('4', '5')), ('D', ('7', '8'))],
                # removed
                ["B"],
            ),
            diffAssignments(
                # old
                {"A": ("1", "2"), "B": ("3", "4"), "C": ("5", "6")},
                # new
                {"D": ("7", "8"), "C": ("4", "5"), "A": ("1", "2")},
            )
        )
Ejemplo n.º 6
0
class DelegationTest(StoreTestCase):

    @inlineCallbacks
    def setUp(self):
        yield super(DelegationTest, self).setUp()
        self.store = self.storeUnderTest()
        self.groupCacher = GroupCacher(self.directory)

        yield Delegates._memcacher.flushAll()


    @inlineCallbacks
    def test_recordCreation(self):
        """
        Verify the record we get back from recordWithShortName has a shortName
        that matches the one we looked up.
        """
        record = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegateGroup,
            "foo"
        )
        self.assertEquals(record.shortNames[0], "foo")


    @inlineCallbacks
    def test_directDelegation(self):
        txn = self.store.newTransaction(label="test_directDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        delegate2 = yield self.directory.recordWithUID(u"__cdaboo1__")

        # Add 1 delegate
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals([u"__sagen1__"], [d.uid for d in delegates])
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        yield txn.commit()  # So delegateService will see the changes
        txn = self.store.newTransaction(label="test_directDelegation")

        # The "proxy-write" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-write")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__sagen1__"]
        )
        # The "proxy-read" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegateGroup,
            u"__wsanchez1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__wsanchez1__#calendar-proxy-read")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )
        # The "proxy-write-for" pseudoGroup will have one member
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-write-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            [u"__wsanchez1__"]
        )
        # The "proxy-read-for" pseudoGroup will have no members
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.readDelegatorGroup,
            u"__sagen1__"
        )
        self.assertEquals(pseudoGroup.uid, u"__sagen1__#calendar-proxy-read-for")
        self.assertEquals(
            [r.uid for r in (yield pseudoGroup.members())],
            []
        )

        # Add another delegate
        yield Delegates.addDelegate(txn, delegator, delegate2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield Delegates.delegatedTo(txn, delegate2, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Remove 1 delegate
        yield Delegates.removeDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals([u"__cdaboo1__"], [d.uid for d in delegates])
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals(0, len(delegators))

        # Remove the other delegate
        yield Delegates.removeDelegate(txn, delegator, delegate2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(0, len(delegates))
        delegators = (yield Delegates.delegatedTo(txn, delegate2, True))
        self.assertEquals(0, len(delegators))

        yield txn.commit()  # So delegateService will see the changes

        # Now set delegate assignments by using pseudoGroup.setMembers()
        pseudoGroup = yield self.directory.recordWithShortName(
            DelegateRecordType.writeDelegateGroup,
            u"__wsanchez1__"
        )
        yield pseudoGroup.setMembers([delegate1, delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()

        # Set a different group of assignments:
        yield pseudoGroup.setMembers([delegate2])

        # Verify the assignments were made
        txn = self.store.newTransaction(label="test_directDelegation")
        delegates = (yield Delegates.delegatesOf(txn, delegator, True))
        self.assertEquals(
            set([u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()


    @inlineCallbacks
    def test_indirectDelegation(self):
        txn = self.store.newTransaction(label="test_indirectDelegation")

        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")
        group2 = yield self.directory.recordWithUID(u"__sub_group_1__")

        # Add group delegate
        yield Delegates.addDelegate(txn, delegator, group1, True)
        # Passing expanded=False will return the group
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=False))
        self.assertEquals(1, len(delegates))
        self.assertEquals(delegates[0].uid, u"__top_group_1__")
        # Passing expanded=True will return not the group -- it only returns
        # non-groups
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )
        delegators = (yield Delegates.delegatedTo(txn, delegate1, True))
        self.assertEquals([u"__wsanchez1__"], [d.uid for d in delegators])

        # Verify we can ask for all delegated-to groups
        yield Delegates.addDelegate(txn, delegator, group2, True)
        groups = (yield txn.allGroupDelegates())
        self.assertEquals(
            set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
        )

        # Delegate to a user who is already indirectly delegated-to
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__"]),
            set([d.uid for d in delegates])
        )

        # Add a member to the group; they become a delegate
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"sagen1", u"glyph1", u"dre1"):
            record = (
                yield self.directory.recordWithShortName(RecordType.user, name)
            )
            newSet.add(record.uid)
        group = yield txn.groupByUID(group1.uid)
        _ignore_added, _ignore_removed = (
            yield self.groupCacher.synchronizeMembers(txn, group.groupID, newSet)
        )
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__", u"__glyph1__", u"__dre1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the top group
        yield Delegates.removeDelegate(txn, delegator, group1, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__", u"__cdaboo1__"]),
            set([d.uid for d in delegates])
        )

        # Remove delegate access from the sub group
        yield Delegates.removeDelegate(txn, delegator, group2, True)
        delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
        self.assertEquals(
            set([u"__sagen1__"]),
            set([d.uid for d in delegates])
        )
        yield txn.commit()


    @inlineCallbacks
    def test_noDuplication(self):
        """
        Make sure addDelegate( ) is idempotent
        """
        delegator = yield self.directory.recordWithUID(u"__wsanchez1__")

        # Delegate users:
        delegate1 = yield self.directory.recordWithUID(u"__sagen1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, delegate1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = yield DelegateRecord.query(
            txn,
            (DelegateRecord.delegator == delegator.uid.encode("utf-8")).And(
                DelegateRecord.readWrite == 1
            )
        )
        yield txn.commit()
        self.assertEquals(["__sagen1__", ], [record.delegate for record in results])

        # Delegate groups:
        group1 = yield self.directory.recordWithUID(u"__top_group_1__")

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        yield Delegates.addDelegate(txn, delegator, group1, True)
        yield txn.commit()

        txn = self.store.newTransaction(label="test_noDuplication")
        results = yield DelegateGroupsRecord.delegateGroups(
            txn,
            delegator.uid,
            True,
        )
        yield txn.commit()
        self.assertEquals(["__top_group_1__", ], [record.groupUID for record in results])
Ejemplo n.º 7
0
class GroupCacherTest(StoreTestCase):

    @inlineCallbacks
    def setUp(self):
        yield super(GroupCacherTest, self).setUp()
        self.groupCacher = GroupCacher(self.directory)


    @inlineCallbacks
    def test_multipleCalls(self):
        """
        Ensure multiple calls to groupByUID() don't raise an exception
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield txn.groupByUID(record.uid)
        yield txn.groupByUID(record.uid)

        yield txn.commit()


    @inlineCallbacks
    def test_refreshGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"__top_group_1__")
        yield self.groupCacher.refreshGroup(txn, record.uid)

        group = (yield txn.groupByUID(record.uid))

        self.assertEquals(group.extant, True)
        self.assertEquals(group.membershipHash, "553eb54e3bbb26582198ee04541dbee4")

        group = yield txn.groupByID(group.groupID)
        self.assertEquals(group.groupUID, record.uid)
        self.assertEquals(group.name, u"Top Group 1")
        self.assertEquals(group.membershipHash, "553eb54e3bbb26582198ee04541dbee4")
        self.assertEquals(group.extant, True)

        members = (yield txn.groupMemberUIDs(group.groupID))
        self.assertEquals(
            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__']),
            members
        )

        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(
            set([r.uid for r in records]),
            set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'])
        )

        # sagen is in the top group, even though it's actually one level
        # removed
        record = yield self.directory.recordWithUID(u"__sagen1__")
        groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
        self.assertEquals(set([u"__top_group_1__"]), groups)

        yield txn.commit()


    @inlineCallbacks
    def test_synchronizeMembers(self):
        """
        After loading in a group via refreshGroup(), pass new member sets to
        synchronizeMembers() and verify members are added and removed as
        expected
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Refresh the group so it's assigned a group_id
        uid = u"__top_group_1__"
        yield self.groupCacher.refreshGroup(txn, uid)
        group = yield txn.groupByUID(uid)

        # Remove two members, and add one member
        newSet = set()
        for name in (u"wsanchez1", u"cdaboo1", u"dre1"):
            record = (
                yield self.directory.recordWithShortName(
                    RecordType.user,
                    name
                )
            )
            newSet.add(record.uid)
        added, removed = (
            yield self.groupCacher.synchronizeMembers(
                txn, group.groupID, newSet
            )
        )
        self.assertEquals(added, set(["__dre1__", ]))
        self.assertEquals(removed, set(["__glyph1__", "__sagen1__", ]))
        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(
            set([r.shortNames[0] for r in records]),
            set(["wsanchez1", "cdaboo1", "dre1"])
        )

        # Remove all members
        added, removed = (
            yield self.groupCacher.synchronizeMembers(txn, group.groupID, set())
        )
        self.assertEquals(added, set())
        self.assertEquals(removed, set(["__wsanchez1__", "__cdaboo1__", "__dre1__", ]))
        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
        self.assertEquals(len(records), 0)

        yield txn.commit()


    @inlineCallbacks
    def test_groupByID(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        # Non-existent groupID
        yield self.failUnlessFailure(txn.groupByID(42), NotFoundError)

        uid = u"__top_group_1__"
        hash = "553eb54e3bbb26582198ee04541dbee4"
        yield self.groupCacher.refreshGroup(txn, uid)
        group = yield txn.groupByUID(uid)
        group = yield txn.groupByID(group.groupID)
        self.assertEqual(group.groupUID, uid)
        self.assertEqual(group.name, u"Top Group 1")
        self.assertEqual(group.membershipHash, hash)
        self.assertEqual(group.extant, True)

        yield txn.commit()


    @inlineCallbacks
    def test_externalAssignments(self):

        store = self.storeUnderTest()
        txn = store.newTransaction()

        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(oldExternalAssignments, {})

        newAssignments = {
            u"__wsanchez1__": (None, u"__top_group_1__")
        }
        yield self.groupCacher.scheduleExternalAssignments(
            txn, newAssignments, immediately=True
        )
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    None,
                    u"__top_group_1__"
                )
            }
        )

        newAssignments = {
            u"__cdaboo1__":
            (
                u"__sub_group_1__",
                None
            ),
            u"__wsanchez1__":
            (
                u"__sub_group_1__",
                u"__top_group_1__"
            ),
        }

        yield self.groupCacher.scheduleExternalAssignments(
            txn, newAssignments, immediately=True
        )
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    u"__sub_group_1__",
                    u"__top_group_1__"
                ),
                u"__cdaboo1__":
                (
                    u"__sub_group_1__",
                    None
                )
            }
        )

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__top_group_1__",
                    u"__sub_group_1__"
                ]
            )
        )

        # Fault in the read-only group
        yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", False, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__sagen1__",
                    u"__cdaboo1__"
                ]
            )
        )

        # Fault in the read-write group
        yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")

        # Wilfredo should have 4 users as read-write delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", True, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__sagen1__",
                    u"__cdaboo1__",
                    u"__glyph1__"
                ]
            )
        )

        #
        # Now, remove some external assignments
        #
        newAssignments = {
            u"__wsanchez1__":
            (
                u"__sub_group_1__",
                None
            ),
        }
        yield self.groupCacher.scheduleExternalAssignments(
            txn, newAssignments, immediately=True
        )
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    u"__sub_group_1__",
                    None
                ),
            }
        )

        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__sub_group_1__"
                ]
            )
        )

        # Wilfredo should have Sagen and Daboo as read-only delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", False, expanded=True)
        )
        self.assertEquals(
            delegates,
            set(
                [
                    u"__sagen1__",
                    u"__cdaboo1__"
                ]
            )
        )

        # Wilfredo should have no read-write delegates
        delegates = (yield txn.delegates(
            u"__wsanchez1__", True, expanded=True)
        )
        self.assertEquals(
            delegates,
            set([])
        )

        # Only 1 group as delegate now:
        allGroupDelegates = (yield txn.allGroupDelegates())
        self.assertEquals(
            allGroupDelegates,
            set(
                [
                    u"__sub_group_1__"
                ]
            )
        )

        #
        # Say somebody messed up and stuck a non-existent group UID in
        # as a delegate
        #
        newAssignments = {
            u"__wsanchez1__":
            (
                u"__sub_group_1__",
                u"__non_existent_group__",
            ),
        }
        yield self.groupCacher.scheduleExternalAssignments(
            txn, newAssignments, immediately=True
        )
        oldExternalAssignments = (yield txn.externalDelegates())
        self.assertEquals(
            oldExternalAssignments,
            {
                u"__wsanchez1__":
                (
                    u"__sub_group_1__",
                    None  # <--- (not __non_existent_group__)
                ),
            }
        )

        yield txn.commit()


    def test_diffAssignments(self):
        """
        Ensure external proxy assignment diffing works
        """

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {},
                # new
                {}
            )
        )

        self.assertEquals(
            (
                # changed
                [],
                # removed
                [],
            ),
            diffAssignments(
                # old
                {"B": ("1", "2")},
                # new
                {"B": ("1", "2")},
            )
        )

        self.assertEquals(
            map(set, (
                # changed
                [("A", ("1", "2")), ("B", ("3", "4"))],
                # removed
                [],
            )),
            map(set, diffAssignments(
                # old
                {},
                # new
                {"A": ("1", "2"), "B": ("3", "4")}
            ))
        )

        self.assertEquals(
            map(set, (
                # changed
                [],
                # removed
                ["A", "B"],
            )),
            map(set, diffAssignments(
                # old
                {"A": ("1", "2"), "B": ("3", "4")},
                # new
                {},
            ))
        )

        self.assertEquals(
            map(set, (
                # changed
                [('C', ('4', '5')), ('D', ('7', '8'))],
                # removed
                ["B"],
            )),
            map(set, diffAssignments(
                # old
                {"A": ("1", "2"), "B": ("3", "4"), "C": ("5", "6")},
                # new
                {"D": ("7", "8"), "C": ("4", "5"), "A": ("1", "2")},
            ))
        )


    @inlineCallbacks
    def test_recursiveGroup(self):
        """
        Verify refreshGroup() adds a group to the Groups table with the
        expected membership hash value and members
        """

        store = self.storeUnderTest()
        txn = store.newTransaction()

        record = yield self.directory.recordWithUID(u"recursive1_coasts")
        members = yield record.expandedMembers()
        self.assertEquals(
            set([r.uid for r in members]),
            set([u'6423F94A-6B76-4A3A-815B-D52CFD77935D', u'5A985493-EE2C-4665-94CF-4DFEA3A89500'])
        )

        yield txn.commit()


    @inlineCallbacks
    def test_groupChangeCacheNotification(self):
        """
        Verify refreshGroup() triggers a cache notification for the group and all
        members that are added or removed
        """

        class TestNotifier(object):
            changedTokens = []
            def changed(self, token):
                self.changedTokens.append(token)

        self.groupCacher.cacheNotifier = TestNotifier()

        # No change
        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [])

        # Add member to group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.addMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove member from group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.removeMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Add member to sub-group
        record = yield self.directory.recordWithUID(u"__sub_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.addMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__sub_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove member from sub-group
        record = yield self.directory.recordWithUID(u"__sub_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__dre1__")
        yield record.removeMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__top_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
        yield self.commit()

        self.assertEqual(TestNotifier.changedTokens, [
            "__sub_group_1__",
            "__dre1__",
        ])
        TestNotifier.changedTokens = []

        # Remove sub-group member from group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
        yield record.removeMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(set(TestNotifier.changedTokens), set([
            "__top_group_1__",
            "__sagen1__",
            "__cdaboo1__",
        ]))
        TestNotifier.changedTokens = []

        # Add sub-group member to group
        record = yield self.directory.recordWithUID(u"__top_group_1__")
        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
        yield record.addMembers([addrecord, ])

        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
        yield self.commit()

        self.assertEqual(set(TestNotifier.changedTokens), set([
            "__top_group_1__",
            "__sagen1__",
            "__cdaboo1__",
        ]))
        TestNotifier.changedTokens = []