Attached Documents

patch series (4919855.patch)

patch series (4919855.patch)

download
# HG changeset patch
# User Christophe de Vienne <christophe@unlish.com>
# Date 1423174237 -3600
#      Thu Feb 05 23:10:37 2015 +0100
# Node ID df655cf48679c0863b660908b72467c2bde084cf
# Parent  a0a11be5a9cb982e2d95eceb33ee043c5cc336ee
[wip] Introduce a security_ctx in rqlrewrite

This patch is only a proof-of-concept which demonstrate how the rqlrewriter
could make use of variables from security context.

Related to #4919855

diff --git a/rqlrewrite.py b/rqlrewrite.py
--- a/rqlrewrite.py
+++ b/rqlrewrite.py
@@ -225,12 +225,13 @@ class RQLRewriter(object):
     This class *isn't thread safe*.
     """
 
-    def __init__(self, session):
+    def __init__(self, session, security_ctx=None):
         self.session = session
         vreg = session.vreg
         self.schema = vreg.schema
         self.annotate = vreg.rqlhelper.annotate
         self._compute_solutions = vreg.solutions
+        self.security_ctx = security_ctx
 
     def compute_solutions(self):
         self.annotate(self.select)
@@ -763,6 +764,16 @@ class RQLRewriter(object):
                     'eid', unicode(argname), 'Substitute')
                 self.kwargs[argname] = self.session.user.eid
             return self.u_varname
+        if vname.startswith('CTX_'):
+            # handle final entities
+            value = self.security_ctx.get(vname[4:].lower())
+            if isinstance(value, basestring):
+                c_type = 'String'
+            # XXX handle other types, maybe add the possibility to force
+            # in the ctx.
+            # XXX It may be easier/better to return argument
+            return n.Constant(value, c_type)
+            # handle non-final entities
         key = (self.current_expr, self.varmap, vname)
         try:
             return self.rewritten[key]
diff --git a/test/unittest_rqlrewrite.py b/test/unittest_rqlrewrite.py
--- a/test/unittest_rqlrewrite.py
+++ b/test/unittest_rqlrewrite.py
@@ -48,7 +48,7 @@ def eid_func_map(eid):
             2: 'Card',
             3: 'Affaire'}[eid]
 
-def _prepare_rewriter(rewriter_cls, kwargs):
+def _prepare_rewriter(rewriter_cls, kwargs, security_ctx=None):
     class FakeVReg:
         schema = schema
         @staticmethod
@@ -61,10 +61,11 @@ def _prepare_rewriter(rewriter_cls, kwar
             @staticmethod
             def simplify(mainrqlst, needcopy=False):
                 rqlhelper.simplify(rqlst, needcopy)
-    return rewriter_cls(mock_object(vreg=FakeVReg, user=(mock_object(eid=1))))
+    return rewriter_cls(
+        mock_object(vreg=FakeVReg, user=(mock_object(eid=1))), security_ctx)
 
-def rewrite(rqlst, snippets_map, kwargs, existingvars=None):
-    rewriter = _prepare_rewriter(rqlrewrite.RQLRewriter, kwargs)
+def rewrite(rqlst, snippets_map, kwargs, existingvars=None, security_ctx=None):
+    rewriter = _prepare_rewriter(rqlrewrite.RQLRewriter, kwargs, security_ctx)
     snippets = []
     for v, exprs in sorted(snippets_map.items()):
         rqlexprs = [isinstance(snippet, basestring)
@@ -115,6 +116,16 @@ class RQLRewriteTC(TestCase):
                          "EXISTS(C in_state A, B in_group E, F require_state A, "
                          "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)")
 
+    def test_security_ctx_var(self):
+        constraint = ('X owned_by USER, USER in_group G, G name CTX_MAINGROUP')
+        rqlst = parse('Card C')
+        rewrite(rqlst, {('C', 'X'): (constraint,)},
+                {}, security_ctx={'maingroup': 'agroupname'})
+        self.assertEqual(rqlst.as_string(),
+                         u"Any C WHERE C is Card, "
+                         "EXISTS(C owned_by A, A in_group B, "
+                         "B name 'agroupname', A is CWUser, B is CWGroup)")
+
     def test_multiple_var(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
# HG changeset patch
# User Christophe de Vienne <christophe@unlish.com>
# Date 1423493581 -3600
#      Mon Feb 09 15:53:01 2015 +0100
# Node ID dd582ac2a0371188b2ce12acbbc3e5d02532a37c
# Parent  df655cf48679c0863b660908b72467c2bde084cf
[wip] Add access to the security_context from subsitute variables

Related to #4919855

diff --git a/rqlrewrite.py b/rqlrewrite.py
--- a/rqlrewrite.py
+++ b/rqlrewrite.py
@@ -882,6 +882,10 @@ class RQLRewriter(object):
 
     def visit_constant(self, node):
         """generate filter name for a constant"""
+        if node.value.startswith('ctx_'):
+            # Make sure the corresponding var is copied from the ctx
+            if node.value not in self.kwargs:
+                self.kwargs[node.value] = self.security_ctx[node.value[4:]]
         return n.Constant(node.value, node.type)
 
     def visit_variableref(self, node):
diff --git a/test/unittest_rqlrewrite.py b/test/unittest_rqlrewrite.py
--- a/test/unittest_rqlrewrite.py
+++ b/test/unittest_rqlrewrite.py
@@ -126,6 +126,20 @@ class RQLRewriteTC(TestCase):
                          "EXISTS(C owned_by A, A in_group B, "
                          "B name 'agroupname', A is CWUser, B is CWGroup)")
 
+    def test_security_ctx_subst_var(self):
+        constraint = (
+            'X owned_by USER, USER in_group G, G name %(ctx_maingroup)s')
+        rqlst = parse('Card C')
+        kwargs = {}
+        rewrite(rqlst, {('C', 'X'): (constraint,)},
+                kwargs, security_ctx={'maingroup': 'agroupname'})
+        self.assertEqual(rqlst.as_string(),
+                         u"Any C WHERE C is Card, "
+                         "EXISTS(C owned_by A, A in_group B, "
+                         "B name %(ctx_maingroup)s, A is CWUser, B is CWGroup)")
+        self.assertIn('ctx_maingroup', kwargs)
+        self.assertEquals(kwargs['ctx_maingroup'], 'agroupname')
+
     def test_multiple_var(self):
         card_constraint = ('X in_state S, U in_group G, P require_state S,'
                            'P name "read", P require_group G')
# HG changeset patch
# User Christophe de Vienne <christophe@unlish.com>
# Date 1424079449 -3600
#      Mon Feb 16 10:37:29 2015 +0100
# Node ID 37f0846caf23fa3627399680d2c95ec3e0a66c66
# Parent  dd582ac2a0371188b2ce12acbbc3e5d02532a37c
[wip] Inject security context in RQLExpression._check

Related to #4919855

diff --git a/schema.py b/schema.py
--- a/schema.py
+++ b/schema.py
@@ -317,6 +317,10 @@ class RQLExpression(object):
         _cw may be a request or a server side transaction
         """
         creating = kwargs.get('creating')
