regscale-cli 6.16.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.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (481) hide show
  1. regscale/__init__.py +1 -0
  2. regscale/airflow/__init__.py +9 -0
  3. regscale/airflow/azure/__init__.py +9 -0
  4. regscale/airflow/azure/cli.py +89 -0
  5. regscale/airflow/azure/upload_dags.py +116 -0
  6. regscale/airflow/click_dags.py +127 -0
  7. regscale/airflow/click_mixins.py +82 -0
  8. regscale/airflow/config.py +25 -0
  9. regscale/airflow/factories/__init__.py +0 -0
  10. regscale/airflow/factories/connections.py +58 -0
  11. regscale/airflow/factories/workflows.py +78 -0
  12. regscale/airflow/hierarchy.py +88 -0
  13. regscale/airflow/operators/__init__.py +0 -0
  14. regscale/airflow/operators/click.py +36 -0
  15. regscale/airflow/sensors/__init__.py +0 -0
  16. regscale/airflow/sensors/sql.py +107 -0
  17. regscale/airflow/sessions/__init__.py +0 -0
  18. regscale/airflow/sessions/sql/__init__.py +3 -0
  19. regscale/airflow/sessions/sql/queries.py +64 -0
  20. regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
  21. regscale/airflow/tasks/__init__.py +0 -0
  22. regscale/airflow/tasks/branches.py +22 -0
  23. regscale/airflow/tasks/cli.py +116 -0
  24. regscale/airflow/tasks/click.py +73 -0
  25. regscale/airflow/tasks/debugging.py +9 -0
  26. regscale/airflow/tasks/groups.py +116 -0
  27. regscale/airflow/tasks/init.py +60 -0
  28. regscale/airflow/tasks/states.py +47 -0
  29. regscale/airflow/tasks/workflows.py +36 -0
  30. regscale/ansible/__init__.py +9 -0
  31. regscale/core/__init__.py +0 -0
  32. regscale/core/app/__init__.py +3 -0
  33. regscale/core/app/api.py +571 -0
  34. regscale/core/app/application.py +665 -0
  35. regscale/core/app/internal/__init__.py +136 -0
  36. regscale/core/app/internal/admin_actions.py +230 -0
  37. regscale/core/app/internal/assessments_editor.py +873 -0
  38. regscale/core/app/internal/catalog.py +316 -0
  39. regscale/core/app/internal/comparison.py +459 -0
  40. regscale/core/app/internal/control_editor.py +571 -0
  41. regscale/core/app/internal/encrypt.py +79 -0
  42. regscale/core/app/internal/evidence.py +1240 -0
  43. regscale/core/app/internal/file_uploads.py +151 -0
  44. regscale/core/app/internal/healthcheck.py +66 -0
  45. regscale/core/app/internal/login.py +305 -0
  46. regscale/core/app/internal/migrations.py +240 -0
  47. regscale/core/app/internal/model_editor.py +1701 -0
  48. regscale/core/app/internal/poam_editor.py +632 -0
  49. regscale/core/app/internal/workflow.py +105 -0
  50. regscale/core/app/logz.py +74 -0
  51. regscale/core/app/utils/XMLIR.py +258 -0
  52. regscale/core/app/utils/__init__.py +0 -0
  53. regscale/core/app/utils/api_handler.py +358 -0
  54. regscale/core/app/utils/app_utils.py +1110 -0
  55. regscale/core/app/utils/catalog_utils/__init__.py +0 -0
  56. regscale/core/app/utils/catalog_utils/common.py +91 -0
  57. regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
  58. regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
  59. regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
  60. regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
  61. regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
  62. regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
  63. regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
  64. regscale/core/app/utils/file_utils.py +238 -0
  65. regscale/core/app/utils/parser_utils.py +81 -0
  66. regscale/core/app/utils/pickle_file_handler.py +57 -0
  67. regscale/core/app/utils/regscale_utils.py +319 -0
  68. regscale/core/app/utils/report_utils.py +119 -0
  69. regscale/core/app/utils/variables.py +226 -0
  70. regscale/core/decorators.py +31 -0
  71. regscale/core/lazy_group.py +65 -0
  72. regscale/core/login.py +63 -0
  73. regscale/core/server/__init__.py +0 -0
  74. regscale/core/server/flask_api.py +473 -0
  75. regscale/core/server/helpers.py +373 -0
  76. regscale/core/server/rest.py +64 -0
  77. regscale/core/server/static/css/bootstrap.css +6030 -0
  78. regscale/core/server/static/css/bootstrap.min.css +6 -0
  79. regscale/core/server/static/css/main.css +176 -0
  80. regscale/core/server/static/images/regscale-cli.svg +49 -0
  81. regscale/core/server/static/images/regscale.svg +38 -0
  82. regscale/core/server/templates/base.html +74 -0
  83. regscale/core/server/templates/index.html +43 -0
  84. regscale/core/server/templates/login.html +28 -0
  85. regscale/core/server/templates/make_base64.html +22 -0
  86. regscale/core/server/templates/upload_STIG.html +109 -0
  87. regscale/core/server/templates/upload_STIG_result.html +26 -0
  88. regscale/core/server/templates/upload_ssp.html +144 -0
  89. regscale/core/server/templates/upload_ssp_result.html +128 -0
  90. regscale/core/static/__init__.py +0 -0
  91. regscale/core/static/regex.py +14 -0
  92. regscale/core/utils/__init__.py +117 -0
  93. regscale/core/utils/click_utils.py +13 -0
  94. regscale/core/utils/date.py +238 -0
  95. regscale/core/utils/graphql.py +254 -0
  96. regscale/core/utils/urls.py +23 -0
  97. regscale/dev/__init__.py +6 -0
  98. regscale/dev/analysis.py +454 -0
  99. regscale/dev/cli.py +235 -0
  100. regscale/dev/code_gen.py +492 -0
  101. regscale/dev/dirs.py +69 -0
  102. regscale/dev/docs.py +384 -0
  103. regscale/dev/monitoring.py +26 -0
  104. regscale/dev/profiling.py +216 -0
  105. regscale/exceptions/__init__.py +4 -0
  106. regscale/exceptions/license_exception.py +7 -0
  107. regscale/exceptions/validation_exception.py +9 -0
  108. regscale/integrations/__init__.py +1 -0
  109. regscale/integrations/commercial/__init__.py +486 -0
  110. regscale/integrations/commercial/ad.py +433 -0
  111. regscale/integrations/commercial/amazon/__init__.py +0 -0
  112. regscale/integrations/commercial/amazon/common.py +106 -0
  113. regscale/integrations/commercial/aqua/__init__.py +0 -0
  114. regscale/integrations/commercial/aqua/aqua.py +91 -0
  115. regscale/integrations/commercial/aws/__init__.py +6 -0
  116. regscale/integrations/commercial/aws/cli.py +322 -0
  117. regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
  118. regscale/integrations/commercial/aws/inventory/base.py +64 -0
  119. regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
  120. regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
  121. regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
  122. regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
  123. regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
  124. regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
  125. regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
  126. regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
  127. regscale/integrations/commercial/aws/scanner.py +823 -0
  128. regscale/integrations/commercial/azure/__init__.py +0 -0
  129. regscale/integrations/commercial/azure/common.py +32 -0
  130. regscale/integrations/commercial/azure/intune.py +488 -0
  131. regscale/integrations/commercial/azure/scanner.py +49 -0
  132. regscale/integrations/commercial/burp.py +78 -0
  133. regscale/integrations/commercial/cpe.py +144 -0
  134. regscale/integrations/commercial/crowdstrike.py +1117 -0
  135. regscale/integrations/commercial/defender.py +1511 -0
  136. regscale/integrations/commercial/dependabot.py +210 -0
  137. regscale/integrations/commercial/durosuite/__init__.py +0 -0
  138. regscale/integrations/commercial/durosuite/api.py +1546 -0
  139. regscale/integrations/commercial/durosuite/process_devices.py +101 -0
  140. regscale/integrations/commercial/durosuite/scanner.py +637 -0
  141. regscale/integrations/commercial/durosuite/variables.py +21 -0
  142. regscale/integrations/commercial/ecr.py +90 -0
  143. regscale/integrations/commercial/gcp/__init__.py +237 -0
  144. regscale/integrations/commercial/gcp/auth.py +96 -0
  145. regscale/integrations/commercial/gcp/control_tests.py +238 -0
  146. regscale/integrations/commercial/gcp/variables.py +18 -0
  147. regscale/integrations/commercial/gitlab.py +332 -0
  148. regscale/integrations/commercial/grype.py +165 -0
  149. regscale/integrations/commercial/ibm.py +90 -0
  150. regscale/integrations/commercial/import_all/__init__.py +0 -0
  151. regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
  152. regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
  153. regscale/integrations/commercial/jira.py +1046 -0
  154. regscale/integrations/commercial/mappings/__init__.py +0 -0
  155. regscale/integrations/commercial/mappings/csf_controls.json +713 -0
  156. regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
  157. regscale/integrations/commercial/nessus/__init__.py +0 -0
  158. regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
  159. regscale/integrations/commercial/nessus/scanner.py +416 -0
  160. regscale/integrations/commercial/nexpose.py +90 -0
  161. regscale/integrations/commercial/okta.py +798 -0
  162. regscale/integrations/commercial/opentext/__init__.py +0 -0
  163. regscale/integrations/commercial/opentext/click.py +99 -0
  164. regscale/integrations/commercial/opentext/scanner.py +143 -0
  165. regscale/integrations/commercial/prisma.py +91 -0
  166. regscale/integrations/commercial/qualys.py +1462 -0
  167. regscale/integrations/commercial/salesforce.py +980 -0
  168. regscale/integrations/commercial/sap/__init__.py +0 -0
  169. regscale/integrations/commercial/sap/click.py +31 -0
  170. regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
  171. regscale/integrations/commercial/sap/sysdig/click.py +57 -0
  172. regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
  173. regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
  174. regscale/integrations/commercial/sap/tenable/click.py +49 -0
  175. regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
  176. regscale/integrations/commercial/servicenow.py +1756 -0
  177. regscale/integrations/commercial/sicura/__init__.py +0 -0
  178. regscale/integrations/commercial/sicura/api.py +855 -0
  179. regscale/integrations/commercial/sicura/commands.py +73 -0
  180. regscale/integrations/commercial/sicura/scanner.py +481 -0
  181. regscale/integrations/commercial/sicura/variables.py +16 -0
  182. regscale/integrations/commercial/snyk.py +90 -0
  183. regscale/integrations/commercial/sonarcloud.py +260 -0
  184. regscale/integrations/commercial/sqlserver.py +369 -0
  185. regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
  186. regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
  187. regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
  188. regscale/integrations/commercial/stigv2/__init__.py +0 -0
  189. regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
  190. regscale/integrations/commercial/stigv2/click_commands.py +95 -0
  191. regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
  192. regscale/integrations/commercial/synqly/__init__.py +0 -0
  193. regscale/integrations/commercial/synqly/assets.py +46 -0
  194. regscale/integrations/commercial/synqly/ticketing.py +132 -0
  195. regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
  196. regscale/integrations/commercial/synqly_jira.py +840 -0
  197. regscale/integrations/commercial/tenablev2/__init__.py +0 -0
  198. regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
  199. regscale/integrations/commercial/tenablev2/click.py +1584 -0
  200. regscale/integrations/commercial/tenablev2/scanner.py +504 -0
  201. regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
  202. regscale/integrations/commercial/tenablev2/utils.py +78 -0
  203. regscale/integrations/commercial/tenablev2/variables.py +17 -0
  204. regscale/integrations/commercial/trivy.py +162 -0
  205. regscale/integrations/commercial/veracode.py +96 -0
  206. regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
  207. regscale/integrations/commercial/wizv2/__init__.py +0 -0
  208. regscale/integrations/commercial/wizv2/click.py +429 -0
  209. regscale/integrations/commercial/wizv2/constants.py +1001 -0
  210. regscale/integrations/commercial/wizv2/issue.py +361 -0
  211. regscale/integrations/commercial/wizv2/models.py +112 -0
  212. regscale/integrations/commercial/wizv2/parsers.py +339 -0
  213. regscale/integrations/commercial/wizv2/sbom.py +115 -0
  214. regscale/integrations/commercial/wizv2/scanner.py +416 -0
  215. regscale/integrations/commercial/wizv2/utils.py +796 -0
  216. regscale/integrations/commercial/wizv2/variables.py +39 -0
  217. regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
  218. regscale/integrations/commercial/xray.py +91 -0
  219. regscale/integrations/integration/__init__.py +2 -0
  220. regscale/integrations/integration/integration.py +26 -0
  221. regscale/integrations/integration/inventory.py +17 -0
  222. regscale/integrations/integration/issue.py +100 -0
  223. regscale/integrations/integration_override.py +149 -0
  224. regscale/integrations/public/__init__.py +103 -0
  225. regscale/integrations/public/cisa.py +641 -0
  226. regscale/integrations/public/criticality_updater.py +70 -0
  227. regscale/integrations/public/emass.py +411 -0
  228. regscale/integrations/public/emass_slcm_import.py +697 -0
  229. regscale/integrations/public/fedramp/__init__.py +0 -0
  230. regscale/integrations/public/fedramp/appendix_parser.py +548 -0
  231. regscale/integrations/public/fedramp/click.py +479 -0
  232. regscale/integrations/public/fedramp/components.py +714 -0
  233. regscale/integrations/public/fedramp/docx_parser.py +259 -0
  234. regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
  235. regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
  236. regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
  237. regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
  238. regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
  239. regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
  240. regscale/integrations/public/fedramp/import_workbook.py +495 -0
  241. regscale/integrations/public/fedramp/inventory_items.py +244 -0
  242. regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
  243. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
  244. regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
  245. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
  246. regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
  247. regscale/integrations/public/fedramp/mappings/user.py +175 -0
  248. regscale/integrations/public/fedramp/mappings/values.py +141 -0
  249. regscale/integrations/public/fedramp/markdown_parser.py +150 -0
  250. regscale/integrations/public/fedramp/metadata.py +689 -0
  251. regscale/integrations/public/fedramp/models/__init__.py +59 -0
  252. regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
  253. regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
  254. regscale/integrations/public/fedramp/parts_mapper.py +107 -0
  255. regscale/integrations/public/fedramp/poam/__init__.py +0 -0
  256. regscale/integrations/public/fedramp/poam/scanner.py +851 -0
  257. regscale/integrations/public/fedramp/properties.py +201 -0
  258. regscale/integrations/public/fedramp/reporting.py +84 -0
  259. regscale/integrations/public/fedramp/resources.py +496 -0
  260. regscale/integrations/public/fedramp/rosetta.py +110 -0
  261. regscale/integrations/public/fedramp/ssp_logger.py +87 -0
  262. regscale/integrations/public/fedramp/system_characteristics.py +922 -0
  263. regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
  264. regscale/integrations/public/fedramp/system_implementation.py +190 -0
  265. regscale/integrations/public/fedramp/xml_utils.py +87 -0
  266. regscale/integrations/public/nist_catalog.py +275 -0
  267. regscale/integrations/public/oscal.py +1946 -0
  268. regscale/integrations/public/otx.py +169 -0
  269. regscale/integrations/scanner_integration.py +2692 -0
  270. regscale/integrations/variables.py +25 -0
  271. regscale/models/__init__.py +7 -0
  272. regscale/models/app_models/__init__.py +5 -0
  273. regscale/models/app_models/catalog_compare.py +213 -0
  274. regscale/models/app_models/click.py +252 -0
  275. regscale/models/app_models/datetime_encoder.py +21 -0
  276. regscale/models/app_models/import_validater.py +321 -0
  277. regscale/models/app_models/mapping.py +260 -0
  278. regscale/models/app_models/pipeline.py +37 -0
  279. regscale/models/click_models.py +413 -0
  280. regscale/models/config.py +154 -0
  281. regscale/models/email_style.css +67 -0
  282. regscale/models/hierarchy.py +8 -0
  283. regscale/models/inspect_models.py +79 -0
  284. regscale/models/integration_models/__init__.py +0 -0
  285. regscale/models/integration_models/amazon_models/__init__.py +0 -0
  286. regscale/models/integration_models/amazon_models/inspector.py +262 -0
  287. regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
  288. regscale/models/integration_models/aqua.py +247 -0
  289. regscale/models/integration_models/azure_alerts.py +255 -0
  290. regscale/models/integration_models/base64.py +23 -0
  291. regscale/models/integration_models/burp.py +433 -0
  292. regscale/models/integration_models/burp_models.py +128 -0
  293. regscale/models/integration_models/cisa_kev_data.json +19333 -0
  294. regscale/models/integration_models/defender_data.py +93 -0
  295. regscale/models/integration_models/defenderimport.py +143 -0
  296. regscale/models/integration_models/drf.py +443 -0
  297. regscale/models/integration_models/ecr_models/__init__.py +0 -0
  298. regscale/models/integration_models/ecr_models/data.py +69 -0
  299. regscale/models/integration_models/ecr_models/ecr.py +239 -0
  300. regscale/models/integration_models/flat_file_importer.py +1079 -0
  301. regscale/models/integration_models/grype_import.py +247 -0
  302. regscale/models/integration_models/ibm.py +126 -0
  303. regscale/models/integration_models/implementation_results.py +85 -0
  304. regscale/models/integration_models/nexpose.py +140 -0
  305. regscale/models/integration_models/prisma.py +202 -0
  306. regscale/models/integration_models/qualys.py +720 -0
  307. regscale/models/integration_models/qualys_scanner.py +160 -0
  308. regscale/models/integration_models/sbom/__init__.py +0 -0
  309. regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
  310. regscale/models/integration_models/send_reminders.py +620 -0
  311. regscale/models/integration_models/snyk.py +155 -0
  312. regscale/models/integration_models/synqly_models/__init__.py +0 -0
  313. regscale/models/integration_models/synqly_models/capabilities.json +1 -0
  314. regscale/models/integration_models/synqly_models/connector_types.py +22 -0
  315. regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
  316. regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
  317. regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
  318. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
  319. regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
  320. regscale/models/integration_models/synqly_models/param.py +72 -0
  321. regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
  322. regscale/models/integration_models/synqly_models/tenants.py +39 -0
  323. regscale/models/integration_models/tenable_models/__init__.py +0 -0
  324. regscale/models/integration_models/tenable_models/integration.py +187 -0
  325. regscale/models/integration_models/tenable_models/models.py +513 -0
  326. regscale/models/integration_models/trivy_import.py +231 -0
  327. regscale/models/integration_models/veracode.py +217 -0
  328. regscale/models/integration_models/xray.py +135 -0
  329. regscale/models/locking.py +100 -0
  330. regscale/models/platform.py +110 -0
  331. regscale/models/regscale_models/__init__.py +67 -0
  332. regscale/models/regscale_models/assessment.py +570 -0
  333. regscale/models/regscale_models/assessment_plan.py +52 -0
  334. regscale/models/regscale_models/asset.py +567 -0
  335. regscale/models/regscale_models/asset_mapping.py +190 -0
  336. regscale/models/regscale_models/case.py +42 -0
  337. regscale/models/regscale_models/catalog.py +261 -0
  338. regscale/models/regscale_models/cci.py +46 -0
  339. regscale/models/regscale_models/change.py +167 -0
  340. regscale/models/regscale_models/checklist.py +372 -0
  341. regscale/models/regscale_models/comment.py +49 -0
  342. regscale/models/regscale_models/compliance_settings.py +112 -0
  343. regscale/models/regscale_models/component.py +412 -0
  344. regscale/models/regscale_models/component_mapping.py +65 -0
  345. regscale/models/regscale_models/control.py +38 -0
  346. regscale/models/regscale_models/control_implementation.py +1128 -0
  347. regscale/models/regscale_models/control_objective.py +261 -0
  348. regscale/models/regscale_models/control_parameter.py +100 -0
  349. regscale/models/regscale_models/control_test.py +34 -0
  350. regscale/models/regscale_models/control_test_plan.py +75 -0
  351. regscale/models/regscale_models/control_test_result.py +52 -0
  352. regscale/models/regscale_models/custom_field.py +245 -0
  353. regscale/models/regscale_models/data.py +109 -0
  354. regscale/models/regscale_models/data_center.py +40 -0
  355. regscale/models/regscale_models/deviation.py +203 -0
  356. regscale/models/regscale_models/email.py +97 -0
  357. regscale/models/regscale_models/evidence.py +47 -0
  358. regscale/models/regscale_models/evidence_mapping.py +40 -0
  359. regscale/models/regscale_models/facility.py +59 -0
  360. regscale/models/regscale_models/file.py +382 -0
  361. regscale/models/regscale_models/filetag.py +37 -0
  362. regscale/models/regscale_models/form_field_value.py +94 -0
  363. regscale/models/regscale_models/group.py +169 -0
  364. regscale/models/regscale_models/implementation_objective.py +335 -0
  365. regscale/models/regscale_models/implementation_option.py +275 -0
  366. regscale/models/regscale_models/implementation_role.py +33 -0
  367. regscale/models/regscale_models/incident.py +177 -0
  368. regscale/models/regscale_models/interconnection.py +43 -0
  369. regscale/models/regscale_models/issue.py +1176 -0
  370. regscale/models/regscale_models/leveraged_authorization.py +125 -0
  371. regscale/models/regscale_models/line_of_inquiry.py +52 -0
  372. regscale/models/regscale_models/link.py +205 -0
  373. regscale/models/regscale_models/meta_data.py +64 -0
  374. regscale/models/regscale_models/mixins/__init__.py +0 -0
  375. regscale/models/regscale_models/mixins/parent_cache.py +124 -0
  376. regscale/models/regscale_models/module.py +224 -0
  377. regscale/models/regscale_models/modules.py +191 -0
  378. regscale/models/regscale_models/objective.py +14 -0
  379. regscale/models/regscale_models/parameter.py +87 -0
  380. regscale/models/regscale_models/ports_protocol.py +81 -0
  381. regscale/models/regscale_models/privacy.py +89 -0
  382. regscale/models/regscale_models/profile.py +50 -0
  383. regscale/models/regscale_models/profile_link.py +68 -0
  384. regscale/models/regscale_models/profile_mapping.py +124 -0
  385. regscale/models/regscale_models/project.py +63 -0
  386. regscale/models/regscale_models/property.py +278 -0
  387. regscale/models/regscale_models/question.py +85 -0
  388. regscale/models/regscale_models/questionnaire.py +87 -0
  389. regscale/models/regscale_models/questionnaire_instance.py +177 -0
  390. regscale/models/regscale_models/rbac.py +132 -0
  391. regscale/models/regscale_models/reference.py +86 -0
  392. regscale/models/regscale_models/regscale_model.py +1643 -0
  393. regscale/models/regscale_models/requirement.py +29 -0
  394. regscale/models/regscale_models/risk.py +274 -0
  395. regscale/models/regscale_models/sbom.py +54 -0
  396. regscale/models/regscale_models/scan_history.py +436 -0
  397. regscale/models/regscale_models/search.py +53 -0
  398. regscale/models/regscale_models/security_control.py +132 -0
  399. regscale/models/regscale_models/security_plan.py +204 -0
  400. regscale/models/regscale_models/software_inventory.py +159 -0
  401. regscale/models/regscale_models/stake_holder.py +64 -0
  402. regscale/models/regscale_models/stig.py +647 -0
  403. regscale/models/regscale_models/supply_chain.py +152 -0
  404. regscale/models/regscale_models/system_role.py +188 -0
  405. regscale/models/regscale_models/system_role_external_assignment.py +40 -0
  406. regscale/models/regscale_models/tag.py +37 -0
  407. regscale/models/regscale_models/tag_mapping.py +19 -0
  408. regscale/models/regscale_models/task.py +133 -0
  409. regscale/models/regscale_models/threat.py +196 -0
  410. regscale/models/regscale_models/user.py +175 -0
  411. regscale/models/regscale_models/user_group.py +55 -0
  412. regscale/models/regscale_models/vulnerability.py +242 -0
  413. regscale/models/regscale_models/vulnerability_mapping.py +162 -0
  414. regscale/models/regscale_models/workflow.py +55 -0
  415. regscale/models/regscale_models/workflow_action.py +34 -0
  416. regscale/models/regscale_models/workflow_instance.py +269 -0
  417. regscale/models/regscale_models/workflow_instance_step.py +114 -0
  418. regscale/models/regscale_models/workflow_template.py +58 -0
  419. regscale/models/regscale_models/workflow_template_step.py +45 -0
  420. regscale/regscale.py +815 -0
  421. regscale/utils/__init__.py +7 -0
  422. regscale/utils/b64conversion.py +14 -0
  423. regscale/utils/click_utils.py +118 -0
  424. regscale/utils/decorators.py +48 -0
  425. regscale/utils/dict_utils.py +59 -0
  426. regscale/utils/files.py +79 -0
  427. regscale/utils/fxns.py +30 -0
  428. regscale/utils/graphql_client.py +113 -0
  429. regscale/utils/lists.py +16 -0
  430. regscale/utils/numbers.py +12 -0
  431. regscale/utils/shell.py +148 -0
  432. regscale/utils/string.py +121 -0
  433. regscale/utils/synqly_utils.py +165 -0
  434. regscale/utils/threading/__init__.py +8 -0
  435. regscale/utils/threading/threadhandler.py +131 -0
  436. regscale/utils/threading/threadsafe_counter.py +47 -0
  437. regscale/utils/threading/threadsafe_dict.py +242 -0
  438. regscale/utils/threading/threadsafe_list.py +83 -0
  439. regscale/utils/version.py +104 -0
  440. regscale/validation/__init__.py +0 -0
  441. regscale/validation/address.py +37 -0
  442. regscale/validation/record.py +48 -0
  443. regscale/visualization/__init__.py +5 -0
  444. regscale/visualization/click.py +34 -0
  445. regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
  446. regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
  447. regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
  448. regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
  449. regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
  450. regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
  451. tests/fixtures/__init__.py +2 -0
  452. tests/fixtures/api.py +87 -0
  453. tests/fixtures/models.py +91 -0
  454. tests/fixtures/test_fixture.py +144 -0
  455. tests/mocks/__init__.py +0 -0
  456. tests/mocks/objects.py +3 -0
  457. tests/mocks/response.py +32 -0
  458. tests/mocks/xml.py +13 -0
  459. tests/regscale/__init__.py +0 -0
  460. tests/regscale/core/__init__.py +0 -0
  461. tests/regscale/core/test_api.py +232 -0
  462. tests/regscale/core/test_app.py +406 -0
  463. tests/regscale/core/test_login.py +37 -0
  464. tests/regscale/core/test_logz.py +66 -0
  465. tests/regscale/core/test_sbom_generator.py +87 -0
  466. tests/regscale/core/test_validation_utils.py +163 -0
  467. tests/regscale/core/test_version.py +78 -0
  468. tests/regscale/models/__init__.py +0 -0
  469. tests/regscale/models/test_asset.py +71 -0
  470. tests/regscale/models/test_config.py +26 -0
  471. tests/regscale/models/test_control_implementation.py +27 -0
  472. tests/regscale/models/test_import.py +97 -0
  473. tests/regscale/models/test_issue.py +36 -0
  474. tests/regscale/models/test_mapping.py +52 -0
  475. tests/regscale/models/test_platform.py +31 -0
  476. tests/regscale/models/test_regscale_model.py +346 -0
  477. tests/regscale/models/test_report.py +32 -0
  478. tests/regscale/models/test_tenable_integrations.py +118 -0
  479. tests/regscale/models/test_user_model.py +121 -0
  480. tests/regscale/test_about.py +19 -0
  481. tests/regscale/test_authorization.py +65 -0
