conviso-ast 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. conviso_ast-3.0.0.data/scripts/flow_bash_completer.sh +21 -0
  2. conviso_ast-3.0.0.data/scripts/flow_fish_completer.fish +1 -0
  3. conviso_ast-3.0.0.data/scripts/flow_zsh_completer.sh +32 -0
  4. conviso_ast-3.0.0.dist-info/METADATA +37 -0
  5. conviso_ast-3.0.0.dist-info/RECORD +128 -0
  6. conviso_ast-3.0.0.dist-info/WHEEL +5 -0
  7. conviso_ast-3.0.0.dist-info/entry_points.txt +3 -0
  8. conviso_ast-3.0.0.dist-info/top_level.txt +1 -0
  9. convisoappsec/__init__.py +0 -0
  10. convisoappsec/common/__init__.py +5 -0
  11. convisoappsec/common/box.py +251 -0
  12. convisoappsec/common/cleaner.py +78 -0
  13. convisoappsec/common/docker.py +399 -0
  14. convisoappsec/common/exceptions.py +8 -0
  15. convisoappsec/common/git_data_parser.py +76 -0
  16. convisoappsec/common/graphql/__init__.py +0 -0
  17. convisoappsec/common/graphql/error_handlers.py +75 -0
  18. convisoappsec/common/graphql/errors.py +16 -0
  19. convisoappsec/common/graphql/low_client.py +51 -0
  20. convisoappsec/common/retry_handler.py +40 -0
  21. convisoappsec/common/strings.py +8 -0
  22. convisoappsec/flow/__init__.py +3 -0
  23. convisoappsec/flow/api.py +104 -0
  24. convisoappsec/flow/cleaner.py +118 -0
  25. convisoappsec/flow/graphql_api/__init__.py +0 -0
  26. convisoappsec/flow/graphql_api/beta/__init__.py +0 -0
  27. convisoappsec/flow/graphql_api/beta/client.py +18 -0
  28. convisoappsec/flow/graphql_api/beta/models/__init__.py +0 -0
  29. convisoappsec/flow/graphql_api/beta/models/issues/__init__.py +0 -0
  30. convisoappsec/flow/graphql_api/beta/models/issues/container.py +72 -0
  31. convisoappsec/flow/graphql_api/beta/models/issues/iac.py +6 -0
  32. convisoappsec/flow/graphql_api/beta/models/issues/normalize.py +13 -0
  33. convisoappsec/flow/graphql_api/beta/models/issues/sast.py +53 -0
  34. convisoappsec/flow/graphql_api/beta/models/issues/sca.py +78 -0
  35. convisoappsec/flow/graphql_api/beta/resources_api.py +142 -0
  36. convisoappsec/flow/graphql_api/beta/schemas/__init__.py +0 -0
  37. convisoappsec/flow/graphql_api/beta/schemas/mutations/__init__.py +61 -0
  38. convisoappsec/flow/graphql_api/beta/schemas/resolvers/__init__.py +0 -0
  39. convisoappsec/flow/graphql_api/v1/__init__.py +0 -0
  40. convisoappsec/flow/graphql_api/v1/client.py +46 -0
  41. convisoappsec/flow/graphql_api/v1/models/__init__.py +0 -0
  42. convisoappsec/flow/graphql_api/v1/models/asset.py +14 -0
  43. convisoappsec/flow/graphql_api/v1/models/issues.py +16 -0
  44. convisoappsec/flow/graphql_api/v1/models/project.py +35 -0
  45. convisoappsec/flow/graphql_api/v1/resources_api.py +489 -0
  46. convisoappsec/flow/graphql_api/v1/schemas/__init__.py +0 -0
  47. convisoappsec/flow/graphql_api/v1/schemas/mutations/__init__.py +212 -0
  48. convisoappsec/flow/graphql_api/v1/schemas/resolvers/__init__.py +180 -0
  49. convisoappsec/flow/source_code_scanner/__init__.py +9 -0
  50. convisoappsec/flow/source_code_scanner/exceptions.py +2 -0
  51. convisoappsec/flow/source_code_scanner/scc.py +68 -0
  52. convisoappsec/flow/source_code_scanner/source_code_scanner.py +177 -0
  53. convisoappsec/flow/util/__init__.py +7 -0
  54. convisoappsec/flow/util/ci_provider.py +99 -0
  55. convisoappsec/flow/util/metrics.py +16 -0
  56. convisoappsec/flow/util/source_code_compressor.py +22 -0
  57. convisoappsec/flow/version_control_system_adapter.py +528 -0
  58. convisoappsec/flow/version_searchers/__init__.py +9 -0
  59. convisoappsec/flow/version_searchers/sorted_by_versioning_style.py +85 -0
  60. convisoappsec/flow/version_searchers/timebased_version_seacher.py +39 -0
  61. convisoappsec/flow/version_searchers/version_searcher_result.py +33 -0
  62. convisoappsec/flow/versioning_style/__init__.py +0 -0
  63. convisoappsec/flow/versioning_style/semantic_versioning.py +44 -0
  64. convisoappsec/flowcli/__init__.py +3 -0
  65. convisoappsec/flowcli/__main__.py +4 -0
  66. convisoappsec/flowcli/assets/__init__.py +4 -0
  67. convisoappsec/flowcli/assets/create.py +88 -0
  68. convisoappsec/flowcli/assets/entrypoint.py +20 -0
  69. convisoappsec/flowcli/assets/ls.py +63 -0
  70. convisoappsec/flowcli/ast/__init__.py +3 -0
  71. convisoappsec/flowcli/ast/entrypoint.py +427 -0
  72. convisoappsec/flowcli/common.py +175 -0
  73. convisoappsec/flowcli/companies/__init__.py +0 -0
  74. convisoappsec/flowcli/companies/ls.py +25 -0
  75. convisoappsec/flowcli/container/__init__.py +3 -0
  76. convisoappsec/flowcli/container/entrypoint.py +17 -0
  77. convisoappsec/flowcli/container/run.py +306 -0
  78. convisoappsec/flowcli/context.py +49 -0
  79. convisoappsec/flowcli/deploy/__init__.py +0 -0
  80. convisoappsec/flowcli/deploy/create/__init__.py +4 -0
  81. convisoappsec/flowcli/deploy/create/context.py +12 -0
  82. convisoappsec/flowcli/deploy/create/entrypoint.py +31 -0
  83. convisoappsec/flowcli/deploy/create/with_/__init__.py +3 -0
  84. convisoappsec/flowcli/deploy/create/with_/entrypoint.py +20 -0
  85. convisoappsec/flowcli/deploy/create/with_/tag_tracker/__init__.py +4 -0
  86. convisoappsec/flowcli/deploy/create/with_/tag_tracker/context.py +11 -0
  87. convisoappsec/flowcli/deploy/create/with_/tag_tracker/entrypoint.py +30 -0
  88. convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/__init__.py +4 -0
  89. convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/entrypoint.py +21 -0
  90. convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/time_.py +84 -0
  91. convisoappsec/flowcli/deploy/create/with_/tag_tracker/sort_by/versioning_style.py +115 -0
  92. convisoappsec/flowcli/deploy/create/with_/values.py +133 -0
  93. convisoappsec/flowcli/entrypoint.py +103 -0
  94. convisoappsec/flowcli/environment_checker.py +45 -0
  95. convisoappsec/flowcli/findings/__init__.py +4 -0
  96. convisoappsec/flowcli/findings/create/__init__.py +4 -0
  97. convisoappsec/flowcli/findings/create/entrypoint.py +18 -0
  98. convisoappsec/flowcli/findings/create/with_/__init__.py +3 -0
  99. convisoappsec/flowcli/findings/create/with_/entrypoint.py +19 -0
  100. convisoappsec/flowcli/findings/create/with_/version_tracker.py +93 -0
  101. convisoappsec/flowcli/findings/entrypoint.py +19 -0
  102. convisoappsec/flowcli/findings/import_sarif/__init__.py +4 -0
  103. convisoappsec/flowcli/findings/import_sarif/entrypoint.py +430 -0
  104. convisoappsec/flowcli/help_option.py +18 -0
  105. convisoappsec/flowcli/iac/__init__.py +3 -0
  106. convisoappsec/flowcli/iac/entrypoint.py +17 -0
  107. convisoappsec/flowcli/iac/run.py +328 -0
  108. convisoappsec/flowcli/requirements_verifier.py +132 -0
  109. convisoappsec/flowcli/sast/__init__.py +3 -0
  110. convisoappsec/flowcli/sast/entrypoint.py +17 -0
  111. convisoappsec/flowcli/sast/run.py +485 -0
  112. convisoappsec/flowcli/sbom/__init__.py +3 -0
  113. convisoappsec/flowcli/sbom/entrypoint.py +17 -0
  114. convisoappsec/flowcli/sbom/generate.py +235 -0
  115. convisoappsec/flowcli/sca/__init__.py +3 -0
  116. convisoappsec/flowcli/sca/entrypoint.py +17 -0
  117. convisoappsec/flowcli/sca/run.py +479 -0
  118. convisoappsec/flowcli/vulnerability/__init__.py +3 -0
  119. convisoappsec/flowcli/vulnerability/assert_security_rules.py +201 -0
  120. convisoappsec/flowcli/vulnerability/container_vulnerability_manager.py +175 -0
  121. convisoappsec/flowcli/vulnerability/entrypoint.py +18 -0
  122. convisoappsec/flowcli/vulnerability/rules_schema.json +53 -0
  123. convisoappsec/flowcli/vulnerability/run.py +487 -0
  124. convisoappsec/logger.py +29 -0
  125. convisoappsec/sast/__init__.py +0 -0
  126. convisoappsec/sast/decision.py +45 -0
  127. convisoappsec/sast/sastbox.py +296 -0
  128. convisoappsec/version.py +1 -0
