[PATCH 4 of 4 eac] [schema] Backport security settings from the saem_ref cube

Sylvain Thenault sylvain.thenault at logilab.fr
Wed Mar 8 10:13:16 CET 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1488958771 -3600
#      Wed Mar 08 08:39:31 2017 +0100
# Node ID f8611ef99b97cec2af1792d3c0e70a1134f77b34
# Parent  47724158121da77c11fa61c543d855a7a4842fc6
[schema] Backport security settings from the saem_ref cube

introducing a dependency to the compound cube.

diff --git a/cubicweb-eac.spec b/cubicweb-eac.spec
--- a/cubicweb-eac.spec
+++ b/cubicweb-eac.spec
@@ -22,10 +22,11 @@ BuildRoot:      %{_tmppath}/%{name}-%{ve
 BuildRequires:  %{python} %{python}-setuptools
 Requires:       cubicweb >= 3.24.0
 Requires:       cubicweb-prov >= 0.4.0
 Requires:       cubicweb-skos
 Requires:       cubicweb-addressbook
+Requires:       cubicweb-compound
 Requires:       %{python}-six >= 1.4.0
 
 %description
 Implementation of Encoded Archival Context for CubicWeb
 
diff --git a/cubicweb_eac/__init__.py b/cubicweb_eac/__init__.py
--- a/cubicweb_eac/__init__.py
+++ b/cubicweb_eac/__init__.py
@@ -1,10 +1,13 @@
 """cubicweb-eac application package
 
 Implementation of Encoded Archival Context for CubicWeb
 """
 
+from functools import partial
+from cubicweb_compound import CompositeGraph
+
 
 # EAC mappings
 
 TYPE_MAPPING = {
     'corporateBody': u'authority',
@@ -21,5 +24,8 @@ MAINTENANCETYPE_MAPPING = {
 ADDRESS_MAPPING = [
     ('StreetName', 'street'),
     ('PostCode', 'postalcode'),
     ('CityName', 'city'),
 ]
+
+
+AuthorityRecordGraph = partial(CompositeGraph, skiprtypes=('generated', 'used'))
diff --git a/cubicweb_eac/__pkginfo__.py b/cubicweb_eac/__pkginfo__.py
--- a/cubicweb_eac/__pkginfo__.py
+++ b/cubicweb_eac/__pkginfo__.py
@@ -17,10 +17,11 @@ web = 'http://www.cubicweb.org/project/%
     'cubicweb': '>= 3.24.0',
     'six': '>= 1.4.0',
     'cubicweb-prov': '>= 0.4.0',
     'cubicweb-skos': None,
     'cubicweb-addressbook': None,
+    'cubicweb-compound': None,
     'python-dateutil': None,
 }
 __recommends__ = {}
 
 classifiers = [
diff --git a/cubicweb_eac/hooks.py b/cubicweb_eac/hooks.py
--- a/cubicweb_eac/hooks.py
+++ b/cubicweb_eac/hooks.py
@@ -41,5 +41,19 @@ class EnsureVocabularySourceOp(hook.Data
     def precommit_event(self):
         cnx = self.cnx
         for eid, concept_eid in self.get_data():
             cnx.execute('SET X vocabulary_source SC WHERE NOT X vocabulary_source SC, '
                         'C in_scheme SC, C eid %(c)s, X eid %(x)s', {'x': eid, 'c': concept_eid})
+
+
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__)
+
+    # Add relations involved in a composite graph with security setup to "on
+    # commit" check step.
+    from cubicweb.server import ON_COMMIT_ADD_RELATIONS
+    from cubicweb_compound.lib import mandatory_rdefs
+    from cubicweb_eac import AuthorityRecordGraph
+
+    graph = AuthorityRecordGraph(vreg.schema)
+    for rdef, __ in mandatory_rdefs(vreg.schema, graph.parent_structure('AuthorityRecord')):
+        ON_COMMIT_ADD_RELATIONS.add(rdef.rtype)
diff --git a/cubicweb_eac/migration/0.5.0_Any.py b/cubicweb_eac/migration/0.5.0_Any.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/migration/0.5.0_Any.py
@@ -0,0 +1,1 @@
+add_cube('compound')
diff --git a/cubicweb_eac/schema.py b/cubicweb_eac/schema.py
--- a/cubicweb_eac/schema.py
+++ b/cubicweb_eac/schema.py
@@ -393,5 +393,15 @@ class equivalent_concept(RelationDefinit
     constraints = [RQLVocabularyConstraint('S vocabulary_source SC, O in_scheme SC')]
     cardinality = '?*'
     # relation with 'ExternalUri' as object can't be inlined because of a limitation of
     # data-import's (massive) store
     inlined = False
+
+
+def post_build_callback(schema):
+    from cubicweb_eac import AuthorityRecordGraph
+    from cubicweb_compound.lib import (graph_set_etypes_update_permissions,
+                                       graph_set_write_rdefs_permissions)
+
+    graph = AuthorityRecordGraph(schema)
+    graph_set_etypes_update_permissions(schema, graph, 'AuthorityRecord')
+    graph_set_write_rdefs_permissions(schema, graph, 'AuthorityRecord')
diff --git a/debian/control b/debian/control
--- a/debian/control
+++ b/debian/control
@@ -15,10 +15,11 @@ Depends:
  python-cubicweb (>= 3.24.0),
  python-six (>= 1.4.0),
  cubicweb-prov (>= 0.4.0),
  cubicweb-skos,
  cubicweb-addressbook,
+ cubicweb-compound,
  ${python:Depends},
  ${misc:Depends},
 Description: Implementation of Encoded Archival Context for CubicWeb
  CubicWeb is a semantic web application framework.
  .
diff --git a/dev-requirements.txt b/dev-requirements.txt
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,3 +1,4 @@
 pytest
 mock
 docutils
+http://hg.logilab.org/review/cubes/compound/archive/tip.tar.bz2#egg=cubicweb-compound
diff --git a/test/test_schema.py b/test/test_schema.py
--- a/test/test_schema.py
+++ b/test/test_schema.py
@@ -18,14 +18,15 @@
 import sqlite3
 import unittest
 from datetime import date
 from contextlib import contextmanager
 
-from cubicweb import ValidationError
+from cubicweb import ValidationError, Unauthorized
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubicweb_eac import testutils
+from cubicweb_compound.lib import optional_relations, graph_relations
+from cubicweb_eac import testutils, AuthorityRecordGraph
 
 
 @contextmanager
 def assertValidationError(self, cnx):
     with self.assertRaises(ValidationError) as cm:
@@ -77,7 +78,115 @@ class SchemaConstraintsTC(CubicWebTC):
             with self.assertValidationError(cnx) as cm:
                 agent.cw_set(start_date=date(527, 4, 12))
             self.assertIn("must be less than", str(cm.exception))
 
 
+class AuthorityRecordGraphTC(CubicWebTC):
+
+    def test_graph_structure(self):
+        graph = AuthorityRecordGraph(self.schema)
+        expected = {
+            'AgentFunction': {('function_agent', 'subject'): set(['AuthorityRecord'])},
+            'AgentPlace': {('place_agent', 'subject'): set(['AuthorityRecord'])},
+            'Citation': {('has_citation', 'object'): set([
+                'GeneralContext', 'Mandate', 'Occupation', 'AgentFunction',
+                'AgentPlace', 'History', 'LegalStatus',
+            ])},
+            'EACOtherRecordId': {('eac_other_record_id_of', 'subject'):
+                                 set(['AuthorityRecord'])},
+            'EACResourceRelation': {('resource_relation_agent', 'subject'):
+                                    set(['AuthorityRecord'])},
+            'EACSource': {('source_agent', 'subject'): set(['AuthorityRecord'])},
+            'GeneralContext': {('general_context_of', 'subject'): set(['AuthorityRecord'])},
+            'History': {('history_agent', 'subject'): set(['AuthorityRecord'])},
+            'LegalStatus': {('legal_status_agent', 'subject'): set(['AuthorityRecord'])},
+            'Mandate': {('mandate_agent', 'subject'): set(['AuthorityRecord'])},
+            'NameEntry': {('name_entry_for', 'subject'): set(['AuthorityRecord'])},
+            'Occupation': {('occupation_agent', 'subject'): set(['AuthorityRecord'])},
+            'PostalAddress': {('place_address', 'object'): set(['AgentPlace'])},
+            'Structure': {('structure_agent', 'subject'): set(['AuthorityRecord'])},
+        }
+        struct = dict(
+            (k, dict((rel, set(targets)) for rel, targets in v.items()))
+            for k, v in graph.parent_structure('AuthorityRecord').items())
+        self.assertEqual(struct, expected)
+
+    def test_optional_relations(self):
+        graph = AuthorityRecordGraph(self.schema)
+        structure = graph.parent_structure('AuthorityRecord')
+        opts = optional_relations(self.schema, structure)
+        expected = {}
+        self.assertEqual(opts, expected)
+
+    def test_relations_consistency(self):
+        graph = AuthorityRecordGraph(self.schema)
+        structure = graph.parent_structure('AuthorityRecord')
+        structurals, optionals, mandatories = graph_relations(
+            self.schema, structure)
+        self.assertEqual(structurals - optionals, mandatories)
+
+
+class SecurityTC(CubicWebTC):
+    """Test case for permissions set in the schema"""
+
+    @contextmanager
+    def assertUnauthorized(self, cnx):
+        with self.assertRaises(Unauthorized) as cm:
+            yield cm
+            cnx.commit()
+        cnx.rollback()
+
+    def test_agentkind_type(self):
+        with self.admin_access.cnx() as cnx:
+            kind = cnx.find('AgentKind', name=u'person').one()
+            # no one can update nor delete a kind
+            with self.assertUnauthorized(cnx):
+                kind.cw_set(name=u'gloups')
+            with self.assertUnauthorized(cnx):
+                kind.cw_delete()
+
+        with self.admin_access.cnx() as cnx:
+            self.create_user(cnx, login=u'toto', groups=('users', 'guests'))
+            cnx.commit()
+        with self.new_access('toto').cnx() as cnx:
+            cnx.create_entity('AgentKind', name=u'new')
+
+    def test_agent_kind_relation(self):
+        """Test we can only change kind from unknown to another."""
+        with self.admin_access.cnx() as cnx:
+            record = testutils.authority_record(cnx, u'bob', kind=u'unknown-agent-kind')
+            cnx.commit()
+            record.cw_set(agent_kind=cnx.find('AgentKind', name=u'person').one())
+            cnx.commit()
+            with self.assertRaises(Unauthorized):
+                record.cw_set(agent_kind=cnx.find('AgentKind', name=u'authority').one())
+
+    def test_authority_record_base(self):
+        with self.admin_access.cnx() as cnx:
+            self.create_user(cnx, login=u'toto', groups=('users', 'guests'))
+            function = cnx.create_entity('AgentFunction', name=u'director')
+            testutils.authority_record(cnx, u'admin record', reverse_function_agent=function)
+            cnx.commit()
+
+        with self.new_access('toto').cnx() as cnx:
+            # user can create and modify its own records
+            function = cnx.create_entity('AgentFunction', name=u'grouillot')
+            record = testutils.authority_record(cnx, u'bob', reverse_function_agent=function)
+            cnx.commit()
+            function.cw_set(name=u'grouyo')
+            cnx.commit()
+            cnx.create_entity('GeneralContext', content=u'plop',
+                              general_context_of=record)
+            cnx.commit()
+            # but not modify other's
+            record = cnx.find('AuthorityRecord', has_text=u'admin record').one()
+            with self.assertUnauthorized(cnx):
+                record.cw_set(record_id=u'bobby')
+            with self.assertUnauthorized(cnx):
+                record.reverse_function_agent[0].cw_set(name=u'grouillot')
+            with self.assertUnauthorized(cnx):
+                function = cnx.create_entity('AgentFunction', name=u'grouillot')
+                record.cw_set(reverse_function_agent=function)
+
+
 if __name__ == '__main__':
     unittest.main()


More information about the saem-devel mailing list