Beispiel #1
0
    def insert_pending(self):
        """pending_keys hold variable referenced by U has_<action>_permission X
        relation.

        Once the snippet introducing this has been inserted and solutions
        recomputed, we have to insert snippet defined for <action> of entity
        types taken by X
        """
        stmt = self.current_statement()
        while self.pending_keys:
            key, action = self.pending_keys.pop()
            try:
                varname = self.rewritten[key]
            except KeyError:
                try:
                    varname = self.revvarmap[key[-1]][0]
                except KeyError:
                    # variable isn't used anywhere else, we can't insert security
                    raise Unauthorized()
            ptypes = stmt.defined_vars[varname].stinfo['possibletypes']
            if len(ptypes) > 1:
                # XXX dunno how to handle this
                self.session.error(
                    'cant check security of %s, ambigous type for %s in %s',
                    stmt, varname, key[0])  # key[0] == the rql expression
                raise Unauthorized()
            etype = next(iter(ptypes))
            eschema = self.schema.eschema(etype)
            if not eschema.has_perm(self.session, action):
                rqlexprs = eschema.get_rqlexprs(action)
                if not rqlexprs:
                    raise Unauthorized()
                self.insert_snippets([({varname: 'X'}, rqlexprs)])
 def _test(self, func):
     self.assertEqual(func(Unauthorized()),
                      'You are not allowed to perform this operation')
     self.assertEqual(func(Unauthorized('a')), 'a')
     self.assertEqual(func(Unauthorized('a', 'b')),
                      'You are not allowed to perform a operation on b')
     self.assertEqual(func(Unauthorized('a', 'b', 'c')), 'a b c')
Beispiel #3
0
def check_no_password_selected(rqlst):
    """check that Password entities are not selected"""
    for solution in rqlst.solutions:
        for var, etype in solution.items():
            if etype == 'Password':
                raise Unauthorized('Password selection is not allowed (%s)' %
                                   var)
Beispiel #4
0
 def insert_varmap_snippets(self, varmap, rqlexprs, varexistsmap):
     try:
         self.init_from_varmap(varmap, varexistsmap)
     except VariableFromSubQuery as ex:
         # variable may have been moved to a newly inserted subquery
         # we should insert snippet in that subquery
         subquery = self.select.aliases[ex.variable].query
         assert len(subquery.children) == 1, subquery
         subselect = subquery.children[0]
         RQLRewriter(self.session).rewrite(subselect, [(varmap, rqlexprs)],
                                           self.kwargs)
         return
     self._insert_scope = None
     previous = None
     inserted = False
     for rqlexpr in rqlexprs:
         self.current_expr = rqlexpr
         if varexistsmap is None:
             try:
                 new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, previous)
             except Unsupported:
                 continue
             inserted = True
             if new is not None and self._insert_scope is None:
                 self.exists_snippet[rqlexpr] = new
             previous = previous or new
         else:
             # called to reintroduce snippet due to ambiguity creation,
             # so skip snippets which are not introducing this ambiguity
             exists = varexistsmap[varmap]
             if self.exists_snippet.get(rqlexpr) is exists:
                 self.insert_snippet(varmap, rqlexpr.snippet_rqlst, exists)
     if varexistsmap is None and not inserted:
         # no rql expression found matching rql solutions. User has no access right
         raise Unauthorized()  # XXX may also be because of bad constraints in schema definition
