Repository: ckan/ckan

Issue:
Private datasets belonging to groups
1. User seanh creates organization MyOrg
2. Seanh adds MyPrivateDataset to MyOrg, private
3. Seanh creates group MyGroup
4. Seanh adds MyPrivateDataset to MyGroup
5. Seanh adds user Fred to MyGroup
6. Now if Fred visits the CKAN front page, he can see MyPrivateDataset even though he is not a member of MyOrg (he can't actually load the dataset's page though)



Discussion:
I can reproduce  this on 2.2.1 but not on master. Can't find the commit that fixed it. Is it worth digging deeper to find what fixed this, so we can backport it?

I would say it's worth it if you have some time


Resolving patch:
diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py
--- a/ckan/lib/dictization/model_dictize.py
+++ b/ckan/lib/dictization/model_dictize.py
@@ -366,10 +366,13 @@ def get_packages_for_this_group(group_):
             else:
                 q['fq'] = 'groups:"{0}"'.format(group_.name)
 
-            is_group_member = (context.get('user') and
-                new_authz.has_user_permission_for_group_or_org(group_.id, context.get('user'), 'read'))
-            if is_group_member:
-                context['ignore_capacity_check'] = True
+            # Allow members of organizations to see private datasets.
+            if group_.is_organization:
+                is_group_member = (context.get('user') and
+                    new_authz.has_user_permission_for_group_or_org(
+                        group_.id, context.get('user'), 'read'))
+                if is_group_member:
+                    context['ignore_capacity_check'] = True
 
             if not context.get('for_view'):
                 q['rows'] = 1000    # Only the first 1000 datasets are returned


Test patch:
diff --git a/ckan/new_tests/logic/action/test_get.py b/ckan/new_tests/logic/action/test_get.py
--- a/ckan/new_tests/logic/action/test_get.py
+++ b/ckan/new_tests/logic/action/test_get.py
@@ -424,6 +424,58 @@ def test_package_autocomplete_does_not_return_private_datasets(self):
                                            q='some')
         eq(len(package_list), 1)
 
+    def test_group_show_does_not_show_private_datasets(self):
+        '''group_show() should never show private datasets.
+
+        If a dataset is a private member of an organization and also happens to
+        be a member of a group, group_show() should not return the dataset as
+        part of the group dict, even if the user calling group_show() is a
+        member or admin of the group or the organization or is a sysadmin.
+
+        '''
+        org_member = factories.User()
+        org = factories.Organization(user=org_member)
+        private_dataset = factories.Dataset(user=org_member,
+                                            owner_org=org['name'], private=True)
+
+        group = factories.Group()
+
+        # Add the private dataset to the group.
+        helpers.call_action('member_create', id=group['id'],
+                            object=private_dataset['id'], object_type='package',
+                            capacity='public')
+
+        # Create a member user and an admin user of the group.
+        group_member = factories.User()
+        helpers.call_action('member_create', id=group['id'],
+                            object=group_member['id'], object_type='user',
+                            capacity='member')
+        group_admin = factories.User()
+        helpers.call_action('member_create', id=group['id'],
+                            object=group_admin['id'], object_type='user',
+                            capacity='admin')
+
+        # Create a user who isn't a member of any group or organization.
+        non_member = factories.User()
+
+        sysadmin = factories.Sysadmin()
+
+        # None of the users should see the dataset when they call group_show().
+        for user in (org_member, group_member, group_admin, non_member,
+                     sysadmin, None):
+
+            if user is None:
+                context = None  # No user logged-in.
+            else:
+                context = {'user': user['name']}
+
+            group = helpers.call_action('group_show', id=group['id'],
+                                        context=context)
+
+            assert private_dataset['id'] not in [dataset['id'] for dataset
+                                                 in group['packages']], (
+                "group_show() should never show private datasets")
+
 
 class TestBadLimitQueryParameters(object):
     '''test class for #1258 non-int query parameters cause 500 errors