@@ -0,0 +1,583 @@
1
+ """Ticketing Connector Model"""
2
+
3
+ import base64
4
+ import os
5
+ import tempfile
6
+ from typing import Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from synqly.engine.resources.ticketing.types.ticket import Ticket
10
+
11
+ import rich.progress
12
+ from pathlib import Path
13
+ from pydantic import ConfigDict
14
+
15
+ from regscale.core.app.utils.app_utils import (
16
+ check_file_path,
17
+ compute_hashes_in_directory,
18
+ get_current_datetime,
19
+ )
20
+ from regscale.models.integration_models.synqly_models.synqly_model import SynqlyModel
21
+ from regscale.models.regscale_models import File, Issue
22
+
23
+
24
+ class Ticketing(SynqlyModel):
25
+ """Ticketing Connector Model"""
26
+
27
+ model_config = ConfigDict(populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True)
28
+
29
+ integration_id: str = ""
30
+ integration_id_field: str = ""
31
+ has_integration_field: bool = False
32
+ manage_attachments: bool = False
33
+
34
+ def __init__(self, integration: str, **kwargs: dict):
35
+ super().__init__(connector_type=self.__class__.__name__, integration=integration, **kwargs)
36
+ self.integration_id = f"{self._connector_type.lower()}_{self.integration.lower()}"
37
+ self.integration_id_field = f"{self.integration[0:1].lower()}{self.integration[1:]}Id"
38
+ self.has_integration_field = hasattr(Issue(), self.integration_id_field)
39
+ self.manage_attachments = (
40
+ "create_attachment" in self.capabilities and "download_attachment" in self.capabilities
41
+ )
42
+
43
+ def integration_sync(self, regscale_id: int, regscale_module: str, sync_attachments: bool = True, **kwargs) -> None:
44
+ """
45
+ Runs the integration sync process
46
+
47
+ :param int regscale_id: RegScale record ID Number to sync issues to
48
+ :param str regscale_module: RegScale module to sync issues to
49
+ :param bool sync_attachments: Whether to sync attachments or not, defaults to True
50
+ :rtype: None
51
+ """
52
+ sync_attachments = sync_attachments and self.manage_attachments
53
+ self.logger.info(f"Fetching tickets from {self.integration}...")
54
+ if project := kwargs.get("project"):
55
+ query_filter = f"project[eq]{project}"
56
+ elif default_project := kwargs.get("default_project"):
57
+ query_filter = f"project[eq]{default_project}"
58
+ else:
59
+ query_filter = None
60
+ kwargs["filter"] = query_filter
61
+ integration_issues = self.fetch_integration_data(
62
+ func=self.tenant.engine_client.ticketing.query_tickets, **kwargs
63
+ )
64
+ self.logger.info(f"Found {len(integration_issues)} ticket(s) in {self.integration}")
65
+ self.logger.info("Fetching issues from RegScale...")
66
+ (
67
+ regscale_issues,
68
+ regscale_attachments,
69
+ ) = Issue.fetch_issues_and_attachments_by_parent(
70
+ parent_id=regscale_id,
71
+ parent_module=regscale_module,
72
+ fetch_attachments=sync_attachments,
73
+ )
74
+
75
+ self.process_regscale_issues(
76
+ regscale_issues=regscale_issues,
77
+ regscale_attachments=regscale_attachments,
78
+ sync_attachments=sync_attachments,
79
+ **kwargs,
80
+ )
81
+
82
+ self.process_integration_issues(
83
+ integration_issues=integration_issues,
84
+ regscale_issues=regscale_issues,
85
+ regscale_id=regscale_id,
86
+ regscale_module=regscale_module,
87
+ sync_attachments=sync_attachments,
88
+ )
89
+
90
+ def run_sync(self, *args, **kwargs) -> None:
91
+ """
92
+ Syncs RegScale issues with Ticketing connector using Synqly
93
+
94
+ :rtype: None
95
+ """
96
+ self.run_integration_sync(
97
+ *args,
98
+ **kwargs,
99
+ )
100
+
101
+ def download_issue_attachments_to_directory(
102
+ self,
103
+ directory: str,
104
+ integration_issue: "Ticket",
105
+ regscale_issue: Issue,
106
+ ) -> tuple[str, str]:
107
+ """
108
+ Function to download attachments from the integration via Synqly and RegScale issues to a directory
109
+
110
+ :param str directory: Directory to store the files in
111
+ :param Ticket integration_issue: Issue to download the attachments for
112
+ :param Issue regscale_issue: RegScale issue to download the attachments for
113
+ :return: Tuple of strings containing the Integration and RegScale directories
114
+ :rtype: tuple[str, str]
115
+ """
116
+ # determine which attachments need to be uploaded to prevent duplicates by checking hashes
117
+ synqly_dir = os.path.join(directory, self.integration)
118
+ check_file_path(synqly_dir, False)
119
+ # download all attachments from Integration via Synqly to the synqly directory in temp_dir
120
+ self.download_attachments(ticket_id=integration_issue.id, download_dir=synqly_dir)
121
+ # get the regscale issue attachments
122
+ regscale_issue_attachments = File.get_files_for_parent_from_regscale(
123
+ api=self.api,
124
+ parent_id=regscale_issue.id,
125
+ parent_module="issues",
126
+ )
127
+ # create a directory for the regscale attachments
128
+ regscale_dir = os.path.join(directory, "regscale")
129
+ check_file_path(regscale_dir, False)
130
+ # download regscale attachments to the directory
131
+ for attachment in regscale_issue_attachments:
132
+ with open(os.path.join(regscale_dir, attachment.trustedDisplayName), "wb") as file:
133
+ file.write(
134
+ File.download_file_from_regscale_to_memory(
135
+ api=self.api,
136
+ record_id=regscale_issue.id,
137
+ module="issues",
138
+ stored_name=attachment.trustedStorageName,
139
+ file_hash=(attachment.fileHash if attachment.fileHash else attachment.shaHash),
140
+ )
141
+ )
142
+ return synqly_dir, regscale_dir
143
+
144
+ def download_attachments(self, ticket_id: str, download_dir: str) -> int:
145
+ """
146
+ Downloads attachments from a ticket via Synqly
147
+
148
+ :param str ticket_id: Ticket ID to download attachments from
149
+ :param str download_dir: Directory to download attachments to
150
+ :return: # of Synqly attachments downloaded
151
+ :rtype: int
152
+ """
153
+ attachments = self.client.ticketing.list_attachments_metadata(ticket_id)
154
+ self.logger.debug("Found %i attachments for ticket %s", len(attachments.result), ticket_id)
155
+ for attachment in attachments.result:
156
+ download_response = self.client.ticketing.download_attachment(
157
+ ticket_id=ticket_id, attachment_id=attachment.id
158
+ )
159
+ output_path = os.path.join(download_dir, attachment.file_name)
160
+ with open(output_path, "wb") as f:
161
+ f.write(base64.b64decode(download_response.result.content))
162
+ self.logger.debug(
163
+ "Downloaded attachment: %s and wrote its contents to %s",
164
+ download_response.result.file_name,
165
+ attachment.file_name,
166
+ )
167
+ return len(attachments.result)
168
+
169
+ def upload_synqly_attachments(self, ticket_id: str, file_path: Path) -> None:
170
+ """
171
+ Uploads an attachment to a ticket via Synqly
172
+
173
+ :param str ticket_id: Ticket ID to attach the file to
174
+ :param Path file_path: Path to the file to attach
175
+ :rtype: None
176
+ """
177
+ from synqly import engine
178
+
179
+ with open(file_path.absolute(), "rb") as file:
180
+ content = base64.b64encode(file.read()) # type: ignore
181
+ self.logger.debug("Creating attachment for ticket %s", ticket_id)
182
+ self.client.ticketing.create_attachment(
183
+ ticket_id=ticket_id,
184
+ request=engine.CreateAttachmentRequest(
185
+ file_name=file_path.name,
186
+ content=content, # type: ignore
187
+ ),
188
+ )
189
+ self.logger.info("Added an attachment to %s ticket %s", self.integration, ticket_id)
190
+
191
+ def compare_files_for_dupes_and_upload(self, connector_issue: "Ticket", regscale_issue: Issue) -> None:
192
+ """
193
+ Compare attachments for provided Integration and RegScale issues via hash to prevent duplicates
194
+
195
+ :param Ticket connector_issue: Connector issue object to compare attachments from
196
+ :param Issue regscale_issue: RegScale issue object to compare attachments from
197
+ :rtype: None
198
+ """
199
+ connector_uploaded_attachments = []
200
+ regscale_uploaded_attachments = []
201
+ # create a temporary directory to store the downloaded attachments from desired connector and RegScale
202
+ with tempfile.TemporaryDirectory() as temp_dir:
203
+ # write attachments to the temporary directory
204
+ connector_dir, regscale_dir = self.download_issue_attachments_to_directory(
205
+ directory=temp_dir,
206
+ integration_issue=connector_issue,
207
+ regscale_issue=regscale_issue,
208
+ )
209
+ # get the hashes for the attachments in the RegScale and connector directories
210
+ # iterate all files in the connector directory and compute their hashes
211
+ ticket_attachment_hashes = compute_hashes_in_directory(connector_dir)
212
+ regscale_attachment_hashes = compute_hashes_in_directory(regscale_dir)
213
+
214
+ # check where the files need to be uploaded to before uploading
215
+ for file_hash, file in regscale_attachment_hashes.items():
216
+ if file_hash not in ticket_attachment_hashes:
217
+ try:
218
+ self.upload_synqly_attachments(
219
+ ticket_id=connector_issue.id,
220
+ file_path=Path(file),
221
+ )
222
+ connector_uploaded_attachments.append(file)
223
+ except TypeError as ex:
224
+ self.logger.error(
225
+ "Unable to upload %s to %s issue %s.\nError: %s",
226
+ Path(file).name,
227
+ self.integration,
228
+ connector_issue.id,
229
+ ex,
230
+ )
231
+ for file_hash, file in ticket_attachment_hashes.items():
232
+ if file_hash not in regscale_attachment_hashes:
233
+ with open(file, "rb") as in_file:
234
+ if File.upload_file_to_regscale(
235
+ file_name=f"{self.integration}_attachment_{Path(file).name}",
236
+ parent_id=regscale_issue.id,
237
+ parent_module="issues",
238
+ api=self.api,
239
+ file_data=in_file.read(),
240
+ ):
241
+ regscale_uploaded_attachments.append(file)
242
+ self.logger.debug(
243
+ "Uploaded %s to RegScale issue #%i.",
244
+ Path(file).name,
245
+ regscale_issue.id,
246
+ )
247
+ else:
248
+ self.logger.warning(
249
+ "Unable to upload %s to RegScale issue #%i.",
250
+ Path(file).name,
251
+ regscale_issue.id,
252
+ )
253
+ self.log_upload_outcome(
254
+ regscale_uploads=regscale_uploaded_attachments,
255
+ connector_uploads=connector_uploaded_attachments,
256
+ regscale_issue=regscale_issue,
257
+ connector_issue=connector_issue,
258
+ )
259
+
260
+ def log_upload_outcome(
261
+ self, regscale_uploads: list, connector_uploads: list, regscale_issue: Issue, connector_issue: "Ticket"
262
+ ) -> None:
263
+ """
264
+ Log the outcome of the attachment uploads
265
+
266
+ :param list regscale_uploads: List of RegScale attachments uploaded
267
+ :param list connector_uploads: List of Connector attachments uploaded
268
+ :param Issue regscale_issue: RegScale issue object
269
+ :param Ticket connector_issue: Connector issue object
270
+ :rtype: None
271
+ """
272
+ if regscale_uploads and connector_uploads:
273
+ self.logger.info(
274
+ "%i file(s) uploaded to RegScale issue #%i and %i file(s) uploaded to %s ticket %s.",
275
+ len(regscale_uploads),
276
+ regscale_issue.id,
277
+ len(connector_uploads),
278
+ self.integration,
279
+ connector_issue.id,
280
+ )
281
+ elif connector_uploads:
282
+ self.logger.info(
283
+ "%i file(s) uploaded to %s ticket %s.",
284
+ len(connector_uploads),
285
+ self.integration,
286
+ connector_issue.id,
287
+ )
288
+ elif regscale_uploads:
289
+ self.logger.info(
290
+ "%i file(s) uploaded to RegScale issue #%i.",
291
+ len(regscale_uploads),
292
+ regscale_issue.id,
293
+ )
294
+
295
+ def process_regscale_issues(
296
+ self,
297
+ regscale_issues: list[Issue],
298
+ regscale_attachments: Optional[dict] = None,
299
+ sync_attachments: bool = True,
300
+ **kwargs,
301
+ ) -> None:
302
+ """
303
+ Process RegScale issues and sync them to the Integration
304
+
305
+ :param list[Issue] regscale_issues: List of RegScale issues to process
306
+ :param dict regscale_attachments: Dictionary of RegScale attachments
307
+ :param bool sync_attachments: Flag to determine if attachments should be synced, defaults to True
308
+ :rtype: None
309
+ """
310
+ if regscale_issues:
311
+ # sync RegScale issues to Integration
312
+ self.sync_regscale_to_integration(
313
+ regscale_issues=regscale_issues,
314
+ sync_attachments=sync_attachments,
315
+ attachments=regscale_attachments,
316
+ **kwargs,
317
+ )
318
+ if self.regscale_objects_to_update:
319
+ upd_count = len(self.regscale_objects_to_update)
320
+ with self.job_progress as job_progress:
321
+ # create task to update RegScale issues
322
+ updating_issues = job_progress.add_task(
323
+ f"[#f8b737]Updating {upd_count} RegScale issue(s) from {self.integration}...",
324
+ total=upd_count,
325
+ )
326
+ # create threads to analyze Jira issues and RegScale issues
327
+ self.app.thread_manager.submit_tasks_from_list(
328
+ self.update_regscale_issues,
329
+ self.regscale_objects_to_update,
330
+ (updating_issues), # noqa
331
+ )
332
+ self.app.thread_manager.execute_and_verify()
333
+ self.logger.info(
334
+ "%i/%i issue(s) updated in RegScale.",
335
+ len(self.updated_regscale_objects),
336
+ upd_count,
337
+ )
338
+ else:
339
+ self.logger.info("No issues need to be updated in RegScale.")
340
+
341
+ def process_integration_issues(
342
+ self,
343
+ integration_issues: list["Ticket"],
344
+ regscale_issues: list[Issue],
345
+ regscale_id: int,
346
+ regscale_module: str,
347
+ sync_attachments: bool = True,
348
+ ) -> None:
349
+ """
350
+ Process Integration issues and sync them to RegScale
351
+
352
+ :param list[Ticket] integration_issues: List of Integration issues to process
353
+ :param list[Issue] regscale_issues: List of RegScale issues to process
354
+ :param int regscale_id: RegScale record ID Number to sync issues to
355
+ :param str regscale_module: RegScale module to sync issues to
356
+ :param bool sync_attachments: Flag to determine if attachments should be synced, defaults to True
357
+ :rtype: None
358
+ """
359
+ if integration_issues:
360
+ # sync integration issues to RegScale
361
+ with self.job_progress as job_progress:
362
+ # create task to create RegScale issues
363
+ creating_issues = job_progress.add_task(
364
+ f"[#f8b737]Analyzing {len(integration_issues)} {self.integration} ticket(s)"
365
+ f" and {len(regscale_issues)} RegScale issue(s)...",
366
+ total=len(integration_issues),
367
+ )
368
+ # create threads to analyze Jira issues and RegScale issues
369
+ self.app.thread_manager.submit_tasks_from_list(
370
+ self.create_and_update_regscale_issues,
371
+ integration_issues,
372
+ (
373
+ regscale_issues,
374
+ sync_attachments,
375
+ regscale_id,
376
+ regscale_module,
377
+ creating_issues,
378
+ ),
379
+ )
380
+ self.app.thread_manager.execute_and_verify()
381
+ self.logger.info(
382
+ "Analyzed %i %s ticket(s), created %i issue(s) and updated %i issue(s) in RegScale.",
383
+ len(integration_issues),
384
+ self.integration,
385
+ len(self.created_regscale_objects),
386
+ len(self.updated_regscale_objects),
387
+ )
388
+ else:
389
+ self.logger.info(f"No tickets need to be analyzed from {self.integration}.")
390
+
391
+ def create_and_update_regscale_issues(self, *args, **kwargs) -> None:
392
+ """
393
+ Function to create or update issues in RegScale from Jira
394
+
395
+ :rtype: None
396
+ """
397
+ # set up local variables from the passed args
398
+ integration_issue: "Ticket" = args[0] # type: ignore
399
+ (
400
+ regscale_issues,
401
+ add_attachments,
402
+ parent_id,
403
+ parent_module,
404
+ task,
405
+ ) = args[
406
+ 1 # type: ignore
407
+ ]
408
+ if self.has_integration_field:
409
+ regscale_issue: Optional[Issue] = next(
410
+ (
411
+ issue
412
+ for issue in regscale_issues
413
+ if getattr(issue, self.integration_id_field) == integration_issue.id
414
+ ),
415
+ None,
416
+ )
417
+ else:
418
+ # use the manualDetectionSource and manualDetectionId fields
419
+ regscale_issue: Optional[Issue] = next(
420
+ (
421
+ issue
422
+ for issue in regscale_issues
423
+ if issue.manualDetectionSource == self.integration
424
+ and issue.manualDetectionId == integration_issue.id
425
+ ),
426
+ None,
427
+ )
428
+ # see if the Jira issue needs to be created in RegScale
429
+ if integration_issue.status.lower() == "done" and regscale_issue:
430
+ # update the status and date completed of the RegScale issue
431
+ regscale_issue.status = "Closed"
432
+ regscale_issue.dateCompleted = get_current_datetime()
433
+ # update the issue in RegScale
434
+ self.updated_regscale_objects.append(regscale_issue.save())
435
+ elif regscale_issue:
436
+ # update the issue in RegScale
437
+ self.updated_regscale_objects.append(regscale_issue.save())
438
+ else:
439
+ # map the jira issue to a RegScale issue object
440
+ issue = self.mapper.to_regscale(
441
+ ocsf_object=integration_issue,
442
+ connector=self,
443
+ config=self.app.config,
444
+ parent_id=parent_id,
445
+ parent_module=parent_module,
446
+ **kwargs,
447
+ )
448
+ # create the issue in RegScale
449
+ if regscale_issue := issue.create():
450
+ self.logger.debug(
451
+ "Created issue #%i-%s in RegScale.",
452
+ regscale_issue.id,
453
+ regscale_issue.title,
454
+ )
455
+ self.created_regscale_objects.append(regscale_issue)
456
+ else:
457
+ self.logger.warning("Unable to create issue in RegScale.\nIssue: %s", issue.dict())
458
+ if add_attachments and regscale_issue:
459
+ # check if the integration issue has attachments
460
+ attachment_count = self.get_integration_attachment_count(integration_issue.id)
461
+ if attachment_count > 0:
462
+ # determine which attachments need to be uploaded to prevent duplicates by
463
+ # getting the hashes of all Jira & RegScale attachments
464
+ self.compare_files_for_dupes_and_upload(
465
+ connector_issue=integration_issue,
466
+ regscale_issue=regscale_issue,
467
+ )
468
+ # update progress bar
469
+ self.job_progress.update(task, advance=1)
470
+
471
+ def get_integration_attachment_count(self, ticket_id: str) -> int:
472
+ """
473
+ Get the number of attachments for a ticket in Synqly
474
+
475
+ :param str ticket_id: Ticket ID to get the attachments for
476
+ :return: Number of attachments for the ticket
477
+ :rtype: int
478
+ """
479
+ try:
480
+ attachments = self.client.ticketing.list_attachments_metadata(ticket_id)
481
+ return len(attachments.result)
482
+ except Exception as ex:
483
+ self.logger.error(f"Unable to get attachments for ticket {ticket_id}.\nError: {ex}")
484
+ return 0
485
+
486
+ def update_regscale_issues(self, *args) -> None:
487
+ """
488
+ Function to compare Integration issues and RegScale issues
489
+
490
+ :rtype: None
491
+ """
492
+ # set up local variables from the passed args
493
+ regscale_issue: Issue = args[0] # type: ignore
494
+ task: rich.progress.TaskID = args[1] # type: ignore
495
+ # update the issue in RegScale
496
+ regscale_issue.save()
497
+ self.logger.info(
498
+ "RegScale Issue %i was updated with the %s link.",
499
+ regscale_issue.id,
500
+ self.integration.title(),
501
+ )
502
+ self.updated_regscale_objects.append(regscale_issue)
503
+ # update progress bar
504
+ self.job_progress.update(task, advance=1)
505
+
506
+ def create_issue_in_integration(
507
+ self,
508
+ issue: Issue,
509
+ add_attachments: Optional[bool] = False,
510
+ attachments: list[File] = None,
511
+ **kwargs,
512
+ ) -> Optional["Ticket"]:
513
+ """
514
+ Create a new issue in the integration
515
+
516
+ :param Issue issue: RegScale issue object
517
+ :param Optional[bool] add_attachments: Flag to determine if attachments should be added to the issue
518
+ :param list[File] attachments: List of attachments to add to the issue
519
+ :return: Newly created issue in Jira
520
+ :rtype: Optional[Ticket]
521
+ """
522
+ try:
523
+ new_issue = self.mapper.to_ocsf(issue, **kwargs)
524
+ create_response = self.client.ticketing.create_ticket(request=new_issue)
525
+ except Exception as ex:
526
+ self.logger.error(f"Unable to create {self.integration} ticket.\nError: {ex}")
527
+ return None
528
+ if add_attachments and attachments:
529
+ self.compare_files_for_dupes_and_upload(
530
+ connector_issue=create_response.result,
531
+ regscale_issue=issue,
532
+ )
533
+ self.logger.info("Created ticket: {}".format(create_response.result.name))
534
+ return create_response.result
535
+
536
+ def sync_regscale_to_integration(
537
+ self,
538
+ regscale_issues: list[Issue],
539
+ sync_attachments: bool = True,
540
+ attachments: Optional[dict] = None,
541
+ **kwargs,
542
+ ) -> list[Issue]:
543
+ """
544
+ Sync issues from RegScale to Jira
545
+
546
+ :param list[Issue] regscale_issues: list of RegScale issues to sync to Jira
547
+ :param bool sync_attachments: Flag to determine if attachments should be synced, defaults to True
548
+ :param Optional[dict] attachments: Dictionary of attachments to sync, defaults to None
549
+ :return: list of RegScale issues that need to be updated
550
+ :rtype: list[Issue]
551
+ """
552
+ for issue in regscale_issues:
553
+ # see if integration field is an option
554
+ if self.has_integration_field and not getattr(issue, self.integration_id_field, None):
555
+ if new_issue := self.create_issue_in_integration(
556
+ issue=issue,
557
+ add_attachments=sync_attachments,
558
+ attachments=attachments,
559
+ **kwargs,
560
+ ):
561
+ self.created_integration_objects.append(new_issue)
562
+ setattr(issue, self.integration_id_field, new_issue.id)
563
+ self.regscale_objects_to_update.append(issue)
564
+ elif (
565
+ not self.has_integration_field
566
+ and issue.manualDetectionSource != self.integration
567
+ and not issue.manualDetectionId
568
+ ):
569
+ if new_issue := self.create_issue_in_integration(
570
+ issue=issue,
571
+ add_attachments=sync_attachments,
572
+ attachments=attachments,
573
+ **kwargs,
574
+ ):
575
+ self.created_integration_objects.append(new_issue)
576
+ # use the manualDetectionSource and manualDetectionId fields
577
+ issue.manualDetectionSource = self.integration
578
+ issue.manualDetectionId = new_issue.id
579
+ self.regscale_objects_to_update.append(issue)
580
+ # output the final result
581
+ if self.created_integration_objects:
582
+ self.logger.info("%i new ticket(s) opened in %s.", len(self.created_integration_objects), self.integration)
583
+ return self.regscale_objects_to_update