@@ -0,0 +1,399 @@
1
+ import tarfile
2
+ import tempfile
3
+ from contextlib import suppress
4
+ from datetime import datetime
5
+ from threading import Lock, Thread
6
+ from uuid import uuid4
7
+
8
+ from convisoappsec.flow.util import SourceCodeCompressor
9
+ from convisoappsec.logger import LOGGER
10
+
11
+ import docker
12
+
13
+ from .exceptions import *
14
+
15
+ mutex = Lock()
16
+
17
+
18
+ class EventLogging(Thread):
19
+
20
+ def __init__(self, logger, events):
21
+ super().__init__(name='Docker-Events', daemon=True)
22
+ self.logger = logger or LOGGER
23
+ self.events = events
24
+
25
+ def docker_log(self, event):
26
+ ts = datetime.fromtimestamp(event['time']).strftime('%d/%m/%Y %H:%M:%S')
27
+ message = "{timestamp}|{Type} {Action} on {Actor[ID]}".format(**event, timestamp=ts)
28
+ self.logger.debug(message)
29
+
30
+ def run(self):
31
+ for event in self.events:
32
+ if 'conviso-cli-' in event['Actor']['ID']:
33
+ self.docker_log(event)
34
+ self.logger.debug('Docker Events closed')
35
+
36
+
37
+ class Credentials:
38
+
39
+ AUTHS = {}
40
+
41
+ def __init__(self, docker, logger):
42
+ self.docker = docker
43
+ self.logger = logger or LOGGER
44
+
45
+ def login(self, registry, password, username='AWS'):
46
+ mutex.acquire()
47
+ if not registry in Credentials.AUTHS.keys():
48
+ self.logger.debug(
49
+ " \U0001F511 Checking Authorization for {0}".format(registry))
50
+ login_args = {
51
+ 'registry': registry,
52
+ 'username': username,
53
+ 'password': password,
54
+ 'reauth': True,
55
+ }
56
+ try:
57
+ login_result = self.docker.login(**login_args)
58
+ status = login_result['Status']
59
+ self.logger.debug("Login result was {0}".format(login_result))
60
+ if status == 'Login Succeeded':
61
+ Credentials.AUTHS[registry] = password
62
+ self.logger.debug(' \U0001F513 ' + status)
63
+ except docker.errors.APIError:
64
+ self.logger.debug("Docker API Error")
65
+ mutex.release()
66
+
67
+
68
+ class SCSCommon:
69
+
70
+ # General Settings
71
+ DEFAULT_REGISTRY = 'public.ecr.aws/convisoappsec'
72
+ DEFAULT_TAG = 'latest'
73
+ DEFAULT_CONTAINER_CODE_DIR = '/code'
74
+ SUCCESS_EXIT_CODE = 0
75
+ DOCKER_CLIENT = None
76
+ DOCKER_CREDENTIALS = None
77
+ DOCKER_EVENTS = None
78
+ DOCKER_EVENTS_THREAD = None
79
+ TEMPDIR = None
80
+
81
+ def __init__(self, tag=None, registry=None, repository_name=None, token=None, logger=None, command=None, repository_dir=None):
82
+ self.logger = logger or LOGGER
83
+ self.token = token
84
+ uuid = str(uuid4())
85
+ self.docker = self._get_docker_client()
86
+ self.__container_name = "conviso-cli-{0}".format(uuid)
87
+ self.__docker_events = self._get_docker_events()
88
+ self.__docker_events_thread = self._get_docker_events_thread()
89
+ self.__credentials = self._get_docker_credentials()
90
+ self.__source_code_volume_name = "conviso-cli-{0}".format(uuid)
91
+
92
+ self.container = None
93
+ self.tag = tag or self.DEFAULT_TAG
94
+ self.registry = registry or self.DEFAULT_REGISTRY
95
+ self.repository_name = repository_name
96
+ self.command = command
97
+ self.repository_dir = repository_dir
98
+ if token:
99
+ self._login(self.token)
100
+
101
+ def _get_docker_client(self):
102
+ mutex.acquire()
103
+ if not SCSCommon.DOCKER_CLIENT:
104
+ SCSCommon.DOCKER_CLIENT = docker.from_env(
105
+ version="auto"
106
+ )
107
+ mutex.release()
108
+ return SCSCommon.DOCKER_CLIENT
109
+
110
+ def _get_docker_credentials(self):
111
+ mutex.acquire()
112
+ if not SCSCommon.DOCKER_CREDENTIALS:
113
+ SCSCommon.DOCKER_CREDENTIALS = Credentials(
114
+ docker=self.docker,
115
+ logger=self.logger
116
+ )
117
+ mutex.release()
118
+ return SCSCommon.DOCKER_CREDENTIALS
119
+
120
+ def _get_docker_events(self):
121
+ mutex.acquire()
122
+ if not SCSCommon.DOCKER_EVENTS:
123
+ SCSCommon.DOCKER_EVENTS = self.docker.events(decode=True)
124
+ mutex.release()
125
+ return SCSCommon.DOCKER_EVENTS
126
+
127
+ def _get_docker_events_thread(self):
128
+ mutex.acquire()
129
+ if not SCSCommon.DOCKER_EVENTS_THREAD:
130
+ SCSCommon.DOCKER_EVENTS_THREAD = EventLogging(
131
+ logger=self.logger,
132
+ events=self.__docker_events,
133
+ )
134
+ SCSCommon.DOCKER_EVENTS_THREAD.start()
135
+ mutex.release()
136
+ return SCSCommon.DOCKER_EVENTS_THREAD
137
+
138
+ def _login(self, password, username='AWS'):
139
+ self.__credentials.login(
140
+ registry=self.registry,
141
+ password=password,
142
+ )
143
+
144
+ def _create_host_output_directory(self):
145
+ """Creates a directory on the host machine to store container outputs.
146
+
147
+ Returns:
148
+ string: absolute path to the directory
149
+ """
150
+ if not SCSCommon.TEMPDIR:
151
+ SCSCommon.TEMPDIR = tempfile.mkdtemp(
152
+ prefix='conviso-output-',
153
+ dir=self.repository_dir
154
+ )
155
+
156
+ self.logger.debug("Created output directory at {}".format(
157
+ SCSCommon.TEMPDIR
158
+ ))
159
+ return SCSCommon.TEMPDIR
160
+
161
+ @property
162
+ def size(self):
163
+ registry_data = self.docker.images.get_registry_data(self.image)
164
+
165
+ descriptor = registry_data.attrs.get('Descriptor', {})
166
+ return descriptor.get('size') * 1024 * 1024
167
+
168
+ def run(self):
169
+ container = self.__create_container()
170
+ self.__inject_container_source_code(container)
171
+ container.start()
172
+
173
+ container_stderr = container.logs(
174
+ stream=True,
175
+ stdout=False,
176
+ stderr=True
177
+ )
178
+ for message in container_stderr:
179
+ self.logger.debug(message)
180
+
181
+ container_stdout = container.logs(
182
+ stream=True,
183
+ stdout=True,
184
+ stderr=False,
185
+ )
186
+
187
+ for message in container_stdout:
188
+ self.logger.debug(message)
189
+
190
+ def wait(self):
191
+ wait_result = self.__container.wait()
192
+ status_code = wait_result.get('StatusCode')
193
+
194
+ if not status_code == self.SUCCESS_EXIT_CODE:
195
+ raise CommonException()
196
+
197
+ return status_code
198
+
199
+ def __has_method(self, method_name):
200
+ return hasattr(self, method_name)
201
+
202
+ def pull(self):
203
+ '''
204
+ Acts as docker pull <image_name>, downloading the image configured in the instance.
205
+
206
+ Returns:
207
+ # Example:
208
+ {
209
+ 'status': 'Downloading',
210
+ 'progressDetail': {'current': int, 'total': int},
211
+ 'id': 'string'
212
+ }
213
+ '''
214
+ self.logger.debug(
215
+ "Pulling scanner image {}. It might takes some minutes.".format(
216
+ self.repository_name)
217
+ )
218
+ if self.has_pre_pull:
219
+ self._pre_pull()
220
+ self.logger.debug("End of pre_pull method.")
221
+ try:
222
+ return self.docker.images.pull(
223
+ repository=self.repository,
224
+ tag=self.tag
225
+ )
226
+ except docker.errors.ImageNotFound:
227
+ self.logger.debug(
228
+ "Image {} not found on registry".format(self.image)
229
+ )
230
+ return False
231
+
232
+ def __get_container(self):
233
+ return self.docker.containers.get(
234
+ self.__container_name
235
+ )
236
+
237
+ @property
238
+ def __container(self):
239
+ try:
240
+ return self.__get_container()
241
+ except:
242
+ return self.__create_container()
243
+
244
+ def __create_container(self):
245
+ self.logger.debug(
246
+ "Creating Container {0}".format(self.__container_name)
247
+ )
248
+ return self.docker.containers.create(
249
+ self.image,
250
+ name=self.__container_name,
251
+ volumes=self.volumes,
252
+ detach=True,
253
+ command=self.command
254
+ )
255
+
256
+ @property
257
+ def repository(self):
258
+ return "{registry}/{repository_name}".format(
259
+ registry=self.registry,
260
+ repository_name=self.repository_name,
261
+ )
262
+
263
+ @property
264
+ def image(self):
265
+ return "{repository}:{tag}".format(
266
+ repository=self.repository,
267
+ tag=self.tag,
268
+ )
269
+
270
+ @property
271
+ def has_pre_pull(self):
272
+ return self.__has_method('_pre_pull')
273
+
274
+ @property
275
+ def has_read_scan_stdout(self):
276
+ return self.__has_method('_read_scan_stdout')
277
+
278
+ @property
279
+ def has_read_scan_stderr(self):
280
+ return self.__has_method('_read_scan_stderr')
281
+
282
+ @property
283
+ def volumes(self):
284
+ return {
285
+ self.__source_code_volume_name: {
286
+ 'bind': SCSCommon.DEFAULT_CONTAINER_CODE_DIR,
287
+ 'mode': 'rw',
288
+ }
289
+ }
290
+
291
+ @property
292
+ def name(self):
293
+ return self.__container_name
294
+
295
+ def __del__(self):
296
+ self.logger.debug(
297
+ "Removing container {0}".format(self.__container_name)
298
+ )
299
+ with suppress(Exception):
300
+ self.container.remove(v=True, force=True)
301
+
302
+ def __inject_container_source_code(self, container):
303
+ try:
304
+ self.logger.debug(
305
+ "Loading Source code into {0}".format(self.__container_name)
306
+ )
307
+
308
+ with tempfile.TemporaryFile() as wrapper_file:
309
+ compressor = SourceCodeCompressor(
310
+ self.repository_dir
311
+ )
312
+ compressor.write_to(wrapper_file)
313
+ wrapper_file.seek(0)
314
+ container.put_archive(
315
+ SCSCommon.DEFAULT_CONTAINER_CODE_DIR,
316
+ wrapper_file
317
+ )
318
+
319
+ except Exception as e:
320
+ self.logger.error(e)
321
+
322
+ def _get_container_tarball_chunks(self, container_filepath):
323
+ """From the path provided you get the file binary/chunks from the container.
324
+
325
+ Args:
326
+ container_filepath (string): Absolute Path of the file within the container
327
+
328
+ Returns:
329
+ int: For further information see https://docker-py.readthedocs.io/en/stable/containers.html#docker.models.containers.Container.get_archive
330
+ """
331
+ try:
332
+ container = self.__container
333
+ chunks, _ = container.get_archive(container_filepath)
334
+ return chunks
335
+
336
+ except docker.errors.NotFound:
337
+ self.logger.debug("{} does not detected issues, continuing...".format(
338
+ self.repository_name
339
+ ))
340
+ return None
341
+
342
+ def _extract_tarball_chunks(self, tarball_chunks, report_filename):
343
+ """
344
+
345
+ Args:
346
+ tarball_chunks (int): The number of bytes returned by each iteration of the generator
347
+ report_filename (string): The name of the extracted report
348
+
349
+ Returns:
350
+ string: Report absolute filepath in local filesystem
351
+ """
352
+ output_dirpath = self._create_host_output_directory()
353
+ report_absolute_filepath = '{}/{}'.format(
354
+ output_dirpath, report_filename
355
+ )
356
+
357
+ with tempfile.TemporaryFile() as tmp_wrapper_file:
358
+ for chunk in tarball_chunks:
359
+ tmp_wrapper_file.write(chunk)
360
+ tmp_wrapper_file.seek(0)
361
+
362
+ with tarfile.open(mode="r|", fileobj=tmp_wrapper_file) as talball_file:
363
+ talball_file.extractall(path=output_dirpath)
364
+
365
+ self.logger.debug("report from {} saved to {}".format(
366
+ self.__container_name,
367
+ report_absolute_filepath
368
+ ))
369
+ return report_absolute_filepath
370
+
371
+ def get_container_reports(self):
372
+ """ Get all reports found in the instantiated container and save on the host machine.
373
+
374
+ Returns:
375
+ string: Report absolute filepath in local filesystem
376
+ """
377
+ self.logger.debug(
378
+ "Collecting reports from {0}".format(self.__container_name)
379
+ )
380
+ try:
381
+ possible_report_extensions = ['sarif', 'json']
382
+ for ext in possible_report_extensions:
383
+ report_filename = '{}.{}'.format(self.repository_name, ext)
384
+
385
+ container_filepath = '/{}'.format(report_filename)
386
+ tarball_chunks = self._get_container_tarball_chunks(
387
+ container_filepath
388
+ )
389
+ if not tarball_chunks:
390
+ continue
391
+
392
+ absolute_report_path = self._extract_tarball_chunks(
393
+ tarball_chunks,
394
+ report_filename
395
+ )
396
+ return absolute_report_path
397
+
398
+ except Exception as e:
399
+ self.logger.error(e)
@@ -0,0 +1,8 @@
1
+ class CommonException(RuntimeError):
2
+ pass
3
+
4
+ class ExecutionFailureException(CommonException):
5
+
6
+ def __init__(self, *args, **kwargs):
7
+ super().__init__(*args, **kwargs)
8
+ self.exit_code = kwargs['exit_code']
@@ -0,0 +1,76 @@
1
+
2
+ from git import Repo
3
+ from git.exc import GitCommandError, InvalidGitRepositoryError
4
+ from giturlparse import parse as gitparse
5
+ from hashlib import sha256
6
+ import os
7
+
8
+ from convisoappsec.flow.version_control_system_adapter import GitAdapter
9
+ from convisoappsec.logger import LOGGER
10
+
11
+
12
+ class DescriptionParsingError(GitCommandError):
13
+ pass
14
+
15
+
16
+ class GitDataParser:
17
+ def __init__(self, repository_dir):
18
+ try:
19
+ self.__validate_git_repo(repository_dir)
20
+
21
+ self.repository_dir = repository_dir
22
+ self._git_adapter = GitAdapter(repository_dir)
23
+
24
+ except InvalidGitRepositoryError as exp:
25
+ LOGGER.error(
26
+ 'Invalid Git repository: "{}".'.format(repository_dir)
27
+ )
28
+ raise exp
29
+
30
+ except Exception as exp:
31
+ LOGGER.error("An unxpected error occurred.")
32
+ raise exp
33
+
34
+ def parse_name(self):
35
+ try:
36
+ git_remote_url = self._git_adapter._git_client.ls_remote(
37
+ '--get-url'
38
+ )
39
+
40
+ return self.__parse_name_from_git_url(git_remote_url)
41
+ except GitCommandError:
42
+ dirname = os.path.dirname(self.repository_dir)
43
+ basename = os.path.basename(self.repository_dir)
44
+ digest = sha256(dirname.encode('utf-8')).hexdigest()
45
+
46
+ return basename + '-' + digest
47
+ except Exception as exception:
48
+ raise exception
49
+
50
+ def __parse_description(self):
51
+ try:
52
+ readme = self._git_adapter._git_client.show(':README.md')
53
+ readme = readme.replace('\n', '\n<br>')
54
+
55
+ return readme
56
+
57
+ except DescriptionParsingError as exception:
58
+ raise exception
59
+
60
+ except Exception as exception:
61
+ raise exception
62
+
63
+ def __parse_name_from_git_url(self, git_url):
64
+ parser = gitparse(git_url)
65
+
66
+ return '{} ({}/{})'.format(
67
+ parser.name,
68
+ parser.resource,
69
+ parser.owner,
70
+ )
71
+
72
+ def __validate_git_repo(self, repository_dir):
73
+ repo = Repo(repository_dir)
74
+ repo.git.config("--global", "--add", "safe.directory", repository_dir)
75
+
76
+ return repo
File without changes
@@ -0,0 +1,75 @@
1
+ import requests
2
+ import jmespath
3
+
4
+ from convisoappsec.common.graphql.errors import AuthenticationError, Error, ResponseError, ServerError
5
+
6
+
7
+ class RequestErrorHandler:
8
+ def __init__(self, error):
9
+ self.error = error
10
+
11
+ def handle_request_error(self):
12
+ try:
13
+ raise self.error
14
+ except requests.exceptions.HTTPError as e:
15
+ self._handle_http_error(e)
16
+ except Exception as e:
17
+ self._handle_unexpected_error(e)
18
+
19
+ @staticmethod
20
+ def _handle_unexpected_error(error):
21
+ raise Error(str(error)) from error
22
+
23
+ @staticmethod
24
+ def _handle_http_error(error):
25
+ if error.response.status_code == 401:
26
+ error_msg_fmt = 'Unauthorized access, check your credentials. {}'
27
+ response = error.response.json()
28
+ error_messages = response.get('errors', [])
29
+ error_msg = error_msg_fmt.format(error_messages)
30
+
31
+ raise AuthenticationError(error_msg) from error
32
+
33
+ if error.response.status_code == 500:
34
+ error_msg_fmt = 'Internal Server Error. {}'
35
+ error_msg = error_msg_fmt.format(error.response.text)
36
+
37
+ raise ServerError(error_msg) from error
38
+
39
+ raise Error(error.response.text) from error
40
+
41
+
42
+ class GraphQlErrorHandler:
43
+ def __init__(self, response):
44
+ self.response = response
45
+
46
+ def raise_on_graphql_body_error(self):
47
+ data = self.response.get('data', [])
48
+
49
+ for key in data:
50
+ if data[key] is None:
51
+ continue
52
+
53
+ errors = data[key].get('errors', [])
54
+ has_errors = len(errors) > 0
55
+ if has_errors:
56
+ raise ResponseError(errors)
57
+
58
+ def raise_on_graphql_error(self):
59
+ errors = self.response.get('errors', [])
60
+
61
+ if not errors:
62
+ return
63
+
64
+ error = errors[0]
65
+
66
+ error_path = 'extensions.code'
67
+
68
+ code = jmespath.search(
69
+ error_path,
70
+ error
71
+ )
72
+
73
+ message = error.get('message', '')
74
+
75
+ raise ResponseError(message, code)
@@ -0,0 +1,16 @@
1
+ class Error(Exception):
2
+ pass
3
+
4
+
5
+ class AuthenticationError(Error):
6
+ pass
7
+
8
+
9
+ class ServerError(Error):
10
+ pass
11
+
12
+
13
+ class ResponseError(Error):
14
+ def __init__(self, message, code=None):
15
+ super().__init__(message)
16
+ self.code = code
@@ -0,0 +1,51 @@
1
+ import requests
2
+
3
+ from convisoappsec.common.graphql.error_handlers import GraphQlErrorHandler, RequestErrorHandler
4
+ from convisoappsec.version import __version__
5
+
6
+
7
+ class GraphQLClient:
8
+ DEFAULT_HEADERS = {
9
+ 'Accept': 'application/json',
10
+ 'Content-Type': 'application/json',
11
+ "User-Agent": "AST:{version}".format(version=__version__),
12
+ }
13
+
14
+ def __init__(self, url, headers={}):
15
+ self.url = url
16
+ self.__session = requests.Session()
17
+ self.__session.headers.update(
18
+ **self.DEFAULT_HEADERS,
19
+ **headers
20
+ )
21
+
22
+ def execute(self, query, variables={}):
23
+ try:
24
+ payload = self._build_graphql_payload(query, variables)
25
+
26
+ response = self.__session.post(
27
+ url=self.url,
28
+ json=payload,
29
+ )
30
+
31
+ response.raise_for_status()
32
+
33
+ except Exception as e:
34
+ handler = RequestErrorHandler(e)
35
+ handler.handle_request_error()
36
+
37
+ json_response = response.json()
38
+ graphql_handler = GraphQlErrorHandler(json_response)
39
+ graphql_handler.raise_on_graphql_error()
40
+ graphql_handler.raise_on_graphql_body_error()
41
+
42
+ return json_response['data']
43
+
44
+ @staticmethod
45
+ def _build_graphql_payload(query, variables):
46
+ data = {
47
+ 'query': query,
48
+ 'variables': variables
49
+ }
50
+
51
+ return data
@@ -0,0 +1,40 @@
1
+ import time
2
+ import traceback
3
+ from convisoappsec.logger import LOGGER, log_and_notify_ast_event
4
+
5
+
6
+ class RetryHandler:
7
+ def __init__(self, flow_context=None, company_id=None, asset_id=None):
8
+ self.max_retries = 5
9
+ self.initial_delay = 1
10
+ self.backoff_factor = 2
11
+ self.flow_context = flow_context
12
+ self.company_id = company_id
13
+ self.asset_id = asset_id
14
+
15
+ def execute_with_retry(self, func, *args, **kwargs):
16
+ """Execute a function with retry logic and exponential backoff."""
17
+ retries = 0
18
+ delay = self.initial_delay
19
+
20
+ while retries < self.max_retries:
21
+ try:
22
+ return func(*args, **kwargs)
23
+ except Exception:
24
+ retries += 1
25
+ time.sleep(delay)
26
+ delay *= self.backoff_factor
27
+
28
+ if retries == self.max_retries:
29
+ full_trace = traceback.format_exc()
30
+ LOGGER.warning(
31
+ "⚠️ Maximum retries reached. Our technical team has been notified."
32
+ )
33
+
34
+ try:
35
+ log_and_notify_ast_event(
36
+ flow_context=self.flow_context, company_id=self.company_id, asset_id=self.asset_id,
37
+ ast_log=full_trace
38
+ )
39
+ except Exception as log_error:
40
+ LOGGER.warning(f"⚠️ Failed to log and notify AST event: {log_error}")
@@ -0,0 +1,8 @@
1
+
2
+ def parse_to_ascii(str_value):
3
+ str_value_bytes = str_value.encode()
4
+ str_value_as_ascii = str_value_bytes.decode(
5
+ 'ascii', 'ignore'
6
+ )
7
+
8
+ return str_value_as_ascii
@@ -0,0 +1,3 @@
1
+ from .version_control_system_adapter import GitAdapter
2
+
3
+ __all__ = ['GitAdapter']