[CalendarServer-changes] [14946] CalendarServer/trunk/txdav

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 6 08:18:42 PDT 2015


Revision: 14946
          http://trac.calendarserver.org//changeset/14946
Author:   cdaboo at apple.com
Date:     2015-07-06 08:18:42 -0700 (Mon, 06 Jul 2015)
Log Message:
-----------
Fix group sharing with multiple groups.

Modified Paths:
--------------
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/who/test/test_group_sharees.py

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2015-07-06 15:16:47 UTC (rev 14945)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2015-07-06 15:18:42 UTC (rev 14946)
@@ -82,7 +82,8 @@
     UnknownTimezone, SetComponentOptions
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
     CommonObjectResource, ECALENDARTYPE
-from txdav.common.datastore.sql_directory import GroupsRecord
+from txdav.common.datastore.sql_directory import GroupsRecord, \
+    GroupMembershipRecord
 from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
     _ATTACHMENTS_MODE_READ, _ATTACHMENTS_MODE_WRITE, _BIND_MODE_DIRECT, \
     _BIND_MODE_GROUP, _BIND_MODE_GROUP_READ, _BIND_MODE_GROUP_WRITE, \
@@ -2127,34 +2128,53 @@
 
         # First check that the actual group membership has changed
         if (yield self.updateShareeGroupLink(groupUID)):
-            group = yield self._txn.groupByUID(groupUID)
-            memberUIDs = yield self._txn.groupMemberUIDs(group.groupID)
+
+            # First find all the members of all the groups being shared to
+            groupMembers = yield GroupMembershipRecord.query(
+                self._txn,
+                GroupMembershipRecord.groupID.In(
+                    GroupShareeRecord.queryExpr(
+                        expr=GroupShareeRecord.calendarID == self.id(),
+                        attributes=[GroupShareeRecord.groupID, ]
+                    )
+                ),
+                distinct=True,
+            )
+            memberUIDs = set([grp.memberUID for grp in groupMembers])
+
+            # Find each currently bound group sharee UID along with their bind mode
             boundUIDs = set()
