src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Security/SecurityContext.py

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

eric ide

mercurial