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,190 @@
1
+ # flake8: noqa
2
+ """
3
+ System Implementation is a Model Layer in the OSCAL SSP implementation model. The
4
+ model is documented at https://pages.nist.gov/OSCAL/concepts/layer/implementation/ssp/
5
+ Note that the RegScale SSP Data model collapses several NIST OSCAL model layers together
6
+ including Metadata, System Characteristics, and System Implementation.
7
+ """
8
+ from typing import Dict, List
9
+
10
+ from lxml import etree
11
+
12
+ from regscale.core.app.api import Api
13
+ from regscale.core.app.application import Application
14
+ from regscale.core.app.logz import create_logger
15
+ from regscale.integrations.public.fedramp.components import parse_ssp_components
16
+ from regscale.integrations.public.fedramp.fedramp_traversal import FedrampTraversal
17
+ from regscale.integrations.public.fedramp.inventory_items import parse_inventory_items
18
+ from regscale.integrations.public.fedramp.mappings.user import handle_user
19
+ from regscale.integrations.public.fedramp.xml_utils import update_ssp
20
+
21
+ logger = create_logger()
22
+
23
+ sc_events = [] # returned after tracking import events
24
+ SYSTEM_INFO = "System Information"
25
+ SYSTEM_IMP = "System Implementation"
26
+
27
+
28
+ def parse_user_extra_datas(trv: FedrampTraversal):
29
+ root = trv.root
30
+
31
+ ssp_updates_dict = {}
32
+ # USERS NOW
33
+ users = []
34
+ users = root.xpath(
35
+ "/ns1:system-security-plan/ns1:system-implementation/ns1:prop[@name='users-internal']/@value",
36
+ namespaces=trv.namespaces,
37
+ )
38
+
39
+ if len(users) > 0:
40
+ ssp_updates_dict["internalUsers"] = users[0]
41
+ trv.log_info(
42
+ {
43
+ "record_type": SYSTEM_INFO,
44
+ "model_layer": SYSTEM_IMP,
45
+ "event_msg": f"Recorded number of internal system users: {ssp_updates_dict['internalUsers']}",
46
+ }
47
+ )
48
+ else:
49
+ trv.log_error(
50
+ {
51
+ "record_type": SYSTEM_INFO,
52
+ "model_layer": SYSTEM_IMP,
53
+ "missing_element": "Number of Internal Users",
54
+ }
55
+ )
56
+
57
+ # USERS EXTERNAL
58
+ users = []
59
+ users = root.xpath(
60
+ "/ns1:system-security-plan/ns1:system-implementation/ns1:prop[@name='users-external']/@value",
61
+ namespaces=trv.namespaces,
62
+ )
63
+
64
+ if len(users) > 0:
65
+ ssp_updates_dict["externalUsers"] = users[0]
66
+ trv.log_info(
67
+ {
68
+ "record_type": SYSTEM_INFO,
69
+ "model_layer": SYSTEM_IMP,
70
+ "event_msg": f"Recorded number of external system users: {ssp_updates_dict['externalUsers']}",
71
+ }
72
+ )
73
+ else:
74
+ trv.log_error(
75
+ {
76
+ "record_type": SYSTEM_INFO,
77
+ "model_layer": SYSTEM_IMP,
78
+ "missing_element": "Number of External Users",
79
+ }
80
+ )
81
+
82
+ # USERS FUTURE
83
+ users = []
84
+ users = root.xpath(
85
+ "/ns1:system-security-plan/ns1:system-implementation/ns1:prop[@name='users-internal-future']/@value",
86
+ namespaces=trv.namespaces,
87
+ )
88
+
89
+ if len(users) > 0:
90
+ ssp_updates_dict["internalUsersFuture"] = users[0]
91
+ trv.log_info(
92
+ {
93
+ "record_type": SYSTEM_INFO,
94
+ "model_layer": SYSTEM_IMP,
95
+ "event_msg": f"Recorded number of future internal system users: {ssp_updates_dict['internalUsersFuture']}",
96
+ }
97
+ )
98
+ else:
99
+ trv.log_error(
100
+ {
101
+ "record_type": SYSTEM_INFO,
102
+ "model_layer": SYSTEM_IMP,
103
+ "missing_element": "Number of Internal Users (Future)",
104
+ }
105
+ )
106
+
107
+ # Users External
108
+ users = []
109
+ users = root.xpath(
110
+ "/ns1:system-security-plan/ns1:system-implementation/ns1:prop[@name='users-external']/@value",
111
+ namespaces=trv.namespaces,
112
+ )
113
+
114
+ if len(users) > 0:
115
+ ssp_updates_dict["externalUsersFuture"] = users[0]
116
+ trv.log_info(
117
+ {
118
+ "record_type": SYSTEM_INFO,
119
+ "model_layer": SYSTEM_IMP,
120
+ "event_msg": f"Recorded number of future external system users: {ssp_updates_dict['externalUsersFuture']}",
121
+ }
122
+ )
123
+ else:
124
+ trv.log_error(
125
+ {
126
+ "record_type": SYSTEM_INFO,
127
+ "model_layer": SYSTEM_IMP,
128
+ "missing_element": "Number of External Users (Future)",
129
+ }
130
+ )
131
+
132
+ if len(ssp_updates_dict) > 0:
133
+ update_ssp(ssp_updates_dict, trv.ssp_id)
134
+ else:
135
+ trv.log_error(
136
+ {
137
+ "record_type": SYSTEM_INFO,
138
+ "model_layer": SYSTEM_IMP,
139
+ "missing_element": "Number of Users",
140
+ }
141
+ )
142
+
143
+
144
+ def parse_system_implementation(trv: FedrampTraversal) -> List[Dict[str, str]]:
145
+ """Parse system implementation from OSCAL SSP XML to RegScale SSP JSON
146
+
147
+ :param FedrampTraversal trv: FedrampTraversal instance
148
+ :return: List of events
149
+ :rtype: List[Dict[str, str]]
150
+ """
151
+
152
+ # Handle:
153
+ # <system-implementation>
154
+ # <user>
155
+ handle_user(trv)
156
+
157
+ # parses Components
158
+ components_dict = parse_ssp_components(trv)
159
+ inventory_items = parse_inventory_items(trv, components_dict)
160
+
161
+ # Handle:
162
+ # <system-implementation>
163
+ # <users-internal>
164
+ # <users-external>
165
+ # <users-internal-future>
166
+ parse_user_extra_datas(trv)
167
+
168
+ # Handle:
169
+ # <system-implementation>
170
+ # <leveraged-authorization>
171
+ # parse_leveraged_auth_new(trv)
172
+
173
+ return [{"event": "System Implementation parsed successfully"}]
174
+
175
+
176
+ if __name__ == "__main__":
177
+ # Example usage
178
+ app = Application()
179
+ api = Api()
180
+ tree = etree.parse("./artifacts/AwesomeCloudSSP.xml")
181
+ root = tree.getroot()
182
+
183
+ ssp_id = 2037
184
+ namespaces = {
185
+ "oscal": "http://csrc.nist.gov/ns/oscal/1.0",
186
+ "fedramp": "https://fedramp.gov/ns/oscal",
187
+ "ns1": "http://csrc.nist.gov/ns/oscal/1.0",
188
+ }
189
+ trv = FedrampTraversal(api=api, root=root, ssp_id=ssp_id, namespaces=namespaces)
190
+ parse_system_implementation(trv)
@@ -0,0 +1,87 @@
1
+ """XML Utility functions"""
2
+
3
+ from urllib.parse import urljoin
4
+
5
+ from lxml import etree
6
+
7
+ from regscale.core.app.api import Api
8
+ from regscale.core.app.application import Application
9
+ from regscale.core.app.logz import create_logger
10
+
11
+
12
+ def extract_markup_content(element: etree._Element) -> str:
13
+ """
14
+ Extract the text content from an XML element, including text content from child elements
15
+
16
+ :param etree._Element element: The XML element to extract text content from
17
+ :return: String of text content
18
+ :rtype: str
19
+ """
20
+ # List of tags to look for in the content
21
+ markup_tags = [
22
+ "p",
23
+ "h1",
24
+ "h2",
25
+ "h3",
26
+ "h4",
27
+ "h5",
28
+ "h6",
29
+ "ol",
30
+ "ul",
31
+ "pre",
32
+ "table",
33
+ "li",
34
+ ]
35
+ # Initialize a list to store the extracted text content
36
+ content = []
37
+ # Iterate through all the child elements, including text and specific tags
38
+ for child in element.iter():
39
+ # Extract the local name (i.e., the tag name without the namespace) of each child element
40
+ tag_without_namespace = etree.QName(child.tag).localname
41
+ # If the child's tag is one of the specified markup tags, add its text content
42
+ if tag_without_namespace in markup_tags and child.text:
43
+ content.append(child.text.strip())
44
+ # If the child has a tail (text following a tag), add it as well
45
+ if child.tail and child.tail.strip():
46
+ content.append(child.tail.strip())
47
+ # Join the content with newlines and return
48
+ return "\n".join(content).replace(" ", " ").replace(" ", " ")
49
+
50
+
51
+ # Joshua you may or may not agree with putting here. Expect I may need to update SSP from different places. We can talk!
52
+ def update_ssp(ssp_updates_dict: dict, ssp_id: int) -> None:
53
+ """
54
+ This function will attempt to PUT any key-value pairs found in the dict as updates to an existing SSP in RegScale
55
+ It assumes that each key corresponds to a valid SSP field. First retrieves existing SSP using ID, then overwrites
56
+ fields as found in updates before submitting as a PUT to update the record.
57
+
58
+ :param dict ssp_updates_dict: Object to update SSP with
59
+ :param int ssp_id: SSP ID to update in RegScale
60
+ :rtype: None
61
+ """
62
+ app = Application()
63
+ api = Api()
64
+ config = app.config
65
+ logger = create_logger()
66
+ headers = {"accept": "*/*", "Authorization": config["token"]}
67
+ headers_json = {
68
+ "accept": "*/*",
69
+ "Content-Type": "application/json-patch+json",
70
+ "Authorization": config["token"],
71
+ }
72
+
73
+ response = api.get(url=urljoin(config["domain"], f"/api/securityplans/{ssp_id}"), headers=headers)
74
+ if response.ok:
75
+ existing_ssp = response.json()
76
+ for key, value in ssp_updates_dict.items():
77
+ existing_ssp[key] = value
78
+ ssp_json = existing_ssp
79
+ response = api.put(
80
+ url=urljoin(config["domain"], f"/api/securityplans/{ssp_id}"),
81
+ json=ssp_json,
82
+ headers=headers_json,
83
+ )
84
+ if response.ok:
85
+ logger.info(f"Successfully updated SSP {ssp_id} with additional data.")
86
+ else:
87
+ logger.error("Problems updating SSP with latest additional data.")
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Module to allow sorting nist catalog controls into RegScale"""
4
+
5
+ # standard python imports
6
+ import re
7
+ from typing import Tuple, Any, Union, TYPE_CHECKING
8
+
9
+ from pathlib import Path
10
+
11
+ if TYPE_CHECKING:
12
+ from regscale.core.app.application import Application
13
+ from regscale.core.app.api import Api
14
+
15
+ import click
16
+ from requests import JSONDecodeError, Response
17
+ from regscale.core.app.api import normalize_url
18
+
19
+ from regscale.core.app.utils.app_utils import (
20
+ create_logger,
21
+ error_and_exit,
22
+ check_file_path,
23
+ save_data_to,
24
+ create_progress_object,
25
+ )
26
+ from regscale.utils.threading.threadhandler import create_threads, thread_assignment
27
+
28
+ # initialize Application and Api objects
29
+
30
+ logger = create_logger()
31
+ job_progress = create_progress_object()
32
+
33
+ # create global variables for threads to store successful
34
+ # and failed control updates
35
+ updated_controls, failed_controls, retry_failed, retry_success = [], [], [], []
36
+
37
+
38
+ @click.group()
39
+ def nist():
40
+ """Sort the controls of a catalog in RegScale."""
41
+
42
+
43
+ @nist.command(name="sort_control_ids")
44
+ @click.option(
45
+ "--catalog_id",
46
+ type=click.INT,
47
+ help="The RegScale catalog ID number.",
48
+ prompt="RegScale catalog ID#",
49
+ required=True,
50
+ )
51
+ def sort_control_ids(catalog_id: int) -> None:
52
+ """Sort the provided catalog's controls in RegScale with the provided ID #."""
53
+ sort_controls_by_id(catalog_id)
54
+
55
+
56
+ def sort_controls_by_id(catalog_id: int) -> None:
57
+ """
58
+ Sort the provided catalog's controls in RegScale with the provided ID #
59
+
60
+ :param int catalog_id: ID # of the catalog in RegScale to sort controls for
61
+ :rtype: None
62
+ """
63
+ from regscale.core.app.application import Application
64
+ from regscale.core.app.api import Api
65
+
66
+ app = Application()
67
+ api = Api()
68
+ config = app.config
69
+ # update api limits depending on maxThreads
70
+ api.pool_connections = max(api.pool_connections, config["maxThreads"])
71
+ api.pool_maxsize = max(api.pool_maxsize, config["maxThreads"])
72
+ security_control_count: int = 0
73
+
74
+ # get all controls by catalog
75
+ url_controls_get_all = f"{app.config['domain']}/api/SecurityControls/getAllByCatalog/{catalog_id}"
76
+
77
+ # get all existing control implementations
78
+ security_control_res = api.get(url_controls_get_all)
79
+ security_control_data = None
80
+ try:
81
+ # try to convert the response to a JSON object
82
+ security_control_data = security_control_res.json()
83
+ security_control_count = len(security_control_data)
84
+ except JSONDecodeError:
85
+ error_and_exit("Unable to retrieve control implementations for this SSP in RegScale.")
86
+
87
+ # output the RegScale controls, if there are any, else exit
88
+ if security_control_count == 0 or not security_control_data:
89
+ # generate URL to the provided catalog id
90
+ catalog_url = normalize_url(f'{app.config["domain"]}/form/catalogues/{catalog_id}')
91
+ error_and_exit(f"No controls were received for catalog #{catalog_id}.\nPlease verify: {catalog_url}")
92
+ # verify artifacts directory exists before saving the received security controls
93
+ check_file_path("artifacts")
94
+ save_data_to(
95
+ file=Path(f"./artifacts/regscale-catalog-{catalog_id}-controls.json"),
96
+ data=security_control_data,
97
+ )
98
+
99
+ # loop over the controls and add a sortId to each control
100
+ sorted_controls: list = []
101
+ for control in security_control_data:
102
+ control["sortId"] = parse_control_id(control)
103
+ sorted_controls.append(control["sortId"])
104
+
105
+ # output the RegScale controls
106
+ save_data_to(
107
+ file=Path(f"artifacts/catalog-{catalog_id}-sorted-control-ids.json"),
108
+ data=sorted_controls,
109
+ )
110
+
111
+ # create threads to process all controls
112
+ with job_progress:
113
+ logger.info(
114
+ "%s security control(s) will be updated.",
115
+ security_control_count,
116
+ )
117
+ # create progress bar and update the controls in RegScale
118
+ updating_controls = job_progress.add_task(
119
+ f"[#f8b737]Updating {security_control_count} security control(s)...",
120
+ total=security_control_count,
121
+ )
122
+ create_threads(
123
+ process=update_security_controls,
124
+ args=(security_control_data, api, updating_controls, False),
125
+ thread_count=security_control_count,
126
+ )
127
+ # output the result
128
+ logger.info(
129
+ "Updated %s/%s control(s) successfully with %s failure(s).",
130
+ security_control_count,
131
+ len(updated_controls),
132
+ len(failed_controls),
133
+ )
134
+ # check if any controls need to be retried
135
+ if failed_controls:
136
+ save_data_to(file=Path("./artifacts/failed-controls.json"), data=failed_controls)
137
+ with job_progress:
138
+ logger.info(
139
+ "%s security control(s) will be updated.",
140
+ security_control_count,
141
+ )
142
+ # create progress bar and retry the failed controls
143
+ retrying_controls = job_progress.add_task(
144
+ f"[#ffff00]Retrying {len(failed_controls)} failed security control(s)...",
145
+ total=len(failed_controls),
146
+ )
147
+ create_threads(
148
+ process=update_security_controls,
149
+ args=(failed_controls, api, retrying_controls, True),
150
+ thread_count=len(failed_controls),
151
+ )
152
+ logger.info("%i/%i retrie(s) were successful.", len(retry_success), len(failed_controls))
153
+ save_data_to(
154
+ file=Path("./artifacts/retry-successful-controls.json"),
155
+ data=retry_success,
156
+ )
157
+ if retry_failed:
158
+ logger.info("%i failed retrie(s)", len(retry_failed))
159
+ save_data_to(file=Path("./artifacts/retry-failed-controls.json"), data=retry_failed)
160
+
161
+
162
+ def update_security_controls(args: Tuple, thread: int) -> None:
163
+ """
164
+ Function to utilize threading and update security controls in RegScale
165
+
166
+ :param Tuple args: Tuple of args to use during the process
167
+ :param int thread: Thread number of current thread
168
+ :rtype: None
169
+ """
170
+ # set up local variables from args passed
171
+ security_control_data, api, task, retry = args
172
+
173
+ # find which records should be executed by the current thread
174
+ threads = thread_assignment(thread=thread, total_items=len(security_control_data))
175
+
176
+ # iterate through the thread assignment items and process them
177
+ for i in range(len(threads)):
178
+ # set the control for the thread & update it in RegScale
179
+ control = security_control_data[threads[i]]
180
+ control_url = f'{api.config["domain"]}/api/SecurityControls/{control["id"]}'
181
+ # check if the description is populated
182
+ control["description"] = control.get("description", control.get("title"))
183
+ # update control in RegScale
184
+ response = api.put(control_url, json=control)
185
+ # verify update was successful
186
+ append_to_list(response, retry, control)
187
+ # update progress bar
188
+ job_progress.update(task, advance=1)
189
+
190
+
191
+ def append_to_list(response: Response, retry: bool, control: dict) -> None:
192
+ """
193
+ Function to append the control to the correct list based if it passed and if it was a retry
194
+
195
+ :param Response response: The response from the update API call to RegScale
196
+ :param bool retry: Whether this was an attempted retry for a control update in RegScale
197
+ :param dict control: The control data to append to the correct list
198
+ :rtype: None
199
+ """
200
+ if response.ok:
201
+ logger.debug("Success: control #%s was updated successfully.", control["sortId"])
202
+ if retry:
203
+ retry_success.append(control)
204
+ else:
205
+ updated_controls.append(control)
206
+ else:
207
+ logger.debug(
208
+ "Error: unable to update control #%s\n%s: %s",
209
+ control["sortId"],
210
+ response.status_code,
211
+ response.text,
212
+ )
213
+ if retry:
214
+ retry_failed.append(control)
215
+ else:
216
+ failed_controls.append(control)
217
+
218
+
219
+ def parse_control_id(control: Union[dict, str]) -> str:
220
+ """
221
+ Function to parse the provided control dictionary from RegScale and returns a sortId as a string
222
+
223
+ :param Union[dict, str] control: A control from RegScale or a control string
224
+ :raises KeyError: If the control doesn't have a sortId or controlId
225
+ :return: string to use as a sortId
226
+ :rtype: str
227
+ """
228
+
229
+ def _pad_zeros(match: Any) -> str:
230
+ """
231
+ Function to pad zeros to the control's sortId if needed
232
+
233
+ :param Any match: Match object from the regex
234
+ :return: string to use as a sortId
235
+ :rtype: str
236
+ """
237
+ prefix = match.group(1)
238
+ digits = match.group(2)
239
+ return prefix + digits.zfill(2) if len(digits) == 1 else prefix + digits
240
+
241
+ def _extract_id(control: Union[dict, str]) -> str:
242
+ """
243
+ Extracts the ID from the control, handling both string and dictionary types.
244
+
245
+ :param Union[dict, str] control: A control from RegScale or a control string
246
+ :return: ID of the control
247
+ :rtype: str
248
+ """
249
+ if isinstance(control, str):
250
+ return control
251
+ try:
252
+ return control["sortId"]
253
+ except KeyError:
254
+ return control.get("controlId", "")
255
+
256
+ def _format_id(original_id: str, control: dict) -> str:
257
+ """
258
+ Formats the ID by removing leading zeros in specific patterns.
259
+
260
+ :param str original_id: Original ID of the control
261
+ :param dict control: Control data
262
+ :return: Formatted ID of the control
263
+ :rtype: str
264
+ """
265
+ formatted_id = re.sub(r"(-|\.)0*(\d+)", _pad_zeros, original_id)
266
+ if isinstance(control, dict):
267
+ expected_control = re.sub(r"(?<=-)(0+)(?=\d)", "", control["title"].split(" ")[0])
268
+ if expected_control not in formatted_id:
269
+ return control["title"].split(" ")[0]
270
+ return formatted_id
271
+
272
+ original_id = _extract_id(control)
273
+ if original_id == "":
274
+ raise KeyError("Control ID is missing.")
275
+ return _format_id(original_id, control)