+        # Inject security context into kwargs
+        kwargs.update({
+            'ctx_' + k: v for k, v in _cw.security_ctx
+        })
         if not creating and self.eid is not None:
             key = (self.eid, tuple(sorted(kwargs.iteritems())))
             try:
# HG changeset patch
# User Christophe de Vienne <christophe@unlish.com>
# Date 1424081263 -3600
#      Mon Feb 16 11:07:43 2015 +0100
# Node ID 49f4689f5878f1198b37ae5487ce716996a2265c
# Parent  a0a11be5a9cb982e2d95eceb33ee043c5cc336ee
[wip] Implements security_ctx as permanent rql args

This approach to security_ctx inject the security_ctx variables directly into
the rql args just before executing the request.

The big advantage of this approach is its simplicity.

Related to #4919855

diff --git a/server/querier.py b/server/querier.py
--- a/server/querier.py
+++ b/server/querier.py
@@ -538,7 +538,20 @@ class QuerierHelper(object):
         if server.DEBUG & (server.DBG_RQL | server.DBG_SQL):
             if server.DEBUG & (server.DBG_MORE | server.DBG_SQL):
                 print '*'*80
-            print 'querier input', repr(rql), repr(args)
+            print (
+                'querier input', repr(rql), repr(args), repr(cnx.security_ctx))
+        # inject the security context in the args
+        if args is not None:
+            for key in args:
+                if key.starts_with('ctx_'):
+                    raise ValueError('Security context variables cannot be '
+                                     'passed as regular arguments.')
+        if cnx.security_ctx:
+            if args is None:
+                args = {}
+            args.update({
+                'ctx_' + k: v for k, v in cnx.security_ctx.items()})
+
         # parse the query and binds variables
         cachekey = (rql,)
         try:
diff --git a/server/session.py b/server/session.py
--- a/server/session.py
+++ b/server/session.py
@@ -503,6 +503,7 @@ class Connection(RequestSessionBase):
         ### security control attributes
         self._read_security = DEFAULT_SECURITY # handled by a property
         self.write_security = DEFAULT_SECURITY
+        self.security_ctx = {}
 
         # undo control
         config = session.repo.config