[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.

authorAurelien Campeas <aurelien.campeas@logilab.fr>
changeset1b549c1acd4f
branchdefault
phasepublic
hiddenno
parent revision#01124cfd4b1f [etwist] fix handling of multiple files per field
child revision#31ed9dd946d1 [test] typo
files modified by this revision
schema.py
server/__init__.py
# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1372155102 -7200
# Tue Jun 25 12:11:42 2013 +0200
# Node ID 1b549c1acd4f562764ddf14c5d6c6caa11efad28
# Parent 01124cfd4b1f63e0219724e940de41e5768dd153
[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.

diff --git a/schema.py b/schema.py
@@ -42,10 +42,19 @@
1  from rql import parse, nodes, RQLSyntaxError, TypeResolverException
2 
3  import cubicweb
4  from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized
5 
6 +try:
7 +    from cubicweb import server
8 +except ImportError:
9 +    # We need to lookup DEBUG from there,
10 +    # however a pure dbapi client may not have it.
11 +    class server(object): pass
12 +    server.DEBUG = False
13 +
14 +
15  PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
16  VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
17 
18  # set of meta-relations available for every entity types
19  META_RTYPES = set((
@@ -266,28 +275,47 @@
20          return True
21      except Unauthorized:
22          return False
23  PermissionMixIn.has_perm = has_perm
24 
25 +
26  def check_perm(self, _cw, action, **kwargs):
27      # NB: _cw may be a server transaction or a request object.
28      #
29      # check user is in an allowed group, if so that's enough internal
30      # transactions should always stop there
31 +    DBG = False
32 +    if server.DEBUG & server.DBG_SEC:
33 +        if action in server._SECURITY_CAPS:
34 +            _self_str = str(self)
35 +            if server._SECURITY_ITEMS:
36 +                if any(item in _self_str for item in server._SECURITY_ITEMS):
37 +                    DBG = True
38 +            else:
39 +                DBG = True
40      groups = self.get_groups(action)
41      if _cw.user.matching_groups(groups):
42 +        if DBG:
43 +            print 'check_perm: %r %r: user matches %s' % (action, _self_str, groups)
44          return
45      # if 'owners' in allowed groups, check if the user actually owns this
46      # object, if so that's enough
47      #
48      # NB: give _cw to user.owns since user is not be bound to a transaction on
49      # the repository side
50      if 'owners' in groups and (
51            kwargs.get('creating')
52            or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
53 +        if DBG:
54 +            print ('check_perm: %r %r: user is owner or creation time' %
55 +                   (action, _self_str))
56          return
57      # else if there is some rql expressions, check them
58 +    if DBG:
59 +        print ('check_perm: %r %r %s' %
60 +               (action, _self_str, [(rqlexpr, kwargs, rqlexpr.check(_cw, **kwargs))
61 +                                    for rqlexpr in self.get_rqlexprs(action)]))
62      if any(rqlexpr.check(_cw, **kwargs)
63             for rqlexpr in self.get_rqlexprs(action)):
64          return
65      raise Unauthorized(action, str(self))
66  PermissionMixIn.check_perm = check_perm
diff --git a/server/__init__.py b/server/__init__.py
@@ -24,10 +24,11 @@
67  __docformat__ = "restructuredtext en"
68 
69  import sys
70  from os.path import join, exists
71  from glob import glob
72 +from contextlib import contextmanager
73 
74  from logilab.common.modutils import LazyObject
75  from logilab.common.textutils import splitstrip
76  from logilab.common.registry import yes
77  from logilab import database
@@ -78,18 +79,61 @@
78  DBG_MS   = 8
79  #: hooks
80  DBG_HOOKS = 16
81  #: operations
82  DBG_OPS = 32
83 +#: security
84 +DBG_SEC = 64
85  #: more verbosity
86 -DBG_MORE = 64
87 +DBG_MORE = 128
88  #: all level enabled
89 -DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
90 +DBG_ALL  = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_SEC + DBG_MORE
91 +
92 +_SECURITY_ITEMS = []
93 +_SECURITY_CAPS = ['read', 'add', 'update', 'delete']
94 
95  #: current debug mode
96  DEBUG = 0
97 
98 +@contextmanager
99 +def tunesecurity(items=(), capabilities=()):
100 +    """Context manager to use in conjunction with DBG_SEC.
101 +
102 +    This allows some tuning of:
103 +    * the monitored capabilities ('read', 'add', ....)
104 +    * the object being checked by the security checkers
105 +
106 +    When no item is given, all of them will be watched.
107 +    By default all capabilities are monitored, unless specified.
108 +
109 +    Example use::
110 +
111 +      from cubicweb.server import debugged, DBG_SEC, tunesecurity
112 +      with debugged(DBG_SEC):
113 +          with tunesecurity(items=('Elephant', 'trumps'),
114 +                            capabilities=('update', 'delete')):
115 +              babar.cw_set(trumps=celeste)
116 +              flore.cw_delete()
117 +
118 +      ==>
119 +
120 +      check_perm: 'update' 'relation Elephant.trumps.Elephant'
121 +       [(ERQLExpression(Any X WHERE U has_update_permission X, X eid %(x)s, U eid %(u)s),
122 +       {'eid': 2167}, True)]
123 +      check_perm: 'delete' 'Elephant'
124 +       [(ERQLExpression(Any X WHERE U has_delete_permission X, X eid %(x)s, U eid %(u)s),
125 +       {'eid': 2168}, True)]
126 +
127 +    """
128 +    olditems = _SECURITY_ITEMS[:]
129 +    _SECURITY_ITEMS.extend(list(items))
130 +    oldactions = _SECURITY_CAPS[:]
131 +    _SECURITY_CAPS[:] = capabilities
132 +    yield
133 +    _SECURITY_ITEMS[:] = olditems
134 +    _SECURITY_CAPS[:] = oldactions
135 +
136  def set_debug(debugmode):
137      """change the repository debugging mode"""
138      global DEBUG
139      if not debugmode:
140          DEBUG = 0