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,1110 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Functions used throughout the application"""
4
+ import logging
5
+
6
+ # standard python imports
7
+ from typing import TYPE_CHECKING
8
+
9
+ from regscale.core.app import create_logger
10
+ from regscale.core.utils.date import datetime_str
11
+
12
+ if TYPE_CHECKING:
13
+ import pandas as pd # Type Checking
14
+ from regscale.core.app.application import Application
15
+ from regscale.core.app.api import Api
16
+
17
+ import csv
18
+ import glob
19
+ import hashlib
20
+ import json
21
+ import math
22
+ import ntpath
23
+ import os
24
+ import platform
25
+ import random
26
+ import re
27
+ import sys
28
+ from collections import abc
29
+ from datetime import datetime
30
+ from io import BytesIO
31
+ from pathlib import Path
32
+ from shutil import copyfileobj, copytree, rmtree
33
+ from site import getusersitepackages
34
+ from tempfile import gettempdir
35
+ from typing import Any, BinaryIO, Dict, NoReturn, Optional, Union
36
+ from urllib.parse import urlparse
37
+
38
+ import psutil
39
+ import pytz
40
+ import requests
41
+ import xmltodict
42
+ from dateutil import relativedelta
43
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
44
+
45
+ from regscale.core.app.internal.login import is_licensed
46
+ from regscale.exceptions.license_exception import LicenseException
47
+
48
+ logger = create_logger()
49
+
50
+
51
+ def get_cross_platform_user_data_dir() -> Path:
52
+ """
53
+ Return the user data directory for the current platform
54
+
55
+ :return: user data directory
56
+ :rtype: Path
57
+ """
58
+ if sys.platform == "win32":
59
+ return Path(os.getenv("APPDATA")) / "regscale"
60
+ else:
61
+ return Path.home() / ".config" / "regscale"
62
+
63
+
64
+ def check_license(config: Optional[dict] = None) -> "Application":
65
+ """
66
+ Check RegScale license
67
+
68
+ :param Optional[dict] config: Config dictionary, defaults to None
69
+ :raises: LicenseException if Application license isn't at the requested level of the feature
70
+ :return: Application object
71
+ :rtype: Application
72
+ """
73
+ from regscale.core.app.application import Application
74
+
75
+ try:
76
+ app = Application(config)
77
+ if not is_licensed(app):
78
+ raise LicenseException("This feature is limited to RegScale Enterprise, please check RegScale license.")
79
+ except LicenseException as e:
80
+ error_and_exit(str(e.with_traceback(None)))
81
+ return app
82
+
83
+
84
+ def get_site_package_location() -> Path:
85
+ """
86
+ Return site package location as string
87
+
88
+ :return: site package location
89
+ :rtype: Path
90
+ """
91
+ return Path(getusersitepackages())
92
+
93
+
94
+ def creation_date(path_to_file: Union[str, Path]) -> float:
95
+ """
96
+ Try to get the date that a file was created, falling back to when it was
97
+ last modified if that isn't possible.
98
+ See http://stackoverflow.com/a/39501288/1709587 for explanation.
99
+
100
+ :param Union[str, Path] path_to_file: Path to the file
101
+ :return: Date of creation
102
+ :rtype: float
103
+ """
104
+ if platform.system() == "Windows":
105
+ return os.path.getctime(path_to_file)
106
+ else:
107
+ stat = os.stat(path_to_file)
108
+ try:
109
+ return stat.st_birthtime
110
+ except AttributeError:
111
+ # We're probably on Linux. No easy way to get creation dates here,
112
+ # so we'll settle for when its content was last modified.
113
+ return stat.st_mtime
114
+
115
+
116
+ def is_valid_fqdn(fqdn: str) -> bool:
117
+ """
118
+ Function to check if the provided fqdn is valid
119
+
120
+ :param str fqdn: FQDN to check
121
+ :return: True if valid, False if not
122
+ :rtype: bool
123
+ """
124
+ if isinstance(fqdn, str):
125
+ fqdn_regex = r"^(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)*"
126
+ fqdn_regex += r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost)$"
127
+ if re.match(fqdn_regex, fqdn, re.IGNORECASE):
128
+ return True
129
+ return False
130
+
131
+
132
+ def convert_datetime_to_regscale_string(reg_dt: datetime, dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S") -> str:
133
+ """
134
+ Convert a datetime object to a RegScale API friendly string
135
+
136
+ :param datetime reg_dt: Datetime object
137
+ :param Optional[str] dt_format: Defaults to "%Y-%m-%d %H:%M:%S".
138
+ :return: RegScale datetime string
139
+ :rtype: str
140
+ """
141
+ return datetime_str(reg_dt, dt_format)
142
+
143
+
144
+ def reformat_str_date(date_str: str, dt_format: str = "%m/%d/%Y") -> str:
145
+ """
146
+ Function to convert a string into a datetime object and reformat it to dt_format, default \
147
+ format is MM/DD/YYYY
148
+
149
+ :param str date_str: date as a string
150
+ :param str dt_format: datetime string format, defaults to "%m/%d/%Y"
151
+ :return: string with the provided date format
152
+ :rtype: str
153
+ """
154
+ # replace the T with a space and create list of result
155
+ date_str = date_str.replace("T", " ").split(" ")
156
+
157
+ return datetime.strptime(date_str[0], "%Y-%m-%d").strftime(dt_format)
158
+
159
+
160
+ def pretty_short_str(long_str: str, start_length: int, end_length: int) -> str:
161
+ """
162
+ Function to convert long string to shortened string
163
+
164
+ :param str long_str: long string to shorten
165
+ :param int start_length: number of characters to use from string start
166
+ :param int end_length: number of characters to use from string end
167
+ :return: attractive short string of form 'start..end'
168
+ :rtype: str
169
+ """
170
+ return long_str[:start_length] + ".." + long_str[-end_length:]
171
+
172
+
173
+ def camel_case(text: str) -> str:
174
+ """
175
+ Function to convert known module to camel case... (GraphQL)
176
+
177
+ :param str text: string to convert to camelCase
178
+ :return: Provided string in camelCase format
179
+ :rtype: str
180
+ """
181
+ # Split the input string into words using a regular expression
182
+ words = [word for word in re.split(r"[\s_\-]+|(?<=[a-z])(?=[A-Z])", text) if word]
183
+ # Make the first word lowercase, and capitalize the first letter of each subsequent word
184
+ words[0] = words[0].lower()
185
+ for i in range(1, len(words)):
186
+ words[i] = words[i].capitalize()
187
+ # Concatenate the words without spaces
188
+ return "".join(words)
189
+
190
+
191
+ def snake_case(text: str) -> str:
192
+ """
193
+ Function to convert a string to snake_case
194
+
195
+ :param str text: string to convert
196
+ :return: string in snake_case
197
+ :rtype: str
198
+ """
199
+ # Split the input string into words using a regular expression
200
+ words = [word for word in re.split(r"[\s_\-]+|(?<=[a-z])(?=[A-Z])", text) if word]
201
+ # Make the first word lowercase, and capitalize the first letter of each subsequent word
202
+ words[0] = words[0].lower()
203
+ for i in range(1, len(words)):
204
+ words[i] = words[i].capitalize()
205
+ # Concatenate the words without spaces
206
+ return "_".join(words)
207
+
208
+
209
+ def uncamel_case(camel_str: str) -> str:
210
+ """
211
+ Function to convert camelCase strings to Title Case
212
+
213
+ :param str camel_str: string to convert
214
+ :return: Title Case string from provided camelCase
215
+ :rtype: str
216
+ """
217
+ # check to see if a string with data was passed
218
+ if camel_str != "":
219
+ # split at any uppercase letters
220
+ result = re.sub("([A-Z])", r" \1", camel_str)
221
+
222
+ # use title to Title Case the string and strip to remove leading
223
+ # and trailing white spaces
224
+ result = result.title().strip()
225
+ return result
226
+ return ""
227
+
228
+
229
+ def get_css(file_path: str) -> str:
230
+ """
231
+ Function to load the CSS properties from the given file_path
232
+
233
+ :param str file_path: file path to the desired CSS file
234
+ :return: CSS as a string
235
+ :rtype: str
236
+ """
237
+ # create variable to store the string and return
238
+ css = ""
239
+ import importlib.resources as pkg_resources
240
+
241
+ # check if the filepath exists before trying to open it
242
+ with pkg_resources.open_text("regscale.models", file_path) as file:
243
+ # read the file and store it as a string
244
+ css = file.read()
245
+ # return the css variable
246
+ return css
247
+
248
+
249
+ def epoch_to_datetime(
250
+ epoch: str,
251
+ epoch_type: str = "seconds",
252
+ dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S",
253
+ ) -> str:
254
+ """
255
+ Return datetime from unix epoch
256
+
257
+ :param str epoch: unix epoch
258
+ :param str epoch_type: type of epoch, defaults to 'seconds'
259
+ :param Optional[str] dt_format: datetime string format, defaults to "%Y-%m-%d %H:%M:%S"
260
+ :return: datetime string
261
+ :rtype: str
262
+ """
263
+ if epoch_type == "milliseconds":
264
+ return datetime.fromtimestamp(int(epoch) / 1000).strftime(dt_format)
265
+ return datetime.fromtimestamp(int(epoch)).strftime(dt_format)
266
+
267
+
268
+ def get_current_datetime(dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S") -> str:
269
+ """
270
+ Return current datetime
271
+
272
+ :param Optional[str] dt_format: desired format for datetime string, defaults to "%Y-%m-%d %H:%M:%S"
273
+ :return: Current datetime as a string
274
+ :rtype: str
275
+ """
276
+ return datetime.now().strftime(dt_format)
277
+
278
+
279
+ def regscale_string_to_datetime(reg_dt: str) -> datetime:
280
+ """
281
+ Convert a RegScale API friendly string to a datetime object
282
+
283
+ :param str reg_dt: RegScale datetime string
284
+ :return: Datetime object
285
+ :rtype: datetime
286
+ """
287
+ try:
288
+ dt = datetime.fromisoformat(reg_dt)
289
+ # Make sure timezone is UTC aware
290
+ return pytz.utc.localize(dt)
291
+ except ValueError:
292
+ try:
293
+ # remove the milliseconds from the string if they exist (prevents ValueError)
294
+ date_parts = reg_dt.split(".")
295
+ if len(date_parts) > 1:
296
+ reg_dt = date_parts[0]
297
+ dt = datetime.fromisoformat(reg_dt)
298
+ return pytz.utc.localize(dt)
299
+ except ValueError:
300
+ error_and_exit(f"Invalid datetime string provided: {reg_dt}")
301
+
302
+
303
+ def regscale_string_to_epoch(reg_dt: str) -> int:
304
+ """
305
+ Convert a RegScale API friendly string to an epoch seconds integer
306
+
307
+ :param str reg_dt: RegScale datetime string
308
+ :return: Datetime object
309
+ :rtype: int
310
+ """
311
+ dt = regscale_string_to_datetime(reg_dt)
312
+ # convert to epoch
313
+ return int(dt.timestamp())
314
+
315
+
316
+ def cci_control_mapping(file_path: Path) -> list:
317
+ """
318
+ Simple function to read csv artifact to help with STIG mapping
319
+
320
+ :param Path file_path: file path to the csv artifact
321
+ :return: List of the csv contents
322
+ :rtype: list
323
+ """
324
+ with open(file_path, "r", newline="", encoding="utf-8") as file:
325
+ reader = csv.reader(file)
326
+ return list(reader)
327
+
328
+
329
+ def copy_and_overwrite(from_path: Path, to_path: Path) -> None:
330
+ """
331
+ Copy and overwrite files recursively in a given path
332
+
333
+ :param Path from_path: Path to copy from
334
+ :param Path to_path: Path to copy to
335
+ :rtype: None
336
+ """
337
+ if os.path.exists(to_path):
338
+ rmtree(to_path)
339
+ copytree(from_path, to_path)
340
+
341
+
342
+ def create_progress_object(indeterminate: bool = False) -> Progress:
343
+ """
344
+ Function to create and return a progress object
345
+
346
+ :param bool indeterminate: If the progress bar should be indeterminate, defaults to False
347
+ :return: Progress object for live progress in console
348
+ :rtype: Progress
349
+ """
350
+ if indeterminate:
351
+ return Progress(
352
+ "{task.description}",
353
+ SpinnerColumn(),
354
+ )
355
+ return Progress(
356
+ "{task.description}",
357
+ SpinnerColumn(),
358
+ BarColumn(),
359
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
360
+ TimeElapsedColumn(),
361
+ TextColumn("Remaining:"),
362
+ TimeRemainingColumn(),
363
+ )
364
+
365
+
366
+ def get_file_type(file_name: Union[Path, str]) -> str:
367
+ """
368
+ Function to get the file type of the provided file_path and returns it as a string
369
+
370
+ :param Union[Path, str] file_name: Path to the file
371
+ :return: Returns string of file type
372
+ :rtype: str
373
+ """
374
+ if isinstance(file_name, str):
375
+ file_name = Path(file_name)
376
+ file_type = Path(file_name).suffix
377
+ return file_type.lower()
378
+
379
+
380
+ def xml_file_to_dict(file_path: Path) -> dict:
381
+ """
382
+ Function to convert an XML file to a dictionary
383
+
384
+ :param Path file_path: Path to the XML file
385
+ :return: Dictionary of the XML file
386
+ :rtype: dict
387
+ """
388
+
389
+ # Use try/except for performance reasons, faster than check before.
390
+ try:
391
+ return xmltodict.parse(file_path.read_text(encoding="utf-8"))
392
+ except FileNotFoundError:
393
+ error_and_exit(f"The provided file path doesn't exist! Provided: {file_path}")
394
+
395
+
396
+ def get_file_name(file_path: str) -> str:
397
+ """
398
+ Function to parse the provided file path and returns the file's name as a string
399
+
400
+ :param str file_path: path to the file
401
+ :return: File name
402
+ :rtype: str
403
+ """
404
+ # split the provided file_path with ntpath
405
+ directory, file_name = ntpath.split(file_path)
406
+ # return the file_path or directory
407
+ return file_name or ntpath.basename(directory)
408
+
409
+
410
+ def get_recent_files(file_path: Path, file_count: int, file_type: str = None) -> list:
411
+ """
412
+ Function to go to the provided file_path and get the x number of recent items
413
+ optional argument of file_type to filter the directory
414
+
415
+ :param Path file_path: Directory to get recent files in
416
+ :param int file_count: # of files to return
417
+ :param str file_type: file type to sort directory for, defaults to none
418
+ :return: list of recent files in the provided directory
419
+ :rtype: list
420
+ """
421
+ # verify the provided file_path exists
422
+ if os.path.exists(file_path):
423
+ # get the list of files from the provided path, get the desired
424
+ # file_type if provided
425
+ file_list = glob.glob(f"{file_path}/*{file_type}") if file_type else glob.glob(f"{file_path}/*")
426
+
427
+ # sort the file_list by modified date in descending order
428
+ file_list.sort(key=os.path.getmtime, reverse=True)
429
+
430
+ # check if file_list has more items than the provided number, remove the rest
431
+ if len(file_list) > file_count:
432
+ file_list = file_list[:file_count]
433
+ else:
434
+ error_and_exit(f"The provided file path doesn't exist! Provided: {file_path}")
435
+ # return the list of files
436
+ return file_list
437
+
438
+
439
+ def check_config_for_issues(config: dict, issue: str, key: str) -> Optional[Any]:
440
+ """
441
+ Function to check config keys
442
+
443
+ :param dict config: Application config
444
+ :param str issue: Issue to check
445
+ :param str key: Key to check
446
+ :return: Value from config or None
447
+ :rtype: Optional[Any]
448
+ """
449
+ return (
450
+ config["issues"][issue][key]
451
+ if "issues" in config.keys()
452
+ and issue in config["issues"].keys()
453
+ and config["issues"][issue].get(key) is not None
454
+ else None
455
+ )
456
+
457
+
458
+ def find_uuid_in_str(str_to_search: str) -> str:
459
+ """
460
+ Find a UUID in a long string
461
+
462
+ :param str str_to_search: Long string
463
+ :return: Matching string
464
+ :rtype: str
465
+ """
466
+ if dat := re.findall(
467
+ r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
468
+ str_to_search,
469
+ ):
470
+ return dat[0]
471
+ return str_to_search
472
+
473
+
474
+ def recursive_items(nested: Union[abc.Mapping, dict]):
475
+ """
476
+ Function to recursively move through a dictionary and pull out key value pairs
477
+
478
+ :param Union[abc.Mapping, dict] nested: Nested dict to recurse through
479
+ :yield: generated iterable key value pairs
480
+ """
481
+ for key, value in nested.items():
482
+ if isinstance(value, abc.Mapping):
483
+ yield from recursive_items(value)
484
+ if isinstance(value, list):
485
+ for dictionary in value:
486
+ if isinstance(dictionary, dict):
487
+ yield from recursive_items(dictionary)
488
+ else:
489
+ yield key, value
490
+
491
+
492
+ def check_file_path(file_path: Union[str, Path], output: bool = True) -> None:
493
+ """
494
+ Function to check the provided file path, if it doesn't exist it will be created
495
+
496
+ :param Union[str, Path] file_path: Path to the directory
497
+ :param bool output: If the function should output to the console, defaults to True
498
+ :rtype: None
499
+ """
500
+ # see if the provided directory exists, if not create it
501
+ if not os.path.exists(file_path):
502
+ os.makedirs(file_path)
503
+ # notify user directory has been created
504
+ if output:
505
+ logger.info("%s didn't exist, but has been created.", file_path)
506
+
507
+
508
+ def capitalize_words(word: str) -> str:
509
+ """
510
+ Function to convert string to title case
511
+
512
+ :param str word: Desired string to process
513
+ :return: String with words titlecased
514
+ :rtype: str
515
+ """
516
+ return re.sub(r"\w+", lambda m: m.group(0).capitalize(), word)
517
+
518
+
519
+ def error_and_exit(error_desc: str, show_exec: bool = True) -> NoReturn:
520
+ """
521
+ Function to display and log the provided error_desc and exits the application
522
+
523
+ :param str error_desc: Description of the error encountered
524
+ :param bool show_exec: If the function should show the exception, defaults to True
525
+ :rtype: NoReturn
526
+ """
527
+ exc_info = sys.exc_info()[0] is not None if sys.exc_info() and show_exec else None
528
+ if exc_info:
529
+ logger.error(error_desc, exc_info=True)
530
+ else:
531
+ logger.error(error_desc)
532
+ from regscale.core.app.application import Application
533
+
534
+ app = Application()
535
+ if app.running_in_airflow:
536
+ raise RuntimeError(error_desc)
537
+ sys.exit(1)
538
+
539
+
540
+ def check_url(url: str) -> bool:
541
+ """
542
+ Function to check if the provided url is valid
543
+
544
+ :param str url: URL to check
545
+ :return: True if URL is valid, False if not
546
+ :rtype: bool
547
+ """
548
+ try:
549
+ result = urlparse(url)
550
+ return all([result.scheme, result.netloc])
551
+ except ValueError:
552
+ return False
553
+
554
+
555
+ def download_file(url: str, download_path: str = gettempdir(), verify: bool = True) -> Path:
556
+ """
557
+ Download file from the provided url and save it to the provided download_path
558
+
559
+ :param str url: URL location of the file to download
560
+ :param str download_path: Path to download the file to, defaults to gettempdir()
561
+ :param bool verify: SSL verification for requests, defaults to True
562
+ :return: Path to the downloaded file
563
+ :rtype: Path
564
+ """
565
+ path = Path(download_path)
566
+ local_filename = ntpath.basename(url)
567
+ # NOTE the stream=True parameter below
568
+ with requests.get(url, stream=True, timeout=10, verify=verify) as response:
569
+ response.raise_for_status()
570
+ with open(path / local_filename, "wb") as file:
571
+ copyfileobj(response.raw, file)
572
+ return path / local_filename
573
+
574
+
575
+ def check_supported_file_type(file: Path) -> None:
576
+ """
577
+ Check if the file type is supported.
578
+
579
+ :param Path file: Path to the file.
580
+ :raises: RuntimeError if the file type is not supported.
581
+ :rtype: None
582
+ """
583
+ if file.suffix.lower() not in [".csv", ".json", ".xlsx"]:
584
+ raise RuntimeError(f"Unsupported file type: {file.suffix}")
585
+
586
+
587
+ def save_to_csv(file: Path, data: Any, output_log: bool, transpose: bool = True) -> None:
588
+ """
589
+ Save data to a CSV file.
590
+
591
+ :param Path file: Path to the file.
592
+ :param Any data: The data to save.
593
+ :param bool output_log: Whether to output logs.
594
+ :param bool transpose: Whether to transpose the data, defaults to True
595
+ :rtype: None
596
+ """
597
+ import pandas as pd # Optimize import performance
598
+
599
+ if transpose:
600
+ data = pd.DataFrame(data).transpose()
601
+ else:
602
+ data = pd.DataFrame(data)
603
+ data.to_csv(file)
604
+ if output_log:
605
+ logger.info("Data successfully saved to: %s", file.name)
606
+
607
+
608
+ def save_to_excel(file: Path, data: Any, output_log: bool, transpose: bool = True) -> None:
609
+ """
610
+ Save data to an Excel file.
611
+
612
+ :param Path file: Path to the file.
613
+ :param Any data: The data to save.
614
+ :param bool output_log: Whether to output logs.
615
+ :param bool transpose: Whether to transpose the data, defaults to True
616
+ :rtype: None
617
+ """
618
+ import pandas as pd # Optimize import performance
619
+
620
+ d_frame = pd.DataFrame(data)
621
+ if transpose:
622
+ d_frame = d_frame.transpose()
623
+
624
+ d_frame.to_excel(file)
625
+ if output_log:
626
+ logger.info("Data successfully saved to: %s", file.name)
627
+
628
+
629
+ def save_to_json(file: Path, data: Any, output_log: bool) -> None:
630
+ """
631
+ Save data to a JSON file. Attempts to use json.dump and falls back to write if needed.
632
+
633
+ :param Path file: Path to the file.
634
+ :param Any data: The data to save.
635
+ :param bool output_log: Whether to output logs.
636
+ :rtype: None
637
+ """
638
+ try:
639
+ with open(file, "w", encoding="utf-8") as outfile:
640
+ json.dump(data, outfile, indent=4)
641
+ except TypeError:
642
+ with open(file, "w", encoding="utf-8") as outfile:
643
+ outfile.write(str(data))
644
+ if output_log:
645
+ logger.info("Data successfully saved to %s", file.name)
646
+
647
+
648
+ def save_data_to(file: Path, data: Any, output_log: bool = True, transpose_data: bool = True) -> None:
649
+ """
650
+ Save the provided data to the specified file.
651
+
652
+ :param Path file: Path to the file.
653
+ :param Any data: The data to save.
654
+ :param bool output_log: Output logs during execution, defaults to True.
655
+ :param bool transpose_data: Transpose the data for csv or xlsx files, defaults to True.
656
+ :rtype: None
657
+ """
658
+ check_supported_file_type(file)
659
+ check_file_path(file.parent)
660
+
661
+ if output_log:
662
+ logger.info("Prepping data to be saved to %s", file.name)
663
+
664
+ try:
665
+ if file.suffix.lower() == ".csv":
666
+ save_to_csv(file, data, output_log, transpose_data)
667
+ elif file.suffix.lower() == ".xlsx":
668
+ save_to_excel(file, data, output_log, transpose_data)
669
+ elif file.suffix.lower() == ".json":
670
+ save_to_json(file, data, output_log)
671
+ except PermissionError:
672
+ error_and_exit(f"Unable to save {file.name}. Please verify it is closed and try again.")
673
+
674
+
675
+ def remove_nested_dict(data: dict, skip_keys: list = None) -> dict:
676
+ """
677
+ Function to remove nested dictionaries in the provided dictionary,
678
+ also allows the option to remove a key from the provided dictionary
679
+
680
+ :param dict data: The raw data that needs to have nested dictionaries removed
681
+ :param list skip_keys: List of Keys to skip during iteration of the provided dict
682
+ :return: Clean dictionary without nested dictionaries
683
+ :rtype: dict
684
+ """
685
+ # create blank dictionary to store the clean dictionary
686
+ result = {}
687
+ # iterate through the keys and values in the provided dictionary
688
+ for key, value in data.items():
689
+ # see if skip_key was provided and if the current key == skip_key
690
+ if skip_keys and key in skip_keys:
691
+ # continue to the next key
692
+ continue
693
+ # check if the item is a nested dictionary
694
+ if isinstance(value, dict):
695
+ # evaluate the nested dictionary passing the nested dictionary and skip_key
696
+ new_keys = remove_nested_dict(value, skip_keys=skip_keys)
697
+ # update the value to a non-nested dictionary
698
+ # result[key] = value FIXME remove for now, is causing issues
699
+ # iterate through the keys of the nested dictionary
700
+ for inner_key in new_keys:
701
+ # make sure key doesn't already exist in result
702
+ if f"{key}_{inner_key}" in result:
703
+ last_char = inner_key[-1]
704
+ # see if the inner_key ends with a number
705
+ if isinstance(last_char, int):
706
+ # increment the number by 1 and replace the old one with the new one
707
+ last_char += 1
708
+ inner_key[-1] = last_char
709
+ else:
710
+ inner_key += "2"
711
+ # combine the key + nested key and store it into the clean dictionary
712
+ result[f"{key}_{inner_key}"] = result[key][inner_key]
713
+ else:
714
+ # item isn't a nested dictionary, save the data
715
+ result[key] = value
716
+ # return the un-nested dictionary
717
+ return result
718
+
719
+
720
+ def flatten_dict(data: abc.MutableMapping) -> abc.MutableMapping:
721
+ """
722
+ Flattens a dictionary
723
+
724
+ :param abc.MutableMapping data: data that needs to be flattened
725
+ :return: A flattened dictionary that has camelcase keys
726
+ :rtype: abc.MutableMapping
727
+ """
728
+ import pandas as pd # Optimize import performance
729
+
730
+ # create variable to store the clean and flattened dictionary
731
+ flat_dict_clean = {}
732
+
733
+ # flatten the dictionary using panda's json_normalize function
734
+ [flat_dict] = pd.json_normalize(data, sep="@").to_dict(orient="records")
735
+
736
+ # iterate through the keys to camelcase them and
737
+ for key, value in flat_dict.items():
738
+ # find the location of all the @, which are the separator for nested keys
739
+ sep_locations = key.find("@")
740
+
741
+ # check if there are more than one period
742
+ if isinstance(sep_locations, list):
743
+ # iterate through the period locations
744
+ for period in sep_locations:
745
+ # capitalize the character following the period
746
+ key = key[:period] + key[period + 1].upper() + key[period + 2 :]
747
+
748
+ # remove the @
749
+ key = key.replace("@", "")
750
+ elif sep_locations != -1:
751
+ # capitalize the character following the @
752
+ key = key[:sep_locations] + key[sep_locations + 1].upper() + key[sep_locations + 2 :]
753
+
754
+ # remove the @
755
+ key = key.replace("@", "")
756
+
757
+ # add the cleaned key with the original value
758
+ flat_dict_clean[key] = value
759
+ return flat_dict_clean
760
+
761
+
762
+ def days_between(vuln_time: str, fmt: Optional[str] = "%Y-%m-%dT%H:%M:%SZ") -> int:
763
+ """
764
+ Find the difference in days between 2 datetimes
765
+
766
+ :param str vuln_time: date published
767
+ :param Optional[str] fmt: datetime string format, defaults to "%Y-%m-%d %H:%M:%S"
768
+ :return: days between 2 dates
769
+ :rtype: int
770
+ """
771
+ start = datetime.strptime(vuln_time, fmt)
772
+ today = datetime.strftime(datetime.now(), fmt)
773
+ end = datetime.strptime(today, fmt)
774
+ difference = relativedelta.relativedelta(end, start)
775
+ return difference.days
776
+
777
+
778
+ def parse_url_for_pagination(raw_string: str) -> str:
779
+ """
780
+ Function to parse the provided string and get the URL for pagination
781
+
782
+ :param str raw_string: string that needs to be parsed for the pagination URL
783
+ :return: URL for pagination in Okta API
784
+ :rtype: str
785
+ """
786
+ # split the string at the < signs
787
+ split_urls = raw_string.split("<")
788
+
789
+ # get the last entry
790
+ split_url = split_urls[-1]
791
+
792
+ # remove the remaining text from the last entry and return it
793
+ return split_url[: split_url.find(">")]
794
+
795
+
796
+ def random_hex_color() -> str:
797
+ """Return a random hex color
798
+
799
+ :return: hex color
800
+ :rtype: str
801
+ """
802
+ return "#%02X%02X%02X" % (
803
+ random.randint(0, 255),
804
+ random.randint(0, 255),
805
+ random.randint(0, 255),
806
+ )
807
+
808
+
809
+ def format_dict_to_html(data: dict, indent: int = 1) -> str:
810
+ """Format a dictionary to HTML
811
+
812
+ :param dict data: Dictionary of data
813
+ :param int indent: Indentation. Defaults to 1.
814
+ :return: String representing HTML
815
+ :rtype: str
816
+ """
817
+ htmls = []
818
+ for key, val in data.items():
819
+ htmls.append(
820
+ f"<span style='font-style: italic; color: #888'>{key}</span>: {format_data_to_html(val, indent + 1)}"
821
+ )
822
+
823
+ return f'<div style="margin-left: {indent}em">{",<br>".join(htmls)}</div>'
824
+
825
+
826
+ def format_data_to_html(obj: Union[list, dict], indent: int = 1) -> str:
827
+ """Format a list or a dict object to HTML
828
+
829
+ :param Union[list, dict] obj: list or dict of data
830
+ :param int indent: Indentation. Defaults to 1.
831
+ :return: String representing HTML
832
+ :rtype: str
833
+ """
834
+ htmls = []
835
+
836
+ if isinstance(obj, list):
837
+ htmls.extend(format_data_to_html(key, indent + 1) for key in obj)
838
+ elif isinstance(obj, dict):
839
+ htmls.extend(
840
+ f"<span style='font-style: italic; color: #888'>{key}</span>: {format_data_to_html(val, indent + 1)}"
841
+ for key, val in obj.items()
842
+ )
843
+ if htmls:
844
+ return f'<div style="margin-left: {indent}em">{",<br>".join(htmls)}</div>'
845
+ return str(obj)
846
+
847
+
848
+ def get_env_variable(key: str) -> Optional[Any]:
849
+ """Return environment variable value regardless of case.
850
+
851
+ :param str key: Environment variable key
852
+ :return: Environment variable value
853
+ :rtype: Optional[Any]
854
+ """
855
+ return next((v for k, v in os.environ.items() if k.lower() == key.lower()), None)
856
+
857
+
858
+ def find_keys(node: Union[list, dict], kv: Any):
859
+ """
860
+ Python generator function to traverse deeply nested lists or dictionaries to
861
+ extract values of every key found in a given node
862
+
863
+ :param Union[list, dict] node: A string, dict or list to parse.
864
+ :param Any kv: Key, Value pair
865
+ :yield: Value of the key
866
+ """
867
+ if isinstance(node, list):
868
+ for i in node:
869
+ yield from find_keys(i, kv)
870
+ elif isinstance(node, dict):
871
+ if kv in node:
872
+ yield node[kv]
873
+ for j in node.values():
874
+ yield from find_keys(j, kv)
875
+
876
+
877
+ def get_user_names() -> "pd.DataFrame":
878
+ """This function uses API Endpoint to retrieve all user names in database
879
+
880
+ :return: pandas dataframe with usernames
881
+ :rtype: pd.DataFrame
882
+ """
883
+ from regscale.core.app.api import Api
884
+ from regscale.core.app.application import Application
885
+
886
+ app = Application()
887
+ config = app.config
888
+ api = Api()
889
+ accounts = api.get(url=config["domain"] + "/api/accounts").json()
890
+
891
+ user_names = [[" ".join(item["name"].split()), item["id"]] for item in accounts]
892
+ import pandas as pd # Optimize import performance
893
+
894
+ return pd.DataFrame(
895
+ user_names,
896
+ index=None,
897
+ columns=["User", "UserId"],
898
+ )
899
+
900
+
901
+ def check_empty_nan(value: Any, default_return: Any = None) -> Union[str, float, bool, None]:
902
+ """
903
+ This function takes a given value and checks if value is empty, NaN, or a bool value based on value type
904
+
905
+ :param Any value: Value for checking
906
+ :param Any default_return: The default return value, defaults to None
907
+ :return: A string value, float value, bool value, or None
908
+ :rtype: Union[str, float, bool, None]
909
+ """
910
+ if isinstance(value, str) and value.lower().strip() in ["true", "false"]:
911
+ return value.lower().strip() == "true"
912
+ if isinstance(value, str) and value.strip() == "":
913
+ return default_return
914
+ if isinstance(value, float) and math.isnan(value):
915
+ return default_return
916
+ return value
917
+
918
+
919
+ def compute_hash(file: Union[BinaryIO, BytesIO], chunk_size: int = 8192) -> str:
920
+ """
921
+ Computes the SHA-256 hash of a file-like object using chunks to avoid using too much memory
922
+
923
+ :param Union[BinaryIO, BytesIO] file: File-like object that supports .read() and .seek()
924
+ :param int chunk_size: Size of the chunks to read from the file, defaults to 8192
925
+ :return: SHA-256 hash of the file
926
+ :rtype: str
927
+ """
928
+ hasher = hashlib.sha256()
929
+
930
+ # Read the file in small chunks to avoid using too much memory
931
+ while True:
932
+ if chunk := file.read(chunk_size):
933
+ hasher.update(chunk)
934
+ else:
935
+ break
936
+ # Reset the file's position, so it can be read again later
937
+ file.seek(0)
938
+ return hasher.hexdigest()
939
+
940
+
941
+ def compute_hashes_in_directory(directory: Union[str, Path]) -> dict:
942
+ """
943
+ Computes the SHA-256 hash of all files in a directory
944
+
945
+ :param Union[str, Path] directory: Directory to compute hashes for
946
+ :return: Dictionary of hashes keyed by file path
947
+ :rtype: dict
948
+ """
949
+ file_hashes = {}
950
+ for file in os.listdir(directory):
951
+ with open(os.path.join(directory, file), "rb") as in_file:
952
+ file_hash = compute_hash(in_file)
953
+ file_hashes[file_hash] = os.path.join(directory, file)
954
+ return file_hashes
955
+
956
+
957
+ def walk_directory_for_files(directory: str, extension: str = ".ckl") -> tuple[list, int]:
958
+ """
959
+ Recursively search a directory for files with a given extension
960
+
961
+ :param str directory: Directory to search for files
962
+ :param str extension: File extension to search for, defaults to ".ckl"
963
+ :return: Tuple of list of files and total file count analyzed
964
+ :rtype: tuple[list, int]
965
+ """
966
+ desired_files = []
967
+ total_file_count = 0
968
+ for root, dirs, files in os.walk(directory):
969
+ for file in files:
970
+ total_file_count += 1
971
+ if file.endswith(extension):
972
+ file_path = os.path.join(root, file)
973
+ desired_files.append(file_path)
974
+ return desired_files, total_file_count
975
+
976
+
977
+ def detect_shell() -> str:
978
+ """
979
+ Function to detect the current shell and returns it as a string
980
+
981
+ :return: String of the current shell
982
+ :rtype: str
983
+ """
984
+ if os.name == "posix":
985
+ shell_path = os.getenv("SHELL")
986
+ if shell_path:
987
+ return os.path.basename(shell_path)
988
+ elif os.name == "nt":
989
+ try:
990
+ process = psutil.Process(os.getpid())
991
+ while process.name().lower() not in [
992
+ "cmd.exe",
993
+ "powershell.exe",
994
+ ]:
995
+ process = process.parent()
996
+ if process is None:
997
+ return "Unknown"
998
+ terminal_process = process.name()
999
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
1000
+ return "Unknown"
1001
+ if "cmd.exe" in terminal_process.lower():
1002
+ return "CMD"
1003
+ elif "powershell.exe" in terminal_process.lower():
1004
+ return "PowerShell"
1005
+ else:
1006
+ return "Unknown"
1007
+ return "Unknown"
1008
+
1009
+
1010
+ def convert_to_string(data: Union[str, list, datetime, bool, dict]) -> str:
1011
+ """
1012
+ Convert any data type to a string
1013
+
1014
+ :param Union[str, list, datetime, bool, dict] data: Data to convert to a string
1015
+ :return: Representation of the data as a string
1016
+ :rtype: str
1017
+ """
1018
+ if isinstance(data, datetime):
1019
+ return data.strftime("%b %d, %Y")
1020
+ # see if the value is a boolean
1021
+ elif isinstance(data, bool):
1022
+ return "True" if data else "False"
1023
+ elif isinstance(data, dict):
1024
+ # Convert each key and value in the dictionary to a string
1025
+ str_dict = ""
1026
+ for key, value in data.items():
1027
+ str_dict += f"{convert_to_string(key)}: {convert_to_string(value)}\n"
1028
+ return str_dict
1029
+ elif isinstance(data, list):
1030
+ # Convert each item in the list to a string
1031
+ str_list = []
1032
+ for item in data:
1033
+ str_list.append(convert_to_string(item))
1034
+ return ", ".join(str_list)
1035
+ else:
1036
+ return str(data)
1037
+
1038
+
1039
+ def remove_timezone_from_date_str(date: str) -> str:
1040
+ """
1041
+ Function to remove the timezone from a date string
1042
+
1043
+ :param str date: Date as a string to process
1044
+ :return: Clean date string
1045
+ :rtype: str
1046
+ """
1047
+ tz_pattern = r"\s[A-Za-z]{3,4}\s*\+*\d*|[+-]\d{4}"
1048
+
1049
+ # Use re.sub to remove the time zone if it exists
1050
+ clean_date = re.sub(tz_pattern, "", date).rstrip(" ")
1051
+ return clean_date
1052
+
1053
+
1054
+ def update_keys_to_lowercase_first_letter(original_dict: Dict) -> Dict:
1055
+ """
1056
+ Function to update dictionary keys to have the first letter lowercase
1057
+
1058
+ :param Dict original_dict: Dictionary to update
1059
+ :return: Dictionary with updated keys
1060
+ :rtype: Dict
1061
+ """
1062
+ updated_dict = {}
1063
+ for key, value in original_dict.items():
1064
+ # Lowercase the first letter of the key and combine it with the rest of the string
1065
+ updated_key = key[0].lower() + key[1:] if key else key
1066
+ updated_dict[updated_key] = value
1067
+ return updated_dict
1068
+
1069
+
1070
+ def remove_keys(dictionary: dict, keys_to_remove: list) -> None:
1071
+ """
1072
+ Removes specified keys from a dictionary
1073
+
1074
+ :param dict dictionary: The dictionary to remove keys from
1075
+ :param list keys_to_remove: List of keys to remove
1076
+ :rtype: None
1077
+ """
1078
+ for key in keys_to_remove:
1079
+ dictionary.pop(key, None)
1080
+
1081
+
1082
+ def log_memory() -> None:
1083
+ """
1084
+ Function to log the current memory usage in a cross-platform fashion
1085
+
1086
+ :rtype: None
1087
+ """
1088
+ logger.debug("RAM Percent Used: %i", psutil.virtual_memory()[2])
1089
+
1090
+
1091
+ def extract_vuln_id_from_strings(text: str) -> Union[list, str]:
1092
+ """
1093
+ Extract security notices and vuln id strings from a given text.
1094
+
1095
+ :param str text: The input text containing USN strings
1096
+ :return: A list of USN strings or a string
1097
+ :rtype: Union[list, str]
1098
+ """
1099
+ usn_pattern = r"USN-\d{4}-\d{1,4}"
1100
+ ghsa_pattern = r"GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}"
1101
+ cve_pattern = r"CVE-\d{4}-\d{4,7}"
1102
+ svd_pattern = r"SVD-\d{4}-\d{4,7}"
1103
+ usn = re.findall(usn_pattern, text)
1104
+ ghsa = re.findall(ghsa_pattern, text)
1105
+ cve = re.findall(cve_pattern, text)
1106
+ splunk = re.findall(svd_pattern, text)
1107
+ res = usn + ghsa + cve + splunk
1108
+ if res:
1109
+ return res # no need to save spaces
1110
+ return text