[PATCH 1 of 1 sherpa V3] [schema] Implement security for top level types

Sylvain Thenault sylvain.thenault at logilab.fr
Thu Mar 9 10:09:10 CET 2017


# HG changeset patch
# User Philippe Pepiot <philippe.pepiot at logilab.fr>
# Date 1489050349 -3600
#      Thu Mar 09 10:05:49 2017 +0100
# Node ID fc00172aaa22ce94bd2b797095d45f8d044b7d87
# Parent  bf3fa11e713283497dbc332a719e579b5205b336
[schema] Implement security for top level types

(ArchiveTransfer, ArchiveUnit, AuthorityRecord and ConceptScheme).

For now, we only need to setup ConceptScheme compound security since it's not done
yet by the skos cube, while it is in eac and seda cubes.

Closes #16684441

diff --git a/cubicweb_sherpa/migration/0.8.0_Any.py b/cubicweb_sherpa/migration/0.8.0_Any.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_sherpa/migration/0.8.0_Any.py
@@ -0,0 +1,13 @@
+from cubicweb_compound import utils, CompositeGraph
+
+graph = CompositeGraph(schema)
+structure = graph.parent_structure('ConceptScheme')
+
+optionals = utils.optional_relations(schema, structure)
+for child, relations in structure.iteritems():
+    sync_schema_props_perms(child, syncprops=False)
+
+for rdef, parent_role in utils.mandatory_rdefs(schema, structure):
+    sync_schema_props_perms((rdef.subject, rdef.rtype, rdef.object), syncprops=False)
+
+sync_schema_props_perms('SEDAArchiveUnit', syncprops=False)
diff --git a/cubicweb_sherpa/schema.py b/cubicweb_sherpa/schema.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_sherpa/schema.py
@@ -0,0 +1,40 @@
+# copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from cubicweb.schema import ERQLExpression
+
+from cubes.skos.schema import ConceptScheme
+
+ConceptScheme.__permissions__ = {
+    'read': ('managers', 'users', 'guests'),
+    'add': ('managers',),
+    'update': ('managers',),
+    'delete': ('managers',),
+}
+
+
+def post_build_callback(schema):
+    from cubicweb_compound import CompositeGraph, utils
+    graph = CompositeGraph(schema)
+    utils.graph_set_etypes_update_permissions(schema, graph, 'ConceptScheme')
+    utils.graph_set_write_rdefs_permissions(schema, graph, 'ConceptScheme')
+
+    for action in ('update', 'delete'):
+        schema['SEDAArchiveUnit'].set_action_permissions(
+            action, ('managers',
+                     ERQLExpression('U has_{action}_permission C, '
+                                    'X container C'.format(action=action)),
+                     ERQLExpression('NOT EXISTS(X container C), X owned_by U')))
diff --git a/dev-requirements.txt b/dev-requirements.txt
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,1 +1,2 @@
 pytest
+http://hg.logilab.org/review/cubes/compound/archive/tip.tar.bz2#egg=cubicweb-compound
diff --git a/test/test_security.py b/test/test_security.py
new file mode 100644
--- /dev/null
+++ b/test/test_security.py
@@ -0,0 +1,116 @@
+# copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb-sherpa security tests"""
+
+from contextlib import contextmanager
+
+from cubicweb import Unauthorized
+from cubicweb.devtools.testlib import CubicWebTC
+
+
+class SecurityTC(CubicWebTC):
+
+    @contextmanager
+    def assertUnauthorized(self, cnx):
+        with self.assertRaises(Unauthorized) as cm:
+            yield cm
+            cnx.commit()
+        cnx.rollback()
+
+    def setUp(self):
+        super(SecurityTC, self).setUp()
+        with self.admin_access.cnx() as cnx:
+            self.create_user(cnx, 'bob')
+            self.create_user(cnx, 'alice')
+
+    def assertOwnershipBasedAccess(self, etype, to_test_attribute):
+        # alice can read bob entity, but not update nor delete
+        with self.new_access('alice').cnx() as cnx:
+            entity = cnx.find(etype).one()
+            with self.assertUnauthorized(cnx):
+                entity.cw_set(**{to_test_attribute: u'abcd'})
+                cnx.commit()
+            with self.assertUnauthorized(cnx):
+                entity.cw_delete()
+                cnx.commit()
+        # unless explicitly authorized using owned_by relation
+        with self.new_access('bob').cnx() as cnx:
+            entity = cnx.find(etype).one()
+            entity.cw_set(owned_by=cnx.find('CWUser', login='alice').one())
+            cnx.commit()
+        with self.new_access('alice').cnx() as cnx:
+            entity = cnx.find(etype).one()
+            entity.cw_set(**{to_test_attribute: u'abcd'})
+            cnx.commit()
+            entity.cw_delete()
+            cnx.commit()
+
+    def test_add_read_update_delete_record(self):
+        with self.new_access('bob').cnx() as cnx:
+            agent_kind = cnx.find('AgentKind', name=u'person').one()
+            record = cnx.create_entity('AuthorityRecord', agent_kind=agent_kind)
+            cnx.create_entity('NameEntry', parts=u'bob notice', form_variant=u'authorized',
+                              name_entry_for=record)
+            cnx.commit()
+
+        self.assertOwnershipBasedAccess('AuthorityRecord', 'isni')
+
+    def test_add_read_update_delete_transfer(self):
+        with self.new_access('bob').cnx() as cnx:
+            cnx.create_entity('SEDAArchiveTransfer', title=u'goldorak go')
+            cnx.commit()
+
+        self.assertOwnershipBasedAccess('SEDAArchiveTransfer', 'title')
+
+    def test_add_read_update_delete_unit(self):
+        with self.new_access('bob').cnx() as cnx:
+            au = cnx.create_entity('SEDAArchiveUnit', user_annotation=u'title')
+            alt = cnx.create_entity('SEDAAltArchiveUnitArchiveUnitRefId',
+                                    reverse_seda_alt_archive_unit_archive_unit_ref_id=au)
+            last = cnx.create_entity(
+                'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement',
+                reverse_seda_seq_alt_archive_unit_archive_unit_ref_id_management=alt)
+            cnx.create_entity('SEDATitle', seda_title=last)
+            cnx.commit()
+
+        self.assertOwnershipBasedAccess('SEDAArchiveUnit', 'user_annotation')
+
+    def test_add_read_update_delete_schemes(self):
+        with self.new_access('bob').cnx() as cnx:
+            with self.assertUnauthorized(cnx):
+                cnx.create_entity('ConceptScheme', title=u'goldorak')
+
+        with self.admin_access.cnx() as cnx:
+            scheme = cnx.create_entity('ConceptScheme', title=u'goldorak')
+            concept = scheme.add_concept(u'fulguropoint')
+            cnx.commit()
+
+        with self.new_access('bob').cnx() as cnx:
+            scheme = cnx.find('ConceptScheme').one()
+            with self.assertUnauthorized(cnx):
+                scheme.cw_set(title=u'hop')
+            with self.assertUnauthorized(cnx):
+                scheme.add_concept(u'fulguropoint')
+            concept = cnx.find('Concept').one()
+            with self.assertUnauthorized(cnx):
+                concept.cw_delete()
+            with self.assertUnauthorized(cnx):
+                scheme.cw_delete()
+
+
+if __name__ == '__main__':
+    import unittest
+    unittest.main()



More information about the saem-devel mailing list