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,522 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """A class to import Fedramp V4 and V5 POAMs"""
4
+ import logging
5
+ import re
6
+ from collections import Counter
7
+ from typing import Optional, Union
8
+
9
+ import rich.progress
10
+ from openpyxl import Workbook, load_workbook # type: ignore
11
+ from openpyxl.compat import safe_string
12
+ from openpyxl.utils import column_index_from_string # type: ignore
13
+ from openpyxl.utils.exceptions import InvalidFileException # type: ignore
14
+ from pathlib import Path
15
+
16
+ from regscale.core.app.utils.app_utils import create_progress_object, get_current_datetime
17
+ from regscale.core.utils.date import date_str, datetime_str
18
+ from regscale.integrations.integration.issue import IntegrationIssue
19
+ from regscale.integrations.scanner_integration import issue_due_date
20
+ from regscale.integrations.variables import ScannerVariables
21
+ from regscale.models import IssueSeverity, regscale_models
22
+
23
+ logger = logging.getLogger("regscale")
24
+
25
+
26
+ class POAM(IntegrationIssue):
27
+ """
28
+ Custom Integration issue class
29
+ """
30
+
31
+ def __init__(self, file_path: str, module: str, module_id: int, poam_id_header: str = "POAM ID"):
32
+ super().__init__()
33
+ self.skipped_records = 0
34
+ self.blank_records = 0
35
+ self.blank_threshold = 3
36
+ self.error_records = 0
37
+ self.progress = create_progress_object()
38
+ if not file_path:
39
+ raise ValueError("File path is required")
40
+ self.file_path = Path(file_path)
41
+ self.module = module
42
+ self.module_id = module_id
43
+ self.poam_id_header = poam_id_header
44
+ self.poam_data: dict[str, regscale_models.Issue] = {}
45
+ data = self.import_poam()
46
+ self.data = data
47
+
48
+ self.create_or_update_issues(
49
+ issues=list(self.poam_data.values()), parent_id=self.module_id, parent_module=self.module
50
+ )
51
+ logger.info("Finished importing POAMs..")
52
+
53
+ def create_or_update_issues(
54
+ self,
55
+ issues: list[regscale_models.Issue],
56
+ parent_id: int,
57
+ parent_module: str,
58
+ ):
59
+ """
60
+ Create issues in RegScale
61
+
62
+ :param list[Issue] issues: list of issues to create or update
63
+ :param int parent_id: parent id
64
+ :param str parent_module: parent module
65
+ """
66
+ with self.progress as progress:
67
+ issue_task = progress.add_task(
68
+ "[#f8b737]Determining if issues need to be updated or created...", total=len(issues)
69
+ )
70
+ issue_updates = []
71
+ issue_creations = []
72
+ for issue in issues:
73
+ issue.parentId = parent_id
74
+ issue.parentModule = parent_module
75
+ if issue.id != 0:
76
+ issue_updates.append(issue)
77
+ else:
78
+ issue_creations.append(issue)
79
+ progress.update(issue_task, advance=1)
80
+ if issue_creations:
81
+ regscale_models.Issue.batch_create(issue_creations, self.progress)
82
+ if issue_updates:
83
+ regscale_models.Issue.batch_update(issue_updates, self.progress)
84
+
85
+ def pull(self):
86
+ """
87
+ Pull inventory from an Integration platform into RegScale
88
+ """
89
+ # Implement the pull method here
90
+ pass
91
+
92
+ def file_type(self):
93
+ """
94
+ A method to return the file type
95
+ """
96
+ file_type = None
97
+ if self.file_path:
98
+ file_type = self.file_path.suffix
99
+ return file_type
100
+
101
+ @staticmethod
102
+ def get_index_from_column_name(column_name: str) -> int:
103
+ """
104
+ A method to get the index from a column name
105
+
106
+ :param str column_name: A column name
107
+ :return: The index of the column
108
+ :rtype: int
109
+ """
110
+ return column_index_from_string(column_name) - 1
111
+
112
+ def get_row_val(self, row: tuple, column_name: str) -> Optional[str]:
113
+ """
114
+ Get the value from the row
115
+
116
+ :param tuple row: The row
117
+ :param str column_name: The column name
118
+ :return: The value or None
119
+ :rtype: Optional[str]
120
+ """
121
+ try:
122
+ index = self.get_index_from_column_name(column_name)
123
+ return row[index] if index < len(row) else None
124
+ except Exception as e:
125
+ logger.error(f"Error getting value for column {column_name}: {str(e)}")
126
+ return None
127
+
128
+ def get_basis_for_adjustment(self, row: tuple) -> Optional[str]:
129
+ """
130
+ Get the basis for adjustment
131
+
132
+ :param tuple row: The row
133
+ :return: The basis for adjustment or None if adjusted risk rating is the same as risk rating
134
+ :rtype: Optional[str]
135
+ """
136
+ basis_for_adjustment = self.empty(row[23])
137
+ risk_rating = self.get_row_val(row, "S")
138
+ adjusted_risk_rating = self.get_row_val(row, "T")
139
+ if (adjusted_risk_rating != risk_rating) and not basis_for_adjustment:
140
+ return "POAM Import"
141
+ if adjusted_risk_rating == risk_rating:
142
+ return None
143
+ return basis_for_adjustment
144
+
145
+ def process_cve(self, cve: Optional[str], index: int, sheet: str) -> Optional[str]:
146
+ """
147
+ Process and validate CVE string.
148
+
149
+ :param Optional[str] cve: The CVE string to process
150
+ :param int index: The row index for logging purposes
151
+ :param str sheet: The sheet name for logging purposes
152
+ :return: Processed CVE string or None
153
+ :rtype: Optional[str]
154
+ """
155
+ cve = self.empty(cve)
156
+ if not cve:
157
+ return None
158
+
159
+ cve_pattern = r".*CVE-\d{4}-\d{4,7}.*"
160
+ match = re.match(cve_pattern, cve, re.IGNORECASE)
161
+ if match:
162
+ cve_match = re.search(r"CVE-\d{4}-\d{4,7}", cve, re.IGNORECASE)
163
+ if cve_match:
164
+ return cve_match.group(0).upper() # Ensure consistent formatting
165
+ return None # No CVE found within the matching string
166
+ else:
167
+ logger.warning(f"Invalid CVE format: {cve} on row {index}, sheet {sheet}. Setting to empty string.")
168
+ return ""
169
+
170
+ def gen_issue_from_row(
171
+ self, row: tuple, status: str, category: str, index: int, sheet: str
172
+ ) -> Optional[regscale_models.Issue]:
173
+ """
174
+ Generate an Issue object from a row in the POAM spreadsheet.
175
+
176
+ :param tuple row: A row from the POAM spreadsheet
177
+ :param str status: The status of the issue (Open or Closed)
178
+ :param str category: The category of the issue
179
+ :param int index: The index of the row in the spreadsheet
180
+ :param str sheet: The name of the sheet being processed
181
+ :return: An Issue object if successfully generated, None otherwise
182
+ :rtype: Optional[Issue]
183
+ """
184
+ # Extract and validate key fields
185
+ poam_id = self.get_row_val(row, "A")
186
+ weakness_name = str(self.get_row_val(row, "C"))
187
+
188
+ if not poam_id or not poam_id.upper():
189
+ logger.warning(f"Invalid POAM ID on row {index}, sheet {sheet}. Skipping.")
190
+ return None
191
+ if not weakness_name:
192
+ logger.warning(f"Title is required on row {index}, sheet {sheet}. Unable to import")
193
+ return None
194
+
195
+ # Process risk ratings and adjustments
196
+ original_risk_rating = self.empty(self.get_row_val(row, "S"))
197
+ adjusted_risk_rating = self.get_row_val(row, "T")
198
+ adjusted_risk_rating = original_risk_rating or "N/A" if adjusted_risk_rating == "N/A" else adjusted_risk_rating
199
+
200
+ # Process CVE
201
+ cve = self.process_cve(self.get_row_val(row, "AD"), index, sheet)
202
+
203
+ # Determine severity level
204
+ severity_level = getattr(IssueSeverity, category.title(), IssueSeverity.NotAssigned)
205
+
206
+ # Process dates
207
+ date_created = date_str(self.get_row_val(row, "K"))
208
+ date_last_updated = datetime_str(self.get_row_val(row, "O"))
209
+ due_date = self.get_row_val(row, "L")
210
+ if due_date == "#REF!":
211
+ due_date = ""
212
+ due_date = date_str(due_date) or issue_due_date(severity_level, date_created, high=30, moderate=90, low=364)
213
+ date_completed = None
214
+ # Create and return the Issue object
215
+ try:
216
+ if status == "Closed":
217
+ date_completed = date_str(date_last_updated) or due_date or get_current_datetime()
218
+
219
+ issue: regscale_models.Issue = regscale_models.Issue(
220
+ integrationFindingId=poam_id,
221
+ otherIdentifier=poam_id,
222
+ dateCreated=date_created,
223
+ dateLastUpdated=date_last_updated,
224
+ title=weakness_name[:255],
225
+ description=self.get_row_val(row, "D"),
226
+ status=status,
227
+ severityLevel=severity_level,
228
+ assetIdentifier=self.get_row_val(row, "G"),
229
+ isPoam=True,
230
+ issueOwnerId=ScannerVariables.userId,
231
+ securityPlanId=self.module_id if self.module == "securityplans" else 0,
232
+ cve=cve,
233
+ sourceReport=self.get_row_val(row, "E"),
234
+ pluginId=str(self.get_row_val(row, "F")),
235
+ autoApproved="No",
236
+ dueDate=due_date,
237
+ parentId=self.module_id, # type: ignore
238
+ parentModule=self.module, # type: ignore
239
+ basisForAdjustment=self.get_basis_for_adjustment(row),
240
+ dateCompleted=date_completed, # when an issue is closed it has to have a date completed cannot be null
241
+ manualDetectionSource=self.get_row_val(row, "E"),
242
+ manualDetectionId=str(self.get_row_val(row, "F")),
243
+ changes=safe_string(self.get_row_val(row, "N")),
244
+ poamComments=self.empty(self.get_row_val(row, "Z")),
245
+ deviationRationale=self.empty(self.get_row_val(row, "X")),
246
+ remediationDescription=self.empty(self.get_row_val(row, "J")),
247
+ vendorDependency=self.empty(self.get_row_val(row, "P")),
248
+ vendorLastUpdate=self.empty(date_str(self.get_row_val(row, "Q"))),
249
+ vendorName=self.empty(self.get_row_val(row, "R")),
250
+ adjustedRiskRating=adjusted_risk_rating,
251
+ originalRiskRating=original_risk_rating or adjusted_risk_rating,
252
+ falsePositive=self.set_false_positive(row),
253
+ identification="Vulnerability Assessment",
254
+ operationalRequirement=self.set_operational_requirement(row),
255
+ dateFirstDetected=date_str(self.get_row_val(row, "K")),
256
+ riskAdjustment=self.set_risk_adjustment(row),
257
+ ).create_or_update(bulk_update=True)
258
+ if poc := self.get_row_val(row, "H"):
259
+ _ = regscale_models.Property(
260
+ key="POC",
261
+ value=poc,
262
+ parentId=issue.id,
263
+ parentModule="issues",
264
+ ).create_or_update(bulk_update=True, bulk_create=True)
265
+ except Exception as e:
266
+ logger.error(f"Error creating Issue object on row {index}, sheet {sheet}: {str(e)}", exc_info=True)
267
+ return None
268
+
269
+ self.poam_data[poam_id] = issue
270
+ return issue
271
+
272
+ def import_poam(self) -> Optional[Workbook]:
273
+ """
274
+ Import POAM data from the workbook.
275
+
276
+ :return: The processed workbook or None if import failed
277
+ :rtype: Optional[Workbook]
278
+ """
279
+ try:
280
+ workbook = load_workbook(filename=self.file_path, data_only=True, read_only=True)
281
+ except (FileNotFoundError, InvalidFileException) as e:
282
+ logger.error(f"Failed to load workbook: {e}")
283
+ return None
284
+
285
+ poam_sheets = [sheet for sheet in workbook.sheetnames if re.search("POA&M Items", sheet)]
286
+
287
+ with self.progress as progress:
288
+ parsing_progress = progress.add_task("[#f8b737]Parsing data from workbook...", total=len(poam_sheets))
289
+
290
+ for sheet in poam_sheets:
291
+ self.process_sheet(workbook[sheet], sheet, progress)
292
+ progress.update(parsing_progress, advance=1)
293
+
294
+ self.count_issues_by_status()
295
+ return workbook
296
+
297
+ def process_sheet(self, ws, sheet_name: str, progress: rich.progress.Progress):
298
+ """
299
+ Process a single sheet in the POAM workbook.
300
+
301
+ :param ws: The worksheet object
302
+ :param str sheet_name: The name of the sheet
303
+ :param rich.progress.Progress progress: The progress object for updating task progress
304
+ """
305
+ category = ws["C3"].value or "Low"
306
+ if not ws["C3"].value:
307
+ logger.warning(f"Category is required in cell C3. Defaulting to Low import for sheet {sheet_name}.")
308
+ if not category:
309
+ logger.warning(f"Category is required in cell C3. Skipping import for sheet {sheet_name}.")
310
+ return
311
+
312
+ status = self.determine_status(sheet_name)
313
+ if status is None:
314
+ logger.warning(f"Unable to determine POA&M status for sheet {sheet_name}. Skipping import.")
315
+ return
316
+
317
+ start_row = self.find_start_row(ws)
318
+ if start_row is None:
319
+ logger.warning(f"No POAM entries found in sheet {sheet_name}. Skipping.")
320
+ return
321
+
322
+ parsing_poams = progress.add_task(
323
+ f"[#ef5d23]Parsing '{sheet_name}' sheet for POAMs...", total=ws.max_row - start_row + 1
324
+ )
325
+
326
+ for index, row in enumerate(ws.iter_rows(min_row=start_row, values_only=True), start_row):
327
+ try:
328
+ self.process_row(row, status, category, index, sheet_name)
329
+ if self.blank_records >= self.blank_threshold:
330
+ logger.warning("Too many empty records skipped. Stopping import.")
331
+ progress.update(parsing_poams, completed=ws.max_row - start_row + 1)
332
+ break
333
+ except Exception as e:
334
+ logger.error(f"Error processing row {index} in sheet {sheet_name}: {str(e)}")
335
+ self.error_records += 1
336
+ progress.update(parsing_poams, advance=1)
337
+ regscale_models.Issue.bulk_save(progress_context=progress)
338
+ regscale_models.Property.bulk_save(progress_context=progress)
339
+
340
+ def find_start_row(self, ws) -> Optional[int]:
341
+ """
342
+ Find the first row with 'V-' or any identifier-number in column A.
343
+
344
+ :param ws: The worksheet object
345
+ :return: The row number where POAM entries start, or None if not found
346
+ :rtype: Optional[int]
347
+ """
348
+ for row_index, row in enumerate(ws.iter_rows(min_row=1, max_col=1, values_only=True), 1):
349
+ if row[0] and self.poam_id_header in str(row[0]):
350
+ logger.info(f"Found POAM header parsing data from row {row_index + 1}")
351
+ return row_index + 1
352
+ return None
353
+
354
+ @staticmethod
355
+ def identify_id_data(value: str) -> bool:
356
+ """
357
+ Identify the ID
358
+
359
+ :param str value: The value
360
+ :return: The ID
361
+ :rtype: bool
362
+ """
363
+ return bool(re.match(r".+-\d+$", value))
364
+
365
+ @staticmethod
366
+ def determine_status(sheet: str) -> Optional[str]:
367
+ """
368
+ Determine the status based on sheet name.
369
+
370
+ :param str sheet: The name of the sheet
371
+ :return: The status of the POA&M (Closed, Open, or None)
372
+ :rtype: Optional[str]
373
+ """
374
+ # Check if the sheet name contains 'closed' (case-insensitive)
375
+ if "closed" in sheet.lower():
376
+ return "Closed"
377
+ # Check if the sheet name contains 'open' (case-insensitive)
378
+ elif "open" in sheet.lower():
379
+ return "Open"
380
+ # If neither 'closed' nor 'open' is found in the sheet name
381
+ else:
382
+ # Log a warning message
383
+ logger.debug(f"Unable to determine POA&M status for sheet {sheet}. Skipping import.")
384
+ # Return None to indicate that the status couldn't be determined
385
+ return None
386
+
387
+ def process_row(self, row: tuple, status: str, category: str, index: int, sheet: str):
388
+ """
389
+ Process a single row of the POAM sheet.
390
+
391
+ :param tuple row: The row data from the POAM sheet
392
+ :param str status: The status of the POAM (Open or Closed)
393
+ :param str category: The category of the POAM
394
+ :param int index: The index of the current row
395
+ :param str sheet: The name of the current sheet
396
+ """
397
+ try:
398
+ # Get the POAM ID from column A and handle empty values
399
+ poam_id = self.empty(self.get_row_val(row, "A"))
400
+
401
+ # Check if POAM ID is missing
402
+ if not poam_id:
403
+ logger.warning(f"POAM ID is required. Skipping import for row {index} in sheet {sheet}.")
404
+ self.blank_records += 1
405
+ return
406
+ self.blank_records = 0
407
+ # Check if closed POAM already exists in the data
408
+ if status == "Closed" and poam_id in self.poam_data:
409
+ logger.warning(
410
+ f"POAM {poam_id} already exists with status {status}. Skipping import for row {index} in sheet {sheet}."
411
+ )
412
+ self.skipped_records += 1
413
+ return
414
+
415
+ # Generate issue from row data
416
+ issue = self.gen_issue_from_row(row, status, category, index, sheet)
417
+ if issue:
418
+ # Add the generated issue to the POAM data dictionary
419
+ self.poam_data[poam_id] = issue
420
+ else:
421
+ logger.warning(f"Failed to generate issue for POAM {poam_id} from row {index} in sheet {sheet}")
422
+ self.skipped_records += 151
423
+ except Exception as e:
424
+ logger.error(f"Error in process_row for row {index} in sheet {sheet}: {str(e)}")
425
+ self.error_records += 1
426
+
427
+ def count_issues_by_status(self):
428
+ """
429
+ A method to count the issues and log the counts.
430
+ """
431
+ status_list = [issue.status for issue in self.poam_data.values() if issue]
432
+ status_counts = Counter(status_list)
433
+ logger.info(
434
+ "Found %i issues in the POAM Workbook, %i Open and %i Closed.",
435
+ len(self.poam_data),
436
+ status_counts["Open"],
437
+ status_counts["Closed"],
438
+ )
439
+ error_msg = f"Skipped {self.skipped_records} records, {self.error_records} errors"
440
+ if self.error_records:
441
+ logger.error(error_msg)
442
+ elif self.skipped_records:
443
+ logger.warning(error_msg)
444
+ else:
445
+ logger.info(error_msg)
446
+
447
+ @staticmethod
448
+ def empty(string: Optional[str]) -> Union[str, None]:
449
+ """
450
+ A method to empty the data
451
+
452
+ :param str string: A string
453
+ :return: None if the string is 'None' or the input is not a string
454
+ :rtype: Union[str, None]
455
+ """
456
+ if not isinstance(string, str):
457
+ return None
458
+
459
+ if string.lower() in ["none", "n/a"]:
460
+ return None
461
+
462
+ return string
463
+
464
+ def set_false_positive(self, row: tuple) -> str:
465
+ """
466
+ Set the false positive value
467
+
468
+ :param tuple row: The row
469
+ :return: The false positive value
470
+ :rtype: str
471
+ """
472
+ # Map lowercased values to their corresponding responses
473
+ value_map = {"yes": "Yes", "no": "No", "pending": "Pending Review"}
474
+
475
+ # Get the value from the row and convert it to lowercase
476
+ if row_value := self.get_row_val_str(row, "V"):
477
+ row_value = row_value.lower()
478
+
479
+ # Get the corresponding response from the map, default to 'No' if not found
480
+ return value_map.get(row_value, "No") if row_value else "No"
481
+
482
+ def set_operational_requirement(self, row: tuple) -> str:
483
+ """
484
+ Set the operational requirement value
485
+
486
+ :param tuple row: The row
487
+ :return: The operational requirement value
488
+ :rtype: str
489
+ """
490
+ # Map lowercased values to their corresponding responses
491
+ value_map = {"yes": "Yes", "no": "No", "pending": "Pending"}
492
+
493
+ # Get the value from the row and convert it to lowercase
494
+ if row_value := self.get_row_val_str(row, "W"):
495
+ row_value = row_value.lower()
496
+
497
+ # Get the corresponding response from the map, default to No if not found
498
+ return value_map.get(row_value, "No") if row_value else "No"
499
+
500
+ def set_risk_adjustment(self, row: tuple) -> str:
501
+ """
502
+ Set the risk adjustment value
503
+
504
+ :param tuple row: The row
505
+ :return: The Risk adjustment string
506
+ :rtype: str
507
+ """
508
+ value_map = {"yes": "Yes", "no": "No", "pending": "Pending"}
509
+ if row_value := self.get_row_val_str(row, "U"):
510
+ row_value = row_value.lower()
511
+ return value_map.get(row_value, "No") if row_value else "No"
512
+
513
+ def get_row_val_str(self, row: tuple, column_name: str) -> str:
514
+ """
515
+ Get the safe string
516
+
517
+ :param tuple row: The row
518
+ :param str column_name: The column name
519
+ :return: The safe string
520
+ :rtype: str
521
+ """
522
+ return safe_string(self.get_row_val(row, column_name))
@@ -0,0 +1,107 @@
1
+ import json
2
+ from typing import List, Optional, Tuple
3
+
4
+ from regscale.core.decorators import singleton
5
+
6
+
7
+ @singleton
8
+ class PartMapper:
9
+ """
10
+ PartMapper class Standardized approach to mapping identifiers between control id in FedRAMP and other frameworks
11
+
12
+ # Example usage
13
+ mapper = PartMapper()
14
+ mapper.load_fedramp_version_5_mapping() or mapper.load_json_from_file("path/to/fedramp_r5_parts.json")
15
+ control_label_and_part_results = mapper.find_by_control_label_and_part("AC-1", "a1")
16
+ oscal_control_id_and_part_results = mapper.find_by_oscal_control_id_and_part("ac-1", "a1")
17
+ print("Results for control label 'AC-1' and part 'a1':", control_label_and_part_results)
18
+ print("Results for OSCAL control ID 'ac-1' and part 'a1':", oscal_control_id_and_part_results)
19
+ """
20
+
21
+ def __init__(self):
22
+ self.data = []
23
+
24
+ def find_by_source(self, source: str) -> Optional[str]:
25
+ """
26
+ Find a mapping by source.
27
+ :param str source: The source.
28
+ :return: A str of the oscal part identifier or null.
29
+ :rtype: Optional[str]
30
+ """
31
+ result = None
32
+ for item in self.data:
33
+ if str(item.get("SOURCE")).strip() == source:
34
+ result = item.get("OSCAL_PART_IDENTIFIER")
35
+ return result
36
+ return result
37
+
38
+ def find_sub_parts(self, source: str) -> List[str]:
39
+ """
40
+ Find a mapping by source.
41
+ :param str source: The source.
42
+ :return: A list of sub-parts.
43
+ :rtype: List[str]
44
+ """
45
+ result = []
46
+ for item in self.data:
47
+ if str(item.get("SOURCE")).strip() == source:
48
+ parts = item.get("SUB_PARTS", [])
49
+ return parts
50
+ return result
51
+
52
+ def load_json_from_file(self, json_file: str):
53
+ """
54
+ Load json from a file
55
+ :param str json_file: string name of a file
56
+ """
57
+ with open(json_file) as jf:
58
+ parsed_json = json.load(jf)
59
+ self.data = parsed_json
60
+
61
+ def load_fedramp_version_5_mapping(self):
62
+ """
63
+ Load FedRAMP version 5 mapping
64
+ """
65
+ from importlib.resources import path as resource_path
66
+
67
+ with resource_path("regscale.integrations.public.fedramp.mappings", "fedramp_r5_parts.json") as json_file_path:
68
+ self.load_json_from_file(json_file_path.__str__())
69
+
70
+ def load_fedramp_version_4_mapping(self):
71
+ """
72
+ Load FedRAMP version 4 mapping
73
+ """
74
+ from importlib.resources import path as resource_path
75
+
76
+ with resource_path("regscale.integrations.public.fedramp.mappings", "fedramp_r4_parts.json") as json_file_path:
77
+ self.load_json_from_file(json_file_path.__str__())
78
+
79
+ def find_by_control_id_and_part_letter(self, control_label: str, part: str) -> list:
80
+ """
81
+ Find a mapping by control label and part letter.
82
+ :param str control_label: The control label.
83
+ :param str part: The part letter.
84
+ :return: A list of mappings.
85
+ :rtype: list
86
+ """
87
+ result = [
88
+ item.get("OSCAL_PART_IDENTIFIER")
89
+ for item in self.data
90
+ if item.get("CONTROLLABEL") == control_label and item.get("Part") == part
91
+ ]
92
+ return result
93
+
94
+ def find_by_oscal_control_id_and_part_letter(self, oscal_control_id: str, part: str) -> list:
95
+ """
96
+ Find a mapping by OSCAL control ID and part letter.
97
+ :param str oscal_control_id:
98
+ :param str part:
99
+ :return: A list of mappings.
100
+ :rtype: list
101
+ """
102
+ result = [
103
+ item.get("OSCAL_PART_IDENTIFIER")
104
+ for item in self.data
105
+ if item.get("OSCALCONTROL_ID") == oscal_control_id and item.get("Part") == part
106
+ ]
107
+ return result
File without changes