[schema,server] add a security debugging aid (closes #2920304)
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Tue, 25 Jun 2013 12:11:42 +0200
changeset 9148 1b549c1acd4f
parent 9147 01124cfd4b1f
child 9149 31ed9dd946d1
[schema,server] add a security debugging aid (closes #2920304) - Add a DGB_SEC debugging flag (to be used with set_debug/debugged). - Add a context manager (tunesecurity) to filter security assertions. Note: this does not address all read-security mecanisms.
schema.py
server/__init__.py
--- a/schema.py	Mon Jun 24 19:00:40 2013 +0200
+++ b/schema.py	Tue Jun 25 12:11:42 2013 +0200
@@ -44,6 +44,15 @@
 import cubicweb
 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized
 
+try:
+    from cubicweb import server
+except ImportError:
+    # We need to lookup DEBUG from there,
+    # however a pure dbapi client may not have it.
+    class server(object): pass
+    server.DEBUG = False
+
+
 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
 
@@ -268,13 +277,25 @@
         return False
 PermissionMixIn.has_perm = has_perm
 
+
 def check_perm(self, _cw, action, **kwargs):
     # NB: _cw may be a server transaction or a request object.
     #
     # check user is in an allowed group, if so that's enough internal
     # transactions should always stop there
+    DBG = False
+    if server.DEBUG & server.DBG_SEC:
+        if action in server._SECURITY_CAPS:
+            _self_str = str(self)
+            if server._SECURITY_ITEMS:
+                if any(item in _self_str for item in server._SECURITY_ITEMS):
+                    DBG = True
+            else:
+                DBG = True
     groups = self.get_groups(action)
     if _cw.user.matching_groups(groups):
+        if DBG:
+            print 'check_perm: %r %r: user matches %s' % (action, _self_str, groups)
         return
     # if 'owners' in allowed groups, check if the user actually owns this
     # object, if so that's enough
@@ -284,8 +305,15 @@
     if 'owners' in groups and (
           kwargs.get('creating')
           or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
+        if DBG:
+            print ('check_perm: %r %r: user is owner or creation time' %
+                   (action, _self_str))
         return
     # else if there is some rql expressions, check them
+    if DBG:
+        print ('check_perm: %r %r %s' %
+               (action, _self_str, [(rqlexpr, kwargs, rqlexpr.check(_cw, **kwargs))
+                                    for rqlexpr in self.get_rqlexprs(action)]))
     if any(rqlexpr.check(_cw, **kwargs)
            for rqlexpr in self.get_rqlexprs(action)):
         return
--- a/server/__init__.py	Mon Jun 24 19:00:40 2013 +0200
+++ b/server/__init__.py	Tue Jun 25 12:11:42 2013 +0200
@@ -26,6 +26,7 @@
 import sys
 from os.path import join, exists
 from glob import glob
+from contextlib import contextmanager
 
 from logilab.common.modutils import LazyObject
 from logilab.common.textutils import splitstrip
@@ -80,14 +81,57 @@
 DBG_HOOKS = 16
 #: operations
 DBG_OPS = 32
+#: security
+DBG_SEC = 64
 #: more verbosity
-DBG_MORE = 64
+DBG_MORE = 128
 #: all level enabled
-DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
+DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_SEC + DBG_MORE
+
+_SECURITY_ITEMS = []
+_SECURITY_CAPS = ['read', 'add', 'update', 'delete']
 
 #: current debug mode
 DEBUG = 0
 
+@contextmanager
+def tunesecurity(items=(), capabilities=()):
+    """Context manager to use in conjunction with DBG_SEC.
+
+    This allows some tuning of:
+    * the monitored capabilities ('read', 'add', ....)
+    * the object being checked by the security checkers
+
+    When no item is given, all of them will be watched.
+    By default all capabilities are monitored, unless specified.
+
+    Example use::
+
+      from cubicweb.server import debugged, DBG_SEC, tunesecurity
+      with debugged(DBG_SEC):
+          with tunesecurity(items=('Elephant', 'trumps'),
+                            capabilities=('update', 'delete')):
+              babar.cw_set(trumps=celeste)
+              flore.cw_delete()
+
+      ==>
+
+      check_perm: 'update' 'relation Elephant.trumps.Elephant'
+       [(ERQLExpression(Any X WHERE U has_update_permission X, X eid %(x)s, U eid %(u)s),
+       {'eid': 2167}, True)]
+      check_perm: 'delete' 'Elephant'
+       [(ERQLExpression(Any X WHERE U has_delete_permission X, X eid %(x)s, U eid %(u)s),
+       {'eid': 2168}, True)]
+
+    """
+    olditems = _SECURITY_ITEMS[:]
+    _SECURITY_ITEMS.extend(list(items))
+    oldactions = _SECURITY_CAPS[:]
+    _SECURITY_CAPS[:] = capabilities
+    yield
+    _SECURITY_ITEMS[:] = olditems
+    _SECURITY_CAPS[:] = oldactions
+
 def set_debug(debugmode):
     """change the repository debugging mode"""
     global DEBUG