diff -r 643b19d79e4a -r 79d34ba48612 server/querier.py --- a/server/querier.py Mon Jun 16 10:22:24 2014 +0200 +++ b/server/querier.py Thu Feb 13 13:58:28 2014 +0100 @@ -28,6 +28,7 @@ from yams import BASE_TYPES from cubicweb import ValidationError, Unauthorized, UnknownEid +from cubicweb.rqlrewrite import RQLRelationRewriter from cubicweb import Binary, server from cubicweb.rset import ResultSet @@ -72,7 +73,44 @@ except AttributeError: return cnx.entity_metas(term.eval(args))['type'] -def check_read_access(cnx, rqlst, solution, args): +def check_relations_read_access(cnx, select, args): + """Raise :exc:`Unauthorized` if the given user doesn't have credentials to + read relations used in the givel syntaxt tree + """ + # use `term_etype` since we've to deal with rewritten constants here, + # when used as an external source by another repository. + # XXX what about local read security w/ those rewritten constants... + # XXX constants can also happen in some queries generated by req.find() + DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS + schema = cnx.repo.schema + user = cnx.user + if select.where is not None: + for rel in select.where.iget_nodes(Relation): + for solution in select.solutions: + # XXX has_text may have specific perm ? + if rel.r_type in READ_ONLY_RTYPES: + continue + rschema = schema.rschema(rel.r_type) + if rschema.final: + eschema = schema.eschema(term_etype(cnx, rel.children[0], + solution, args)) + rdef = eschema.rdef(rschema) + else: + rdef = rschema.rdef(term_etype(cnx, rel.children[0], + solution, args), + term_etype(cnx, rel.children[1].children[0], + solution, args)) + if not user.matching_groups(rdef.get_groups('read')): + if DBG: + print ('check_read_access: %s %s does not match %s' % + (rdef, user.groups, rdef.get_groups('read'))) + # XXX rqlexpr not allowed + raise Unauthorized('read', rel.r_type) + if DBG: + print ('check_read_access: %s %s matches %s' % + (rdef, user.groups, rdef.get_groups('read'))) + +def get_local_checks(cnx, rqlst, solution): """Check that the given user has credentials to access data read by the query and return a dict defining necessary "local checks" (i.e. rql expression in read permission defined in the schema) where no group grants @@ -80,50 +118,27 @@ Returned dictionary's keys are variable names and values the rql expressions for this variable (with the given solution). + + Raise :exc:`Unauthorized` if access is known to be defined, i.e. if there is + no matching group and no local permissions. """ - # use `term_etype` since we've to deal with rewritten constants here, - # when used as an external source by another repository. - # XXX what about local read security w/ those rewritten constants... DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS schema = cnx.repo.schema - if rqlst.where is not None: - for rel in rqlst.where.iget_nodes(Relation): - # XXX has_text may have specific perm ? - if rel.r_type in READ_ONLY_RTYPES: - continue - rschema = schema.rschema(rel.r_type) - if rschema.final: - eschema = schema.eschema(term_etype(cnx, rel.children[0], - solution, args)) - rdef = eschema.rdef(rschema) - else: - rdef = rschema.rdef(term_etype(cnx, rel.children[0], - solution, args), - term_etype(cnx, rel.children[1].children[0], - solution, args)) - if not cnx.user.matching_groups(rdef.get_groups('read')): - if DBG: - print ('check_read_access: %s %s does not match %s' % - (rdef, cnx.user.groups, rdef.get_groups('read'))) - # XXX rqlexpr not allowed - raise Unauthorized('read', rel.r_type) - if DBG: - print ('check_read_access: %s %s matches %s' % - (rdef, cnx.user.groups, rdef.get_groups('read'))) + user = cnx.user localchecks = {} # iterate on defined_vars and not on solutions to ignore column aliases for varname in rqlst.defined_vars: eschema = schema.eschema(solution[varname]) if eschema.final: continue - if not cnx.user.matching_groups(eschema.get_groups('read')): + if not user.matching_groups(eschema.get_groups('read')): erqlexprs = eschema.get_rqlexprs('read') if not erqlexprs: ex = Unauthorized('read', solution[varname]) ex.var = varname if DBG: print ('check_read_access: %s %s %s %s' % - (varname, eschema, cnx.user.groups, eschema.get_groups('read'))) + (varname, eschema, user.groups, eschema.get_groups('read'))) raise ex # don't insert security on variable only referenced by 'NOT X relation Y' or # 'NOT EXISTS(X relation Y)' @@ -133,7 +148,8 @@ if (not schema.rschema(r.r_type).final and ((isinstance(r.parent, Exists) and r.parent.neged(strict=True)) or isinstance(r.parent, Not)))]) - != len(varinfo['relations'])): + != + len(varinfo['relations'])): localchecks[varname] = erqlexprs return localchecks @@ -258,7 +274,7 @@ newsolutions = [] for solution in rqlst.solutions: try: - localcheck = check_read_access(cnx, rqlst, solution, self.args) + localcheck = get_local_checks(cnx, rqlst, solution) except Unauthorized as ex: msg = 'remove %s from solutions since %s has no %s access to %s' msg %= (solution, cnx.user.login, ex.args[0], ex.args[1]) @@ -573,6 +589,7 @@ if cnx.read_security: for select in rqlst.children: check_no_password_selected(select) + check_relations_read_access(cnx, select, args) # on select query, always copy the cached rqlst so we don't have to # bother modifying it. This is not necessary on write queries since # a new syntax tree is built from them.