server/querier.py
changeset 9954 79d34ba48612
parent 9892 928732ec00dd
child 9955 60a9cd1b3a4b
equal deleted inserted replaced
9953:643b19d79e4a 9954:79d34ba48612
    26 from rql.stmts import Union
    26 from rql.stmts import Union
    27 from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not
    27 from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not
    28 from yams import BASE_TYPES
    28 from yams import BASE_TYPES
    29 
    29 
    30 from cubicweb import ValidationError, Unauthorized, UnknownEid
    30 from cubicweb import ValidationError, Unauthorized, UnknownEid
       
    31 from cubicweb.rqlrewrite import RQLRelationRewriter
    31 from cubicweb import Binary, server
    32 from cubicweb import Binary, server
    32 from cubicweb.rset import ResultSet
    33 from cubicweb.rset import ResultSet
    33 
    34 
    34 from cubicweb.utils import QueryCache, RepeatList
    35 from cubicweb.utils import QueryCache, RepeatList
    35 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
    36 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
    70     try:
    71     try:
    71         return solution[term.name]
    72         return solution[term.name]
    72     except AttributeError:
    73     except AttributeError:
    73         return cnx.entity_metas(term.eval(args))['type']
    74         return cnx.entity_metas(term.eval(args))['type']
    74 
    75 
    75 def check_read_access(cnx, rqlst, solution, args):
    76 def check_relations_read_access(cnx, select, args):
       
    77     """Raise :exc:`Unauthorized` if the given user doesn't have credentials to
       
    78     read relations used in the givel syntaxt tree
       
    79     """
       
    80     # use `term_etype` since we've to deal with rewritten constants here,
       
    81     # when used as an external source by another repository.
       
    82     # XXX what about local read security w/ those rewritten constants...
       
    83     # XXX constants can also happen in some queries generated by req.find()
       
    84     DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
       
    85     schema = cnx.repo.schema
       
    86     user = cnx.user
       
    87     if select.where is not None:
       
    88         for rel in select.where.iget_nodes(Relation):
       
    89             for solution in select.solutions:
       
    90                 # XXX has_text may have specific perm ?
       
    91                 if rel.r_type in READ_ONLY_RTYPES:
       
    92                     continue
       
    93                 rschema = schema.rschema(rel.r_type)
       
    94                 if rschema.final:
       
    95                     eschema = schema.eschema(term_etype(cnx, rel.children[0],
       
    96                                              solution, args))
       
    97                     rdef = eschema.rdef(rschema)
       
    98                 else:
       
    99                     rdef = rschema.rdef(term_etype(cnx, rel.children[0],
       
   100                                                    solution, args),
       
   101                                         term_etype(cnx, rel.children[1].children[0],
       
   102                                                    solution, args))
       
   103                 if not user.matching_groups(rdef.get_groups('read')):
       
   104                     if DBG:
       
   105                         print ('check_read_access: %s %s does not match %s' %
       
   106                                (rdef, user.groups, rdef.get_groups('read')))
       
   107                     # XXX rqlexpr not allowed
       
   108                     raise Unauthorized('read', rel.r_type)
       
   109                 if DBG:
       
   110                     print ('check_read_access: %s %s matches %s' %
       
   111                            (rdef, user.groups, rdef.get_groups('read')))
       
   112 
       
   113 def get_local_checks(cnx, rqlst, solution):
    76     """Check that the given user has credentials to access data read by the
   114     """Check that the given user has credentials to access data read by the
    77     query and return a dict defining necessary "local checks" (i.e. rql
   115     query and return a dict defining necessary "local checks" (i.e. rql
    78     expression in read permission defined in the schema) where no group grants
   116     expression in read permission defined in the schema) where no group grants
    79     him the permission.
   117     him the permission.
    80 
   118 
    81     Returned dictionary's keys are variable names and values the rql expressions
   119     Returned dictionary's keys are variable names and values the rql expressions
    82     for this variable (with the given solution).
   120     for this variable (with the given solution).
       
   121 
       
   122     Raise :exc:`Unauthorized` if access is known to be defined, i.e. if there is
       
   123     no matching group and no local permissions.
    83     """
   124     """
    84     # use `term_etype` since we've to deal with rewritten constants here,
       
    85     # when used as an external source by another repository.
       
    86     # XXX what about local read security w/ those rewritten constants...
       
    87     DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
   125     DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
    88     schema = cnx.repo.schema
   126     schema = cnx.repo.schema
    89     if rqlst.where is not None:
   127     user = cnx.user
    90         for rel in rqlst.where.iget_nodes(Relation):
       
    91             # XXX has_text may have specific perm ?
       
    92             if rel.r_type in READ_ONLY_RTYPES:
       
    93                 continue
       
    94             rschema = schema.rschema(rel.r_type)
       
    95             if rschema.final:
       
    96                 eschema = schema.eschema(term_etype(cnx, rel.children[0],
       
    97                                                     solution, args))
       
    98                 rdef = eschema.rdef(rschema)
       
    99             else:
       
   100                 rdef = rschema.rdef(term_etype(cnx, rel.children[0],
       
   101                                                solution, args),
       
   102                                     term_etype(cnx, rel.children[1].children[0],
       
   103                                                solution, args))
       
   104             if not cnx.user.matching_groups(rdef.get_groups('read')):
       
   105                 if DBG:
       
   106                     print ('check_read_access: %s %s does not match %s' %
       
   107                            (rdef, cnx.user.groups, rdef.get_groups('read')))
       
   108                 # XXX rqlexpr not allowed
       
   109                 raise Unauthorized('read', rel.r_type)
       
   110             if DBG:
       
   111                 print ('check_read_access: %s %s matches %s' %
       
   112                        (rdef, cnx.user.groups, rdef.get_groups('read')))
       
   113     localchecks = {}
   128     localchecks = {}
   114     # iterate on defined_vars and not on solutions to ignore column aliases
   129     # iterate on defined_vars and not on solutions to ignore column aliases
   115     for varname in rqlst.defined_vars:
   130     for varname in rqlst.defined_vars:
   116         eschema = schema.eschema(solution[varname])
   131         eschema = schema.eschema(solution[varname])
   117         if eschema.final:
   132         if eschema.final:
   118             continue
   133             continue
   119         if not cnx.user.matching_groups(eschema.get_groups('read')):
   134         if not user.matching_groups(eschema.get_groups('read')):
   120             erqlexprs = eschema.get_rqlexprs('read')
   135             erqlexprs = eschema.get_rqlexprs('read')
   121             if not erqlexprs:
   136             if not erqlexprs:
   122                 ex = Unauthorized('read', solution[varname])
   137                 ex = Unauthorized('read', solution[varname])
   123                 ex.var = varname
   138                 ex.var = varname
   124                 if DBG:
   139                 if DBG:
   125                     print ('check_read_access: %s %s %s %s' %
   140                     print ('check_read_access: %s %s %s %s' %
   126                            (varname, eschema, cnx.user.groups, eschema.get_groups('read')))
   141                            (varname, eschema, user.groups, eschema.get_groups('read')))
   127                 raise ex
   142                 raise ex
   128             # don't insert security on variable only referenced by 'NOT X relation Y' or
   143             # don't insert security on variable only referenced by 'NOT X relation Y' or
   129             # 'NOT EXISTS(X relation Y)'
   144             # 'NOT EXISTS(X relation Y)'
   130             varinfo = rqlst.defined_vars[varname].stinfo
   145             varinfo = rqlst.defined_vars[varname].stinfo
   131             if varinfo['selected'] or (
   146             if varinfo['selected'] or (
   132                 len([r for r in varinfo['relations']
   147                 len([r for r in varinfo['relations']
   133                      if (not schema.rschema(r.r_type).final
   148                      if (not schema.rschema(r.r_type).final
   134                          and ((isinstance(r.parent, Exists) and r.parent.neged(strict=True))
   149                          and ((isinstance(r.parent, Exists) and r.parent.neged(strict=True))
   135                               or isinstance(r.parent, Not)))])
   150                               or isinstance(r.parent, Not)))])
   136                 != len(varinfo['relations'])):
   151                 !=
       
   152                 len(varinfo['relations'])):
   137                 localchecks[varname] = erqlexprs
   153                 localchecks[varname] = erqlexprs
   138     return localchecks
   154     return localchecks
   139 
   155 
   140 
   156 
   141 # Plans #######################################################################
   157 # Plans #######################################################################
   256         localchecks = {}
   272         localchecks = {}
   257         restricted_vars = set()
   273         restricted_vars = set()
   258         newsolutions = []
   274         newsolutions = []
   259         for solution in rqlst.solutions:
   275         for solution in rqlst.solutions:
   260             try:
   276             try:
   261                 localcheck = check_read_access(cnx, rqlst, solution, self.args)
   277                 localcheck = get_local_checks(cnx, rqlst, solution)
   262             except Unauthorized as ex:
   278             except Unauthorized as ex:
   263                 msg = 'remove %s from solutions since %s has no %s access to %s'
   279                 msg = 'remove %s from solutions since %s has no %s access to %s'
   264                 msg %= (solution, cnx.user.login, ex.args[0], ex.args[1])
   280                 msg %= (solution, cnx.user.login, ex.args[0], ex.args[1])
   265                 msgs.append(msg)
   281                 msgs.append(msg)
   266                 LOGGER.info(msg)
   282                 LOGGER.info(msg)
   571             cachekey = None
   587             cachekey = None
   572         else:
   588         else:
   573             if cnx.read_security:
   589             if cnx.read_security:
   574                 for select in rqlst.children:
   590                 for select in rqlst.children:
   575                     check_no_password_selected(select)
   591                     check_no_password_selected(select)
       
   592                     check_relations_read_access(cnx, select, args)
   576             # on select query, always copy the cached rqlst so we don't have to
   593             # on select query, always copy the cached rqlst so we don't have to
   577             # bother modifying it. This is not necessary on write queries since
   594             # bother modifying it. This is not necessary on write queries since
   578             # a new syntax tree is built from them.
   595             # a new syntax tree is built from them.
   579             rqlst = rqlst.copy()
   596             rqlst = rqlst.copy()
   580             self._annotate(rqlst)
   597             self._annotate(rqlst)