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

changeset 7612
ca1ce1e0fcff
child 7613
382f89c11e27
equal deleted inserted replaced
7611:d546c4e72f52 7612:ca1ce1e0fcff
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a context class for security related checks.
8 """
9
10 #
11 # This code is a modified version of the one in 'bandit'.
12 #
13 # Original Copyright 2014 Hewlett-Packard Development Company, L.P.
14 #
15 # SPDX-License-Identifier: Apache-2.0
16 #
17
18 import ast
19 import sys
20
21 from . import SecurityUtils
22
23
24 class SecurityContext(object):
25 """
26 Class implementing a context class for security related checks.
27 """
28 def __init__(self, contextObject=None):
29 """
30 Constructor
31
32 Initialize the class with a context dictionary or an empty
33 dictionary.
34
35 @param contextObject context dictionary to be used to populate the
36 class
37 @type dict
38 """
39 if contextObject is not None:
40 self.__context = contextObject
41 else:
42 self.__context = {}
43
44 def __repr__(self):
45 """
46 Private method to generate representation of object for printing or
47 interactive use.
48
49 @return string representation of the object
50 @rtype str
51 """
52 return "<SecurityContext {0}>".formar(self.__context)
53
54 @property
55 def callArgs(self):
56 """
57 Public method to get a list of function args.
58
59 @return list of function args
60 @rtype list
61 """
62 args = []
63 if (
64 'call' in self.__context and
65 hasattr(self.__context['call'], 'args')
66 ):
67 for arg in self.__context['call'].args:
68 if hasattr(arg, 'attr'):
69 args.append(arg.attr)
70 else:
71 args.append(self.__getLiteralValue(arg))
72 return args
73
74 @property
75 def callArgsCount(self):
76 """
77 Public method to get the number of args a function call has.
78
79 @return number of args a function call has
80 @rtype int
81 """
82 if 'call' in self.__context and hasattr(self.__context['call'], 'args'):
83 return len(self.__context['call'].args)
84 else:
85 return None
86
87 @property
88 def callFunctionName(self):
89 """
90 Public method to get the name (not FQ) of a function call.
91
92 @return name (not FQ) of a function call
93 @rtype str
94 """
95 return self.__context.get('name')
96
97 @property
98 def callFunctionNameQual(self):
99 """
100 Public method to get the FQ name of a function call.
101
102 @return FQ name of a function call
103 @rtype str
104 """
105 return self.__context.get('qualname')
106
107 @property
108 def callKeywords(self):
109 """
110 Public method to get a dictionary of keyword parameters.
111
112 @return dictionary of keyword parameters
113 @rtype dict
114 """
115 if (
116 'call' in self.__context and
117 hasattr(self.__context['call'], 'keywords')
118 ):
119 returnDict = {}
120 for kw in self.__context['call'].keywords:
121 if hasattr(kw.value, 'attr'):
122 returnDict[kw.arg] = kw.value.attr
123 else:
124 returnDict[kw.arg] = self.__getLiteralValue(kw.value)
125 return returnDict
126
127 else:
128 return None
129
130 @property
131 def node(self):
132 """
133 Public method to get the raw AST node associated with the context.
134
135 @return raw AST node associated with the context
136 @rtype ast.AST
137 """
138 return self.__context.get('node')
139
140 @property
141 def stringVal(self):
142 """
143 Public method to get the value of a standalone unicode or string
144 object.
145
146 @return value of a standalone unicode or string object
147 @rtype str
148 """
149 return self.__context.get('str')
150
151 @property
152 def bytesVal(self):
153 """
154 Public method to get the value of a standalone bytes object.
155
156 @return value of a standalone bytes object
157 @rtype bytes
158 """
159 return self.__context.get('bytes')
160
161 @property
162 def stringValAsEscapedBytes(self):
163 r"""
164 Public method to get the escaped value of the object.
165
166 Turn the value of a string or bytes object into a byte sequence with
167 unknown, control, and \\ characters escaped.
168
169 This function should be used when looking for a known sequence in a
170 potentially badly encoded string in the code.
171
172 @return sequence of printable ascii bytes representing original string
173 @rtype str
174 """
175 val = self.stringVal
176 if val is not None:
177 # it's any of str or unicode in py2, or str in py3
178 return val.encode('unicode_escape')
179
180 val = self.bytesVal
181 if val is not None:
182 return SecurityUtils.escapedBytesRepresentation(val)
183
184 return None
185
186 @property
187 def statement(self):
188 """
189 Public method to get the raw AST for the current statement.
190
191 @return raw AST for the current statement
192 @rtype ast.AST
193 """
194 return self.__context.get('statement')
195
196 @property
197 def functionDefDefaultsQual(self):
198 """
199 Public method to get a list of fully qualified default values in a
200 function def.
201
202 @return list of fully qualified default values in a function def
203 @rtype list
204 """
205 defaults = []
206 if (
207 'node' in self.__context and
208 hasattr(self.__context['node'], 'args') and
209 hasattr(self.__context['node'].args, 'defaults')
210 ):
211 for default in self.__context['node'].args.defaults:
212 defaults.append(SecurityUtils.getQualAttr(
213 default,
214 self.__context['import_aliases']))
215
216 return defaults
217
218 def __getLiteralValue(self, literal):
219 """
220 Private method to turn AST literals into native Python types.
221
222 @param literal AST literal to be converted
223 @type ast.AST
224 @return converted Python object
225 @rtype Any
226 """
227 if isinstance(literal, ast.Num):
228 literalValue = literal.n
229
230 elif isinstance(literal, ast.Str):
231 literalValue = literal.s
232
233 elif isinstance(literal, ast.List):
234 returnList = list()
235 for li in literal.elts:
236 returnList.append(self.__getLiteralValue(li))
237 literalValue = returnList
238
239 elif isinstance(literal, ast.Tuple):
240 returnTuple = tuple()
241 for ti in literal.elts:
242 returnTuple = returnTuple + (self.__getLiteralValue(ti),)
243 literalValue = returnTuple
244
245 elif isinstance(literal, ast.Set):
246 returnSet = set()
247 for si in literal.elts:
248 returnSet.add(self.__getLiteralValue(si))
249 literalValue = returnSet
250
251 elif isinstance(literal, ast.Dict):
252 literalValue = dict(zip(literal.keys, literal.values))
253
254 elif isinstance(literal, ast.Ellipsis):
255 # what do we want to do with this?
256 literalValue = None
257
258 elif isinstance(literal, ast.Name):
259 literalValue = literal.id
260
261 # NameConstants are only part of the AST in Python 3. NameConstants
262 # tend to refer to things like True and False. This prevents them from
263 # being re-assigned in Python 3.
264 elif (
265 sys.version_info >= (3, 0, 0) and
266 isinstance(literal, ast.NameConstant)
267 ):
268 literalValue = str(literal.value)
269
270 # Bytes are only part of the AST in Python 3
271 elif (
272 sys.version_info >= (3, 0, 0) and
273 isinstance(literal, ast.Bytes)
274 ):
275 literalValue = literal.s
276
277 else:
278 literalValue = None
279
280 return literalValue
281
282 def getCallArgValue(self, argumentName):
283 """
284 Public method to get the value of a named argument in a function call.
285
286 @param argumentName name of the argument to get the value for
287 @type str
288 @return value of the named argument
289 @rtype Any
290 """
291 kwdValues = self.callKeywords
292 if kwdValues is not None and argumentName in kwdValues:
293 return kwdValues[argumentName]
294
295 def checkCallArgValue(self, argumentName, argumentValues=None):
296 """
297 Public method to check for a value of a named argument in a function
298 call.
299
300 @param argumentName name of the argument to be checked
301 @type str
302 @param argumentValues value or list of values to test against
303 @type Any or list of Any
304 @return True if argument found and matched, False if found and not
305 matched, None if argument not found at all
306 @rtype bool or None
307 """
308 argValue = self.getCallArgValue(argumentName)
309 if argValue is not None:
310 if not isinstance(argumentValues, list):
311 # if passed a single value, or a tuple, convert to a list
312 argumentValues = list((argumentValues,))
313 for val in argumentValues:
314 if argValue == val:
315 return True
316 return False
317 else:
318 # argument name not found, return None to allow testing for this
319 # eventuality
320 return None
321
322 def getLinenoForCallArg(self, argumentName):
323 """
324 Public method to get the line number for a specific named argument.
325
326 @param argumentName name of the argument to get the line number for
327 @type str
328 @return line number of the found argument or -1
329 @rtype int
330 """
331 if hasattr(self.node, 'keywords'):
332 for key in self.node.keywords:
333 if key.arg == argumentName:
334 return key.value.lineno
335
336 def getCallArgAtPosition(self, positionNum):
337 """
338 Public method to get a positional argument at the specified position
339 (if it exists).
340
341 @param positionNum index of the argument to get the value for
342 @type int
343 @return value of the argument at the specified position if it exists
344 @rtype Any or None
345 """
346 maxArgs = self.callArgsCount
347 if maxArgs and positionNum < maxArgs:
348 return self.__getLiteralValue(
349 self.__context['call'].args[positionNum]
350 )
351 else:
352 return None
353
354 def isModuleBeingImported(self, module):
355 """
356 Public method to check for the given module is currently being
357 imported.
358
359 @param module module name to look for
360 @type str
361 @return flag indicating the given module was found
362 @rtype bool
363 """
364 return self.__context.get('module') == module
365
366 def isModuleImportedExact(self, module):
367 """
368 Public method to check if a given module has been imported; only exact
369 matches.
370
371 @param module module name to look for
372 @type str
373 @return flag indicating the given module was found
374 @rtype bool
375 """
376 return module in self.__context.get('imports', [])
377
378 def isModuleImportedLike(self, module):
379 """
380 Public method to check if a given module has been imported; given
381 module exists.
382
383 @param module module name to look for
384 @type str
385 @return flag indicating the given module was found
386 @rtype bool
387 """
388 if 'imports' in self.__context:
389 for imp in self.__context['imports']:
390 if module in imp:
391 return True
392
393 return False

eric ide

mercurial