eric6/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityNodeVisitor.py

Mon, 08 Jun 2020 08:17:14 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 08 Jun 2020 08:17:14 +0200
changeset 7612
ca1ce1e0fcff
child 7613
382f89c11e27
permissions
-rw-r--r--

Code Style Checker: started to implement checker for security related issues.

# -*- 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):
        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.
        """
        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.
        
        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
        """
        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 __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
        """
        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)

eric ide

mercurial