Beispiel #5
0
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
    him the permission.

    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.
    """
    DBG = (server.DEBUG & server.DBG_SEC) and 'read' in server._SECURITY_CAPS
    schema = cnx.repo.schema
    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 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, 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)'
            varinfo = rqlst.defined_vars[varname].stinfo
            if varinfo['selected'] or (len([
                    r for r in varinfo['relations']
                    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'])):
                localchecks[varname] = erqlexprs
    return localchecks
Beispiel #6
0
def check_entity_attributes(cnx, entity, action, editedattrs=None):
    eid = entity.eid
    eschema = entity.e_schema
    # ._cw_skip_security_attributes is there to bypass security for attributes
    # set by hooks by modifying the entity's dictionary
    if editedattrs is None:
        editedattrs = entity.cw_edited
    dontcheck = editedattrs.skip_security
    etypechecked = False
    for attr in editedattrs:
        if attr in dontcheck:
            continue
        rdef = eschema.rdef(attr, takefirst=True)
        if rdef.final: # non final relation are checked by standard hooks
            perms = rdef.permissions.get(action)
            # comparison below works because the default update perm is:
            #
            #  ('managers', ERQLExpression(Any X WHERE U has_update_permission X,
            #                              X eid %(x)s, U eid %(u)s))
            #
            # is deserialized in this order (groups first), and ERQLExpression
            # implements comparison by rql expression.
            if perms == buildobjs.DEFAULT_ATTRPERMS[action]:
                # The default rule is to delegate to the entity
                # rule. This needs to be checked only once.
                if not etypechecked:
                    entity.cw_check_perm(action)
                    etypechecked = True
                continue
            if perms == ():
                # That means an immutable attribute; as an optimization, avoid
                # going through check_perm.
                raise Unauthorized(action, str(rdef))
            rdef.check_perm(cnx, action, eid=eid)

    if action == 'add' and not etypechecked:
        # think about cnx.create_entity('Foo')
        # the standard metadata were inserted by a hook
        # with a bypass ... we conceptually need to check
        # the eid attribute at *creation* time
        entity.cw_check_perm(action)
Beispiel #7
0
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 given syntax 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 __call__(self):
     raise Unauthorized()
Beispiel #9
0
    def core_handle(self, req):
        """method called by the main publisher to process <req> relative path

        should return a string containing the resulting page or raise a
        `NotFound` exception

        :type req: `web.Request`
        :param req: the request object

        :rtype: str
        :return: the result of the pusblished url
        """
        path = req.relative_path(False)
        # don't log form values they may contains sensitive information
        self.debug('publish "%s" (%s, form params: %s)', path,
                   req.session.sessionid, list(req.form))
        # remove user callbacks on a new request (except for json controllers
        # to avoid callbacks being unregistered before they could be called)
        tstart = process_time()
        commited = False
        try:
            # standard processing of the request
            try:
                # apply CORS sanity checks
                cors.process_request(req, self.vreg.config)
                ctrlid, rset = self.url_resolver.process(req, path)
                try:
                    controller = self.vreg['controllers'].select(ctrlid,
                                                                 req,
                                                                 appli=self)
                except NoSelectableObject:
                    raise Unauthorized(req._('not authorized'))
                req.update_search_state()
                result = controller.publish(rset=rset)
            except cors.CORSPreflight:
                # Return directly an empty 200
                req.status_out = 200
                result = b''
            except Redirect as ex:
                # Redirect may be raised by edit controller when everything went
                # fine, so attempt to commit
                result = self.redirect_handler(req, ex)
            if req.cnx:
                txuuid = req.cnx.commit()
                commited = True
                if txuuid is not None:
                    req.data['last_undoable_transaction'] = txuuid
        # error case
        except NotFound as ex:
            result = self.notfound_content(req)
            req.status_out = ex.status
        except ValidationError as ex:
            result = self.validation_error_handler(req, ex)
        except RemoteCallFailed as ex:
            result = self.ajax_error_handler(req, ex)
        except Unauthorized as ex:
            req.data['errmsg'] = req._(
                'You\'re not authorized to access this page. '
                'If you think you should, please contact the site administrator.'
            )
            req.status_out = http_client.FORBIDDEN
            result = self.error_handler(req, ex, tb=False)
        except Forbidden as ex:
            req.data['errmsg'] = req._(
                'This action is forbidden. '
                'If you think it should be allowed, please contact the site administrator.'
            )
            req.status_out = http_client.FORBIDDEN
            result = self.error_handler(req, ex, tb=False)
        except (BadRQLQuery, RequestError) as ex:
            result = self.error_handler(req, ex, tb=False)
        # pass through exception
        except DirectResponse:
            if req.cnx:
                req.cnx.commit()
            raise
        except (AuthenticationError, LogOut):
            # the rollback is handled in the finally
            raise
        # Last defense line
        except BaseException as ex:
            req.status_out = http_client.INTERNAL_SERVER_ERROR
            result = self.error_handler(req, ex, tb=True)
        finally:
            if req.cnx and not commited:
                try:
                    req.cnx.rollback()
                except Exception:
                    pass  # ignore rollback error at this point
        self.add_undo_link_to_msg(req)
        self.debug('query %s executed in %s sec', path,
                   process_time() - tstart)
        return result
Beispiel #10
0
    def _check_permissions(self, rqlst):
        """Return a dict defining "local checks", i.e. RQLExpression defined in
        the schema that should be inserted in the original query, together with
        a set of variable names which requires some security to be inserted.

        Solutions where a variable has a type which the user can't definitly
        read are removed, else if the user *may* read it (i.e. if an rql
        expression is defined for the "read" permission of the related type),
        the local checks dict is updated.

        The local checks dict has entries for each different local check
        necessary, with associated solutions as value, a local check being
        defined by a list of 2-uple (variable name, rql expressions) for each
        variable which has to be checked. Solutions which don't require local
        checks will be associated to the empty tuple key.

        Note rqlst should not have been simplified at this point.
        """
        cnx = self.cnx
        msgs = []
        # dict(varname: eid), allowing to check rql expression for variables
        # which have a known eid
        varkwargs = {}
        if not cnx.transaction_data.get('security-rqlst-cache'):
            for var in rqlst.defined_vars.values():
                if var.stinfo['constnode'] is not None:
                    eid = var.stinfo['constnode'].eval(self.args)
                    varkwargs[var.name] = int(eid)
        # dictionary of variables restricted for security reason
        localchecks = {}
        restricted_vars = set()
        newsolutions = []
        for solution in rqlst.solutions:
            try:
                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])
                msgs.append(msg)
                LOGGER.info(msg)
            else:
                newsolutions.append(solution)
                # try to benefit of rqlexpr.check cache for entities which
                # are specified by eid in query'args
                for varname, eid in varkwargs.items():
                    try:
                        rqlexprs = localcheck.pop(varname)
                    except KeyError:
                        continue
                    # if entity has been added in the current transaction, the
                    # user can read it whatever rql expressions are associated
                    # to its type
                    if cnx.added_in_transaction(eid):
                        continue
                    for rqlexpr in rqlexprs:
                        if rqlexpr.check(cnx, eid):
                            break
                    else:
                        raise Unauthorized(
                            'No read access on %r with eid %i.' % (var, eid))
                # mark variables protected by an rql expression
                restricted_vars.update(localcheck)
                # turn local check into a dict key
                localcheck = tuple(sorted(localcheck.items()))
                localchecks.setdefault(localcheck, []).append(solution)
        # raise Unautorized exception if the user can't access to any solution
        if not newsolutions:
            raise Unauthorized('\n'.join(msgs))
        # if there is some message, solutions have been modified and must be
        # reconsidered by the syntax treee
        if msgs:
            rqlst.set_possible_types(newsolutions)
        return localchecks, restricted_vars
Beispiel #11
0
 def ensure_ro_rql(self, rql):
     """raise an exception if the given rql is not a select query"""
     first = rql.split(None, 1)[0].lower()
     if first in ('insert', 'set', 'delete'):
         raise Unauthorized(self._('only select queries are authorized'))