Repository: ckan/ckan

Issue:
Restrict access to forms
Resource edit and add member page can be accessed knowing the URL. No action can be saved but it's best to allow access at all.


Discussion:

Resolving patch:
diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py
--- a/ckan/controllers/group.py
+++ b/ckan/controllers/group.py
@@ -670,8 +670,11 @@ def member_new(self, id):
 
         context = {'model': model, 'session': model.Session,
                    'user': c.user}
+        try:
+            self._check_access('group_member_create', context, {'id': id})
+        except NotAuthorized:
+            abort(403, _('Unauthorized to create group %s members') % '')
 
-        # self._check_access('group_delete', context, {'id': id})
         try:
             data_dict = {'id': id}
             data_dict['include_datasets'] = False
diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -546,6 +546,16 @@ def new(self, data=None, errors=None, error_summary=None):
     def resource_edit(self, id, resource_id, data=None, errors=None,
                       error_summary=None):
 
+        context = {'model': model, 'session': model.Session,
+                   'api_version': 3, 'for_edit': True,
+                   'user': c.user, 'auth_user_obj': c.userobj}
+        data_dict = {'id': id}
+
+        try:
+            check_access('package_update', context, data_dict)
+        except NotAuthorized:
+            abort(403, _('User %r not authorized to edit %s') % (c.user, id))
+
         if request.method == 'POST' and not data:
             data = data or \
                 clean_dict(dict_fns.unflatten(tuplize_dict(parse_params(
@@ -553,10 +563,6 @@ def resource_edit(self, id, resource_id, data=None, errors=None,
             # we don't want to include save as it is part of the form
             del data['save']
 
-            context = {'model': model, 'session': model.Session,
-                       'api_version': 3, 'for_edit': True,
-                       'user': c.user, 'auth_user_obj': c.userobj}
-
             data['package_id'] = id
             try:
                 if resource_id:
@@ -574,9 +580,6 @@ def resource_edit(self, id, resource_id, data=None, errors=None,
             h.redirect_to(controller='package', action='resource_read', id=id,
                           resource_id=resource_id)
 
-        context = {'model': model, 'session': model.Session,
-                   'api_version': 3, 'for_edit': True,
-                   'user': c.user, 'auth_user_obj': c.userobj}
         pkg_dict = get_action('package_show')(context, {'id': id})
         if pkg_dict['state'].startswith('draft'):
             # dataset has not yet been fully created


Test patch:
diff --git a/ckan/tests/controllers/test_group.py b/ckan/tests/controllers/test_group.py
--- a/ckan/tests/controllers/test_group.py
+++ b/ckan/tests/controllers/test_group.py
@@ -409,6 +409,62 @@ def test_remove_member(self):
         assert_equal(len(user_roles.keys()), 1)
         assert_equal(user_roles['User One'], 'Admin')
 
+    def test_member_users_cannot_add_members(self):
+
+        user = factories.User()
+        group = factories.Group(
+            users=[{'name': user['name'], 'capacity': 'member'}]
+        )
+
+        app = helpers._get_test_app()
+
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+
+        app.get(
+            url_for(
+                controller='group',
+                action='member_new',
+                id=group['id'],
+            ),
+            extra_environ=env,
+            status=403,
+        )
+
+        app.post(
+            url_for(
+                controller='group',
+                action='member_new',
+                id=group['id'],
+            ),
+            {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'},
+            extra_environ=env,
+            status=403,
+        )
+
+    def test_anonymous_users_cannot_add_members(self):
+        group = factories.Group()
+
+        app = helpers._get_test_app()
+
+        app.get(
+            url_for(
+                controller='group',
+                action='member_new',
+                id=group['id'],
+            ),
+            status=403,
+        )
+
+        app.post(
+            url_for(
+                controller='group',
+                action='member_new',
+                id=group['id'],
+            ),
+            {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'},
+            status=403,
+        )
+
 
 class TestGroupFollow(helpers.FunctionalTestBase):
 
diff --git a/ckan/tests/controllers/test_organization.py b/ckan/tests/controllers/test_organization.py
--- a/ckan/tests/controllers/test_organization.py
+++ b/ckan/tests/controllers/test_organization.py
@@ -444,3 +444,94 @@ def test_organization_search_within_org_no_results(self):
         ds_titles = [t.string for t in ds_titles]
 
         assert_equal(len(ds_titles), 0)
+
+
+class TestOrganizationMembership(helpers.FunctionalTestBase):
+
+    def test_editor_users_cannot_add_members(self):
+
+        user = factories.User()
+        organization = factories.Organization(
+            users=[{'name': user['name'], 'capacity': 'editor'}]
+        )
+
+        app = helpers._get_test_app()
+
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+
+        app.get(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            extra_environ=env,
+            status=403,
+        )
+
+        app.post(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'},
+            extra_environ=env,
+            status=403,
+        )
+
+    def test_member_users_cannot_add_members(self):
+
+        user = factories.User()
+        organization = factories.Organization(
+            users=[{'name': user['name'], 'capacity': 'member'}]
+        )
+
+        app = helpers._get_test_app()
+
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+
+        app.get(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            extra_environ=env,
+            status=403,
+        )
+
+        app.post(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'},
+            extra_environ=env,
+            status=403,
+        )
+
+    def test_anonymous_users_cannot_add_members(self):
+        organization = factories.Organization()
+
+        app = helpers._get_test_app()
+
+        app.get(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            status=403,
+        )
+
+        app.post(
+            url_for(
+                controller='organization',
+                action='member_new',
+                id=organization['id'],
+            ),
+            {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'},
+            status=403,
+        )
diff --git a/ckan/tests/controllers/test_package.py b/ckan/tests/controllers/test_package.py
--- a/ckan/tests/controllers/test_package.py
+++ b/ckan/tests/controllers/test_package.py
@@ -993,6 +993,35 @@ def test_anonymous_users_cannot_add_new_resource(self):
             status=403,
         )
 
+    def test_anonymous_users_cannot_edit_resource(self):
+        organization = factories.Organization()
+        dataset = factories.Dataset(
+            owner_org=organization['id'],
+        )
+        resource = factories.Resource(package_id=dataset['id'])
+        app = helpers._get_test_app()
+
+        response = app.get(
+            url_for(
+                controller='package',
+                action='resource_edit',
+                id=dataset['id'],
+                resource_id=resource['id'],
+            ),
+            status=403,
+        )
+
+        response = app.post(
+            url_for(
+                controller='package',
+                action='resource_edit',
+                id=dataset['id'],
+                resource_id=resource['id'],
+            ),
+            {'name': 'test', 'url': 'test', 'save': 'save', 'id': ''},
+            status=403,
+        )
+
 
 class TestResourceView(helpers.FunctionalTestBase):
     @classmethod
@@ -1298,7 +1327,7 @@ def test_confirm_and_cancel_deleting_a_resource(self):
         # cancelling sends us back to the resource edit page
         form = response.forms['confirm-resource-delete-form']
         response = form.submit('cancel')
-        response = response.follow()
+        response = response.follow(extra_environ=env)
         assert_equal(200, response.status_int)