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

changeset 7612
ca1ce1e0fcff
child 7613
382f89c11e27
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityContext.py	Mon Jun 08 08:17:14 2020 +0200
@@ -0,0 +1,393 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a context class for security related checks.
+"""
+
+#
+# This code is a modified version of the one in 'bandit'.
+#
+# Original Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+import ast
+import sys
+
+from . import SecurityUtils
+
+
+class SecurityContext(object):
+    """
+    Class implementing a context class for security related checks.
+    """
+    def __init__(self, contextObject=None):
+        """
+        Constructor
+        
+        Initialize the class with a context dictionary or an empty
+        dictionary.
+        
+        @param contextObject context dictionary to be used to populate the
+            class
+        @type dict
+        """
+        if contextObject is not None:
+            self.__context = contextObject
+        else:
+            self.__context = {}
+    
+    def __repr__(self):
+        """
+        Private method to generate representation of object for printing or
+        interactive use.
+        
+        @return string representation of the object
+        @rtype str
+        """
+        return "<SecurityContext {0}>".formar(self.__context)
+    
+    @property
+    def callArgs(self):
+        """
+        Public method to get a list of function args.
+        
+        @return list of function args
+        @rtype list
+        """
+        args = []
+        if (
+            'call' in self.__context and
+            hasattr(self.__context['call'], 'args')
+        ):
+            for arg in self.__context['call'].args:
+                if hasattr(arg, 'attr'):
+                    args.append(arg.attr)
+                else:
+                    args.append(self.__getLiteralValue(arg))
+        return args
+    
+    @property
+    def callArgsCount(self):
+        """
+        Public method to get the number of args a function call has.
+        
+        @return number of args a function call has
+        @rtype int
+        """
+        if 'call' in self.__context and hasattr(self.__context['call'], 'args'):
+            return len(self.__context['call'].args)
+        else:
+            return None
+    
+    @property
+    def callFunctionName(self):
+        """
+        Public method to get the name (not FQ) of a function call.
+        
+        @return name (not FQ) of a function call
+        @rtype str
+        """
+        return self.__context.get('name')
+    
+    @property
+    def callFunctionNameQual(self):
+        """
+        Public method to get the FQ name of a function call.
+        
+        @return FQ name of a function call
+        @rtype str
+        """
+        return self.__context.get('qualname')
+    
+    @property
+    def callKeywords(self):
+        """
+        Public method to get a dictionary of keyword parameters.
+        
+        @return dictionary of keyword parameters
+        @rtype dict
+        """
+        if (
+            'call' in self.__context and
+            hasattr(self.__context['call'], 'keywords')
+        ):
+            returnDict = {}
+            for kw in self.__context['call'].keywords:
+                if hasattr(kw.value, 'attr'):
+                    returnDict[kw.arg] = kw.value.attr
+                else:
+                    returnDict[kw.arg] = self.__getLiteralValue(kw.value)
+            return returnDict
+        
+        else:
+            return None
+    
+    @property
+    def node(self):
+        """
+        Public method to get the raw AST node associated with the context.
+        
+        @return raw AST node associated with the context
+        @rtype ast.AST
+        """
+        return self.__context.get('node')
+    
+    @property
+    def stringVal(self):
+        """
+        Public method to get the value of a standalone unicode or string
+        object.
+        
+        @return value of a standalone unicode or string object
+        @rtype str
+        """
+        return self.__context.get('str')
+    
+    @property
+    def bytesVal(self):
+        """
+        Public method to get the value of a standalone bytes object.
+        
+        @return value of a standalone bytes object
+        @rtype bytes
+        """
+        return self.__context.get('bytes')
+    
+    @property
+    def stringValAsEscapedBytes(self):
+        r"""
+        Public method to get the escaped value of the object.
+        
+        Turn the value of a string or bytes object into a byte sequence with
+        unknown, control, and \\ characters escaped.
+
+        This function should be used when looking for a known sequence in a
+        potentially badly encoded string in the code.
+        
+        @return sequence of printable ascii bytes representing original string
+        @rtype str
+        """
+        val = self.stringVal
+        if val is not None:
+            # it's any of str or unicode in py2, or str in py3
+            return val.encode('unicode_escape')
+        
+        val = self.bytesVal
+        if val is not None:
+            return SecurityUtils.escapedBytesRepresentation(val)
+        
+        return None
+    
+    @property
+    def statement(self):
+        """
+        Public method to get the raw AST for the current statement.
+        
+        @return raw AST for the current statement
+        @rtype ast.AST
+        """
+        return self.__context.get('statement')
+    
+    @property
+    def functionDefDefaultsQual(self):
+        """
+        Public method to get a list of fully qualified default values in a
+        function def.
+        
+        @return list of fully qualified default values in a function def
+        @rtype list
+        """
+        defaults = []
+        if (
+            'node' in self.__context and
+            hasattr(self.__context['node'], 'args') and
+            hasattr(self.__context['node'].args, 'defaults')
+        ):
+            for default in self.__context['node'].args.defaults:
+                defaults.append(SecurityUtils.getQualAttr(
+                    default,
+                    self.__context['import_aliases']))
+        
+        return defaults
+    
+    def __getLiteralValue(self, literal):
+        """
+        Private method to turn AST literals into native Python types.
+        
+        @param literal AST literal to be converted
+        @type ast.AST
+        @return converted Python object
+        @rtype Any
+        """
+        if isinstance(literal, ast.Num):
+            literalValue = literal.n
+        
+        elif isinstance(literal, ast.Str):
+            literalValue = literal.s
+        
+        elif isinstance(literal, ast.List):
+            returnList = list()
+            for li in literal.elts:
+                returnList.append(self.__getLiteralValue(li))
+            literalValue = returnList
+        
+        elif isinstance(literal, ast.Tuple):
+            returnTuple = tuple()
+            for ti in literal.elts:
+                returnTuple = returnTuple + (self.__getLiteralValue(ti),)
+            literalValue = returnTuple
+        
+        elif isinstance(literal, ast.Set):
+            returnSet = set()
+            for si in literal.elts:
+                returnSet.add(self.__getLiteralValue(si))
+            literalValue = returnSet
+        
+        elif isinstance(literal, ast.Dict):
+            literalValue = dict(zip(literal.keys, literal.values))
+        
+        elif isinstance(literal, ast.Ellipsis):
+            # what do we want to do with this?
+            literalValue = None
+        
+        elif isinstance(literal, ast.Name):
+            literalValue = literal.id
+        
+        # NameConstants are only part of the AST in Python 3. NameConstants
+        # tend to refer to things like True and False. This prevents them from
+        # being re-assigned in Python 3.
+        elif (
+            sys.version_info >= (3, 0, 0) and
+            isinstance(literal, ast.NameConstant)
+        ):
+            literalValue = str(literal.value)
+        
+        # Bytes are only part of the AST in Python 3
+        elif (
+            sys.version_info >= (3, 0, 0) and
+            isinstance(literal, ast.Bytes)
+        ):
+            literalValue = literal.s
+        
+        else:
+            literalValue = None
+        
+        return literalValue
+    
+    def getCallArgValue(self, argumentName):
+        """
+        Public method to get the value of a named argument in a function call.
+        
+        @param argumentName name of the argument to get the value for
+        @type str
+        @return value of the named argument
+        @rtype Any
+        """
+        kwdValues = self.callKeywords
+        if kwdValues is not None and argumentName in kwdValues:
+            return kwdValues[argumentName]
+    
+    def checkCallArgValue(self, argumentName, argumentValues=None):
+        """
+        Public method to check for a value of a named argument in a function
+        call.
+        
+        @param argumentName name of the argument to be checked
+        @type str
+        @param argumentValues value or list of values to test against
+        @type Any or list of Any
+        @return True if argument found and matched, False if found and not
+            matched, None if argument not found at all
+        @rtype bool or None
+        """
+        argValue = self.getCallArgValue(argumentName)
+        if argValue is not None:
+            if not isinstance(argumentValues, list):
+                # if passed a single value, or a tuple, convert to a list
+                argumentValues = list((argumentValues,))
+            for val in argumentValues:
+                if argValue == val:
+                    return True
+            return False
+        else:
+            # argument name not found, return None to allow testing for this
+            # eventuality
+            return None
+    
+    def getLinenoForCallArg(self, argumentName):
+        """
+        Public method to get the line number for a specific named argument.
+        
+        @param argumentName name of the argument to get the line number for
+        @type str
+        @return line number of the found argument or -1
+        @rtype int
+        """
+        if hasattr(self.node, 'keywords'):
+            for key in self.node.keywords:
+                if key.arg == argumentName:
+                    return key.value.lineno
+    
+    def getCallArgAtPosition(self, positionNum):
+        """
+        Public method to get a positional argument at the specified position
+        (if it exists).
+        
+        @param positionNum index of the argument to get the value for
+        @type int
+        @return value of the argument at the specified position if it exists
+        @rtype Any or None
+        """
+        maxArgs = self.callArgsCount
+        if maxArgs and positionNum < maxArgs:
+            return self.__getLiteralValue(
+                self.__context['call'].args[positionNum]
+            )
+        else:
+            return None
+    
+    def isModuleBeingImported(self, module):
+        """
+        Public method to check for the given module is currently being
+        imported.
+        
+        @param module module name to look for
+        @type str
+        @return flag indicating the given module was found
+        @rtype bool
+        """
+        return self.__context.get('module') == module
+    
+    def isModuleImportedExact(self, module):
+        """
+        Public method to check if a given module has been imported; only exact
+        matches.
+        
+        @param module module name to look for
+        @type str
+        @return flag indicating the given module was found
+        @rtype bool
+        """
+        return module in self.__context.get('imports', [])
+    
+    def isModuleImportedLike(self, module):
+        """
+        Public method to check if a given module has been imported; given
+        module exists.
+        
+        @param module module name to look for
+        @type str
+        @return flag indicating the given module was found
+        @rtype bool
+        """
+        if 'imports' in self.__context:
+            for imp in self.__context['imports']:
+                if module in imp:
+                    return True
+        
+        return False

eric ide

mercurial