-
             home = self._homeSchema
             bind = self._bindSchema
             rows = yield Select(
-                [home.OWNER_UID],
-                From=home,
-                Where=home.RESOURCE_ID.In(
-                    Select(
-                        [bind.HOME_RESOURCE_ID],
-                        From=bind,
-                        Where=(bind.CALENDAR_RESOURCE_ID == self._resourceID).And(
-                            (bind.BIND_MODE == _BIND_MODE_GROUP)
-                            .Or(bind.BIND_MODE == _BIND_MODE_GROUP_READ)
-                            .Or(bind.BIND_MODE == _BIND_MODE_GROUP_WRITE)
-                        )
-                    )
+                [home.OWNER_UID, bind.BIND_MODE],
+                From=bind.join(home, on=bind.HOME_RESOURCE_ID == home.RESOURCE_ID),
+                Where=(bind.CALENDAR_RESOURCE_ID == self._resourceID).And(
+                    bind.BIND_MODE.In((_BIND_MODE_GROUP, _BIND_MODE_GROUP_READ, _BIND_MODE_GROUP_WRITE))
                 )
             ).on(self._txn)
-            for [shareeHomeUID] in rows:
+            for shareeHomeUID, shareeBindMode in rows:
+
                 if shareeHomeUID in memberUIDs:
+                    # Group sharee still referenced via a group - make a note of it
                     boundUIDs.add(shareeHomeUID)
-                else:
+
+                elif shareeBindMode == _BIND_MODE_GROUP:
+                    # Group only sharee is no longer referenced by any group - uninvite them
                     yield self.uninviteUIDFromShare(shareeHomeUID)
                     changed = True
 
+                else:
+                    # Group+individual sharee is no longer referenced by a group so update the bind
+                    # mode to reflect just the individual mode
+                    yield super(Calendar, self).inviteUIDToShare(
+                        shareeHomeUID,
+                        {
+                            _BIND_MODE_GROUP_READ: _BIND_MODE_READ,
+                            _BIND_MODE_GROUP_WRITE: _BIND_MODE_WRITE,
+                        }.get(shareeBindMode),
+                    )
+
             for memberUID in memberUIDs - boundUIDs:
                 # Never reconcile the sharer
                 if memberUID != self._home.uid():

Modified: CalendarServer/trunk/txdav/who/test/test_group_sharees.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_group_sharees.py	2015-07-06 15:16:47 UTC (rev 14945)
+++ CalendarServer/trunk/txdav/who/test/test_group_sharees.py	2015-07-06 15:18:42 UTC (rev 14946)
@@ -28,7 +28,8 @@
 from txdav.who.directory import CalendarDirectoryRecordMixin
 from txdav.who.groups import GroupCacher, GroupShareeReconciliationWork
 import os
-from txdav.common.datastore.sql_tables import _BIND_MODE_GROUP
+from txdav.common.datastore.sql_tables import _BIND_MODE_GROUP, _BIND_MODE_WRITE, \
+    _BIND_MODE_GROUP_READ
 from txdav.common.datastore.sql_tables import _BIND_MODE_READ
 from txdav.common.datastore.sql_tables import _BIND_STATUS_INVITED
 
@@ -68,6 +69,7 @@
     requirements = {
         "user01" : None,
         "user02" : None,
+        "user03" : None,
         "user06" : None,
         "user07" : None,
         "user08" : None,
@@ -670,3 +672,177 @@
         self.assertEqual(len(wps), 0)
         yield self.commit()
         yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+
+    @inlineCallbacks
+    def test_multiple_groups_remove_from_one_group(self):
+        """
+        Test that a multi-group share each containing the same user still lists the user
+        when they are removed from one group.
+        """
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == "group05":
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group05")
+        self.assertEqual(len(wps), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group06")
+        self.assertEqual(len(wps), 0)
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeViews = yield calendar.inviteUIDToShare("group05", _BIND_MODE_WRITE)
+        self.assertEqual(len(shareeViews), 2)
+        shareeViews = yield calendar.inviteUIDToShare("group06", _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, "user03")
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_WRITE)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 2)
+
+        # Change group membership
+        self.patch(CalendarDirectoryRecordMixin, "expandedMembers", expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group05")
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            self.assertEqual(invite.shareeUID, "user02")
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, "user03")
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        yield calendar.uninviteUIDFromShare("group05")
+        yield calendar.uninviteUIDFromShare("group06")
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
+
+
+    @inlineCallbacks
+    def test_multiple_groups_with_individual_remove_from_one_group(self):
+        """
+        Test that a multi-group share each containing the same user still lists the user
+        when they are removed from one group.
+        """
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == "group05":
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group05")
+        self.assertEqual(len(wps), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group06")
+        self.assertEqual(len(wps), 0)
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUIDToShare("user01", _BIND_MODE_READ)
+        self.assertTrue(shareeView is not None)
+        shareeViews = yield calendar.inviteUIDToShare("group05", _BIND_MODE_WRITE)
+        self.assertEqual(len(shareeViews), 2)
+        shareeViews = yield calendar.inviteUIDToShare("group06", _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, "user03")
+            self.assertEqual(invite.uid, shareeView.shareName())
+            if invite.shareeUID == "user01":
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP_READ)
+            else:
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_WRITE)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 2)
+
+        # Change group membership
+        self.patch(CalendarDirectoryRecordMixin, "expandedMembers", expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "group05")
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, "user03")
+            self.assertEqual(invite.uid, shareeView.shareName())
+            if invite.shareeUID == "user01":
+                self.assertEqual(invite.mode, _BIND_MODE_READ)
+            else:
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home="user03", name="calendar")
+        yield calendar.uninviteUIDFromShare("user01")
+        yield calendar.uninviteUIDFromShare("group05")
+        yield calendar.uninviteUIDFromShare("group06")
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150706/a6b3d17a/attachment-0001.html>


More information about the calendarserver-changes mailing list