Wed, 29 Jul 2020 17:37:09 +0200
Code Style Checker: updated the return style checker.
# -*- coding: utf-8 -*- # Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing an AST node visitor for security checks. """ import ast from . import SecurityUtils from .SecurityContext import SecurityContext class SecurityNodeVisitor(object): """ Class implementing an AST node visitor for security checks. """ def __init__(self, checker, secCheckers, filename): """ Constructor @param checker reference to the main security checker object @type SecurityChecker @param secCheckers dictionary containing the available checker routines @type dict @param filename name of the checked file @type str """ self.__checker = checker self.__securityCheckers = secCheckers self.seen = 0 self.depth = 0 self.filename = filename self.imports = set() self.import_aliases = {} # in some cases we can't determine a qualified name try: self.namespace = SecurityUtils.getModuleQualnameFromPath(filename) except SecurityUtils.InvalidModulePath: self.namespace = "" def __runChecks(self, checkType): """ Private method to run all enabled checks for a given check type. @param checkType type of checks to be run @type str """ if checkType in self.__securityCheckers: for check in self.__securityCheckers[checkType]: check(self.__checker.reportError, SecurityContext(self.__context), self.__checker.getConfig()) def visit_ClassDef(self, node): """ Public method defining a visitor for AST ClassDef nodes. Add class name to current namespace for all descendants. @param node reference to the node being inspected @type ast.ClassDef """ # For all child nodes, add this class name to current namespace self.namespace = SecurityUtils.namespacePathJoin( self.namespace, node.name) def visit_FunctionDef(self, node): """ Public method defining a visitor for AST FunctionDef nodes. @param node reference to the node being inspected @type ast.FunctionDef """ self.__visitFunctionDefinition(node) def visit_AsyncFunctionDef(self, node): """ Public method defining a visitor for AST AsyncFunctionDef nodes. @param node reference to the node being inspected @type ast.AsyncFunctionDef """ self.__visitFunctionDefinition(node) def __visitFunctionDefinition(self, node): """ Private method defining a visitor for AST FunctionDef and AsyncFunctionDef nodes. Add relevant information about the node to the context for use in tests which inspect function definitions. Add the function name to the current namespace for all descendants. @param node reference to the node being inspected @type ast.FunctionDef, ast.AsyncFunctionDef """ self.__context['function'] = node qualname = SecurityUtils.namespacePathJoin(self.namespace, node.name) name = qualname.split('.')[-1] self.__context['qualname'] = qualname self.__context['name'] = name # For all child nodes and any tests run, add this function name to # current namespace self.namespace = SecurityUtils.namespacePathJoin( self.namespace, node.name) self.__runChecks("FunctionDef") def visit_Call(self, node): """ Public method defining a visitor for AST Call nodes. Add relevant information about the node to the context for use in tests which inspect function calls. @param node reference to the node being inspected @type ast.Call """ self.__context['call'] = node qualname = SecurityUtils.getCallName(node, self.import_aliases) name = qualname.split('.')[-1] self.__context['qualname'] = qualname self.__context['name'] = name self.__runChecks("Call") def visit_Import(self, node): """ Public method defining a visitor for AST Import nodes. @param node reference to the node being inspected @type ast.Import """ for nodename in node.names: if nodename.asname: self.import_aliases[nodename.asname] = nodename.name self.imports.add(nodename.name) self.__context['module'] = nodename.name self.__runChecks("Import") def visit_ImportFrom(self, node): """ Public method defining a visitor for AST Import nodes. This adds relevant information about the node to the context for use in tests which inspect imports. @param node reference to the node being inspected @type ast.ImportFrom """ module = node.module if module is None: self.visit_Import(node) return for nodename in node.names: if nodename.asname: self.import_aliases[nodename.asname] = ( module + "." + nodename.name ) else: # Even if import is not aliased we need an entry that maps # name to module.name. For example, with 'from a import b' # b should be aliased to the qualified name a.b self.import_aliases[nodename.name] = ( module + '.' + nodename.name) self.imports.add(module + "." + nodename.name) self.__context['module'] = module self.__context['name'] = nodename.name self.__runChecks("ImportFrom") def visit_Constant(self, node): """ Public method defining a visitor for Constant nodes. This calls the appropriate method for the node type. It maintains compatibility with <3.6 and 3.8+ @param node reference to the node being inspected @type ast.Constant """ if isinstance(node.value, str): self.visit_Str(node) elif isinstance(node.value, bytes): self.visit_Bytes(node) def visit_Str(self, node): """ Public method defining a visitor for String nodes. This adds relevant information about node to the context for use in tests which inspect strings. @param node reference to the node being inspected @type ast.Str """ self.__context['str'] = node.s if not isinstance(node._securityParent, ast.Expr): # docstring self.__context['linerange'] = SecurityUtils.linerange_fix( node._securityParent ) self.__runChecks("Str") def visit_Bytes(self, node): """ Public method defining a visitor for Bytes nodes. This adds relevant information about node to the context for use in tests which inspect strings. @param node reference to the node being inspected @type ast.Bytes """ self.__context['bytes'] = node.s if not isinstance(node._securityParent, ast.Expr): # docstring self.__context['linerange'] = SecurityUtils.linerange_fix( node._securityParent ) self.__runChecks("Bytes") def __preVisit(self, node): """ Private method to set up a context for the visit method. @param node node to base the context on @type ast.AST @return flag indicating to visit the node @rtype bool """ self.__context = {} self.__context['imports'] = self.imports self.__context['import_aliases'] = self.import_aliases if hasattr(node, 'lineno'): self.__context['lineno'] = node.lineno ## ## if node.lineno in self.nosec_lines: ## LOG.debug("skipped, nosec") ## self.metrics.note_nosec() ## return False self.__context['node'] = node self.__context['linerange'] = SecurityUtils.linerange_fix(node) self.__context['filename'] = self.filename self.seen += 1 self.depth += 1 return True def visit(self, node): """ Public method to inspected an AST node. @param node AST node to be inspected @type ast.AST """ name = node.__class__.__name__ method = 'visit_' + name visitor = getattr(self, method, None) if visitor is not None: visitor(node) else: self.__runChecks(name) def __postVisit(self, node): """ Private method to clean up after a node was visited. @param node AST node that was visited @type ast.AST """ self.depth -= 1 # Clean up post-recursion stuff that gets setup in the visit methods # for these node types. if isinstance(node, (ast.FunctionDef, ast.ClassDef)): self.namespace = SecurityUtils.namespacePathSplit( self.namespace)[0] def generic_visit(self, node): """ Public method to drive the node visitor. @param node node to be inspected @type ast.AST """ for _, value in ast.iter_fields(node): if isinstance(value, list): maxIndex = len(value) - 1 for index, item in enumerate(value): if isinstance(item, ast.AST): if index < maxIndex: item._securitySibling = value[index + 1] else: item._securitySibling = None item._securityParent = node if self.__preVisit(item): self.visit(item) self.generic_visit(item) self.__postVisit(item) elif isinstance(value, ast.AST): value._securitySibling = None value._securityParent = node if self.__preVisit(value): self.visit(value) self.generic_visit(value) self.__postVisit(value)