regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/api.py +5 -2
  4. regscale/core/app/application.py +36 -6
  5. regscale/core/app/internal/control_editor.py +73 -21
  6. regscale/core/app/internal/evidence.py +727 -204
  7. regscale/core/app/internal/login.py +4 -2
  8. regscale/core/app/internal/model_editor.py +219 -64
  9. regscale/core/app/utils/app_utils.py +86 -12
  10. regscale/core/app/utils/catalog_utils/common.py +1 -1
  11. regscale/core/login.py +21 -4
  12. regscale/core/utils/async_graphql_client.py +363 -0
  13. regscale/core/utils/date.py +77 -1
  14. regscale/dev/cli.py +26 -0
  15. regscale/dev/code_gen.py +109 -24
  16. regscale/dev/version.py +72 -0
  17. regscale/integrations/commercial/__init__.py +30 -2
  18. regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
  19. regscale/integrations/commercial/aws/cli.py +3107 -54
  20. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  21. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  22. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  23. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  24. regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
  25. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  26. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  27. regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
  28. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  29. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  30. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  31. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  32. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  33. regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
  34. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  35. regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
  36. regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
  37. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  38. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  39. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  40. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  41. regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
  42. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  43. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  44. regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
  45. regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
  46. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  47. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  48. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  49. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  50. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  51. regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
  52. regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
  53. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  54. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  55. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  56. regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
  57. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  58. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  59. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  60. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  61. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  62. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  63. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  64. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  65. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  66. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  67. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  68. regscale/integrations/commercial/aws/scanner.py +1072 -205
  69. regscale/integrations/commercial/aws/security_hub.py +319 -0
  70. regscale/integrations/commercial/aws/session_manager.py +282 -0
  71. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  72. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  73. regscale/integrations/commercial/jira.py +489 -153
  74. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  75. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  76. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  77. regscale/integrations/commercial/qualys/__init__.py +167 -68
  78. regscale/integrations/commercial/qualys/scanner.py +305 -39
  79. regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
  80. regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
  81. regscale/integrations/commercial/sicura/api.py +79 -42
  82. regscale/integrations/commercial/sicura/commands.py +8 -2
  83. regscale/integrations/commercial/sicura/scanner.py +83 -44
  84. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  85. regscale/integrations/commercial/synqly/assets.py +133 -16
  86. regscale/integrations/commercial/synqly/edr.py +2 -8
  87. regscale/integrations/commercial/synqly/query_builder.py +536 -0
  88. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  89. regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
  90. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  91. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  92. regscale/integrations/commercial/tenablev2/commands.py +146 -5
  93. regscale/integrations/commercial/tenablev2/scanner.py +1 -3
  94. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  95. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  96. regscale/integrations/commercial/wizv2/click.py +191 -76
  97. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  98. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  99. regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
  100. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  101. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
  102. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
  103. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  104. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  105. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
  106. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  107. regscale/integrations/commercial/wizv2/issue.py +776 -28
  108. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  109. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  110. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  111. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  112. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  113. regscale/integrations/commercial/wizv2/reports.py +243 -0
  114. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  115. regscale/integrations/commercial/wizv2/scanner.py +1031 -441
  116. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  117. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  118. regscale/integrations/commercial/wizv2/variables.py +89 -3
  119. regscale/integrations/compliance_integration.py +1036 -151
  120. regscale/integrations/control_matcher.py +432 -0
  121. regscale/integrations/due_date_handler.py +333 -0
  122. regscale/integrations/milestone_manager.py +291 -0
  123. regscale/integrations/public/__init__.py +14 -0
  124. regscale/integrations/public/cci_importer.py +834 -0
  125. regscale/integrations/public/csam/__init__.py +0 -0
  126. regscale/integrations/public/csam/csam.py +938 -0
  127. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  128. regscale/integrations/public/csam/csam_common.py +154 -0
  129. regscale/integrations/public/csam/csam_controls.py +432 -0
  130. regscale/integrations/public/csam/csam_poam.py +124 -0
  131. regscale/integrations/public/fedramp/click.py +77 -6
  132. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  133. regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
  134. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  135. regscale/integrations/public/fedramp/poam/scanner.py +75 -7
  136. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  137. regscale/integrations/scanner_integration.py +1961 -430
  138. regscale/models/integration_models/CCI_List.xml +1 -0
  139. regscale/models/integration_models/aqua.py +2 -2
  140. regscale/models/integration_models/cisa_kev_data.json +805 -11
  141. regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
  142. regscale/models/integration_models/nexpose.py +36 -10
  143. regscale/models/integration_models/qualys.py +3 -4
  144. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  145. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
  146. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  147. regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
  148. regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
  149. regscale/models/locking.py +12 -8
  150. regscale/models/platform.py +4 -2
  151. regscale/models/regscale_models/__init__.py +7 -0
  152. regscale/models/regscale_models/assessment.py +2 -1
  153. regscale/models/regscale_models/catalog.py +1 -1
  154. regscale/models/regscale_models/compliance_settings.py +251 -1
  155. regscale/models/regscale_models/component.py +1 -0
  156. regscale/models/regscale_models/control_implementation.py +236 -41
  157. regscale/models/regscale_models/control_objective.py +74 -5
  158. regscale/models/regscale_models/file.py +2 -0
  159. regscale/models/regscale_models/form_field_value.py +5 -3
  160. regscale/models/regscale_models/inheritance.py +44 -0
  161. regscale/models/regscale_models/issue.py +301 -102
  162. regscale/models/regscale_models/milestone.py +33 -14
  163. regscale/models/regscale_models/organization.py +3 -0
  164. regscale/models/regscale_models/regscale_model.py +310 -73
  165. regscale/models/regscale_models/security_plan.py +4 -2
  166. regscale/models/regscale_models/vulnerability.py +3 -3
  167. regscale/regscale.py +25 -4
  168. regscale/templates/__init__.py +0 -0
  169. regscale/utils/threading/threadhandler.py +20 -15
  170. regscale/validation/record.py +23 -1
  171. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
  172. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
  173. tests/core/__init__.py +0 -0
  174. tests/core/utils/__init__.py +0 -0
  175. tests/core/utils/test_async_graphql_client.py +472 -0
  176. tests/fixtures/test_fixture.py +13 -8
  177. tests/regscale/core/test_login.py +171 -4
  178. tests/regscale/integrations/commercial/__init__.py +0 -0
  179. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  180. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  181. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  182. tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
  183. tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
  184. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  185. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  186. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  187. tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
  188. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  189. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  190. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  191. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  192. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  193. tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
  194. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  195. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  196. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  197. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  198. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  199. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  200. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  201. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  202. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  203. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  204. tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
  205. tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
  206. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  207. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  208. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  209. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  210. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  211. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  212. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  213. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  214. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  215. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  216. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  217. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  218. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  219. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  220. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  221. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  222. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  223. tests/regscale/integrations/commercial/conftest.py +28 -0
  224. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  225. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  226. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  227. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  228. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  229. tests/regscale/integrations/commercial/test_aws.py +3742 -0
  230. tests/regscale/integrations/commercial/test_burp.py +48 -0
  231. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  232. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  233. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  234. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  235. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  236. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  237. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  238. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  239. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  240. tests/regscale/integrations/commercial/test_sicura.py +349 -0
  241. tests/regscale/integrations/commercial/test_snow.py +423 -0
  242. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  243. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  244. tests/regscale/integrations/commercial/test_stig.py +33 -0
  245. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  246. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  247. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  248. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  249. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  250. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  251. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  252. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  253. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  254. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  255. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  256. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  257. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  258. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  259. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  260. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  261. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  262. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  263. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  264. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  265. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  266. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  267. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  268. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  269. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  270. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  271. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  272. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  273. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  274. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  275. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  276. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  277. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  278. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  279. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  280. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
  281. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  282. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  283. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  284. tests/regscale/integrations/public/__init__.py +0 -0
  285. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  286. tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
  287. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  288. tests/regscale/integrations/public/test_alienvault.py +220 -0
  289. tests/regscale/integrations/public/test_cci.py +1053 -0
  290. tests/regscale/integrations/public/test_cisa.py +1021 -0
  291. tests/regscale/integrations/public/test_emass.py +518 -0
  292. tests/regscale/integrations/public/test_fedramp.py +1152 -0
  293. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  294. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  295. tests/regscale/integrations/public/test_oscal.py +453 -0
  296. tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
  297. tests/regscale/integrations/test_control_matcher.py +1421 -0
  298. tests/regscale/integrations/test_control_matching.py +155 -0
  299. tests/regscale/integrations/test_milestone_manager.py +408 -0
  300. tests/regscale/models/test_control_implementation.py +118 -3
  301. tests/regscale/models/test_form_field_value_integration.py +304 -0
  302. tests/regscale/models/test_issue.py +378 -1
  303. tests/regscale/models/test_module_integration.py +582 -0
  304. tests/regscale/models/test_tenable_integrations.py +811 -105
  305. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
  306. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
  307. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
  308. regscale/integrations/public/fedramp/parts_mapper.py +0 -107
  309. /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
  310. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  311. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
  312. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
  313. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
  314. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,938 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Integrates CSAM into RegScale"""
4
+
5
+ # standard python imports
6
+ import logging
7
+ from typing import List, Optional
8
+ from rich.progress import track
9
+ import click
10
+ from rich.console import Console
11
+ from regscale.core.app.api import Api
12
+ from regscale.core.app.application import Application
13
+ from regscale.core.utils.date import format_to_regscale_iso, date_obj
14
+ from regscale.core.app.utils.app_utils import error_and_exit, filter_list
15
+ from regscale.models.regscale_models import (
16
+ Organization,
17
+ SecurityPlan,
18
+ User,
19
+ )
20
+ from regscale.models.regscale_models.module import Module
21
+ from regscale.models.regscale_models.form_field_value import FormFieldValue
22
+ from regscale.integrations.public.csam.csam_poam import import_csam_poams
23
+ from regscale.integrations.public.csam.csam_controls import (
24
+ import_csam_controls,
25
+ set_inheritable,
26
+ import_csam_inheritance,
27
+ )
28
+ from regscale.integrations.public.csam.csam_agency_defined import update_ssp_agency_details
29
+ from regscale.integrations.public.csam.csam_common import (
30
+ retrieve_ssps_custom_form_map,
31
+ retrieve_custom_form_ssps_map,
32
+ retrieve_from_csam,
33
+ set_custom_fields,
34
+ fix_form_field_value,
35
+ CSAM_FIELD_NAME,
36
+ FISMA_FIELD_NAME,
37
+ SSP_BASIC_TAB,
38
+ )
39
+
40
+
41
+ logger = logging.getLogger("regscale")
42
+ console = Console()
43
+
44
+ ####################################################################################################
45
+ #
46
+ # IMPORT SSP / POAM FROM DoJ's CSAM GRC
47
+ # CSAM API Docs: https://csam.dhs.gov/CSAM/api/docs/index.html (required PIV)
48
+ #
49
+ ####################################################################################################
50
+
51
+
52
+ SSP_SYSTEM_TAB = "System Information"
53
+ SSP_FINANCIAL_TAB = "Financial Info"
54
+ SSP_PRIVACY_TAB = "Privacy-Details"
55
+ SSP_CONTINGENCY_TAB = "Continuity and Incident Response"
56
+
57
+ CUSTOM_FIELDS_BASIC_LIST = [
58
+ "acronym",
59
+ "Classification",
60
+ "FISMA Reportable",
61
+ "Contractor System",
62
+ "Authorization Process",
63
+ "ATO Date",
64
+ "ATO Status",
65
+ "Critical Infrastructure",
66
+ "Mission Essential",
67
+ "uiiCode",
68
+ "HVA Identifier",
69
+ "External Web Interface",
70
+ "CFO Designation",
71
+ "Law Enforcement Sensitive",
72
+ CSAM_FIELD_NAME,
73
+ FISMA_FIELD_NAME,
74
+ ]
75
+
76
+
77
+ @click.group()
78
+ def csam():
79
+ """Integrate CSAM."""
80
+
81
+
82
+ @csam.command(name="import_ssp")
83
+ def import_ssp():
84
+ """
85
+ Import SSP from CSAM
86
+ Into RegScale
87
+ """
88
+
89
+ import_csam_ssp()
90
+
91
+
92
+ @csam.command(name="import_poam")
93
+ def import_poam():
94
+ """
95
+ Import POAMS from CSAM
96
+ Into RegScale
97
+ """
98
+
99
+ import_csam_poams()
100
+
101
+
102
+ def import_csam_ssp():
103
+ """
104
+ Import SSPs from CSAM
105
+ Into RegScale
106
+ According to a filter in init.yaml
107
+ """
108
+
109
+ logger.info("Gathering reference info...")
110
+ # Check Custom Fields exist
111
+ custom_fields_basic_map = FormFieldValue.check_custom_fields(
112
+ CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
113
+ )
114
+
115
+ # Get a map of existing custom forms
116
+ ssp_map = retrieve_custom_form_ssps_map(
117
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
118
+ )
119
+
120
+ # Get a list of orgs and create a map to id
121
+ orgs = Organization.get_list()
122
+ org_map = {org.name: org.id for org in orgs}
123
+
124
+ # Grab the data from CSAM
125
+ app = Application()
126
+ csam_filter = app.config.get("csamFilter", None)
127
+
128
+ logger.info("Retrieving systems from CSAM...")
129
+ results = retrieve_from_csam(
130
+ csam_endpoint="/CSAM/api/v1/systems",
131
+ )
132
+
133
+ if not results:
134
+ error_and_exit("Failure to retrieve plans from CSAM")
135
+ else:
136
+ logger.info("Retrieved plans from CSAM, parsing results...")
137
+
138
+ results = filter_list(results, csam_filter)
139
+ if not results:
140
+ error_and_exit(
141
+ "No results match filter in CSAM. \
142
+ Please check your CSAM configuration."
143
+ )
144
+
145
+ logger.info("Importing systems... ")
146
+ # Parse the results
147
+ updated_ssps = []
148
+ updated_ssps = save_ssp_front_matter(
149
+ results=results,
150
+ ssp_map=ssp_map,
151
+ custom_fields_basic_map=custom_fields_basic_map,
152
+ org_map=org_map,
153
+ )
154
+
155
+ # Now have to get the system details for each system
156
+ update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
157
+
158
+ # Import the authorization process and status
159
+ import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
160
+
161
+ # Import the Privacy date
162
+ import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
163
+
164
+ # Import the Contingency & IR data
165
+ import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
166
+
167
+ # Import the controls
168
+ import_csam_controls(import_ids=[ssp.id for ssp in updated_ssps])
169
+
170
+ # Set inheritance if system type = program
171
+ for result in results:
172
+ if result.get("systemType") == "Program":
173
+ # Get the RegScale SSP Id
174
+ program_id = ssp_map.get(result["externalId"])
175
+ if not program_id:
176
+ logger.error(
177
+ f"Could not find RegScale SSP for CSAM id: {result['externalId']}. \
178
+ Please create or import the Security Plan prior to importing inheritance."
179
+ )
180
+ continue
181
+
182
+ # Set the inheritable flag
183
+ set_inheritable(regscale_id=program_id)
184
+
185
+ # Import the Inheritance
186
+ import_csam_inheritance(import_ids=[ssp.id for ssp in updated_ssps])
187
+
188
+ # Import the POCs
189
+ import_csam_pocs(import_ids=[ssp.id for ssp in updated_ssps])
190
+
191
+
192
+ def sync_csam_ssps():
193
+
194
+ logger.info("Gathering reference info...")
195
+ # Check Custom Fields exist
196
+ custom_fields_basic_map = FormFieldValue.check_custom_fields(
197
+ CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
198
+ )
199
+
200
+ # Get a map of existing custom forms
201
+ ssp_map = retrieve_custom_form_ssps_map(
202
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
203
+ )
204
+ csam_map = retrieve_custom_form_ssps_map(
205
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
206
+ )
207
+
208
+ # Get a list of orgs and create a map to id
209
+ orgs = Organization.get_list()
210
+ org_map = {org.name: org.id for org in orgs}
211
+
212
+ csam_list = []
213
+ for id in csam_map.keys():
214
+ csam_list.append(int(id))
215
+ csam_filter = {"id": csam_list}
216
+
217
+ logger.info("Retrieving systems from CSAM...")
218
+ results = retrieve_from_csam(
219
+ csam_endpoint="/CSAM/api/v1/systems",
220
+ )
221
+
222
+ results = filter_list(results, csam_filter)
223
+
224
+ logger.info("Syncing systems... ")
225
+ # Parse the results
226
+ updated_ssps = []
227
+ updated_ssps = save_ssp_front_matter(
228
+ results=results,
229
+ ssp_map=ssp_map,
230
+ custom_fields_basic_map=custom_fields_basic_map,
231
+ org_map=org_map,
232
+ )
233
+
234
+ # Now have to get the system details for each system
235
+ update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
236
+
237
+ # Import the authorization process and status
238
+ import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
239
+
240
+ # Import the Privacy date
241
+ import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
242
+
243
+ # Import the Contingency & IR data
244
+ import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
245
+
246
+
247
+ def import_csam_pocs(import_ids: Optional[List[int]] = None):
248
+ """
249
+ Import the Points of Contact from CSAM
250
+ Into RegScale
251
+ """
252
+ custom_fields_pocs_list = [
253
+ "Certifying Official",
254
+ "Alternate Information System Security Manager",
255
+ "Alternate Information System Security Officer",
256
+ ]
257
+ # Check Custom Fields exist
258
+ custom_fields_pocs_map = FormFieldValue.check_custom_fields(
259
+ custom_fields_pocs_list, "securityplans", "Points of Contact"
260
+ )
261
+
262
+ # Get existing ssps by CSAM Id
263
+ custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
264
+ ssp_map = retrieve_ssps_custom_form_map(
265
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
266
+ )
267
+
268
+ plans = import_ids if import_ids else list(ssp_map.keys())
269
+
270
+ # Get a list of users and create a map to id
271
+ users = User.get_all()
272
+ user_map = {user.userName: user.id for user in users}
273
+
274
+ # TO DO... Add the rest of the logic
275
+ # Delete these lines: Added to shut up sonarqube
276
+ logger.debug(f"Custom Fields Map: {custom_fields_pocs_map}, User Map: {user_map}")
277
+ logger.debug(f"SSP Map: {ssp_map}, Plans: {plans}")
278
+
279
+
280
+ def import_csam_privacy_info(import_ids: Optional[List[int]] = None):
281
+ """
282
+ Import the Privacy Info from CSAM
283
+ Into RegScale
284
+ """
285
+
286
+ # Get existing ssps by CSAM Id
287
+ custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
288
+ ssp_map = retrieve_ssps_custom_form_map(
289
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
290
+ )
291
+
292
+ ssps = import_ids if import_ids else list(ssp_map.keys())
293
+
294
+ updated_ssps = []
295
+ if len(ssps) == 0:
296
+ return
297
+ for index in track(
298
+ range(len(ssps)),
299
+ description=f"Importing {len(ssps)} SSP privacy...",
300
+ ):
301
+ ssp = ssps[index]
302
+ system_id = ssp_map.get(ssp)
303
+ if not system_id:
304
+ logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
305
+ continue
306
+ else:
307
+ updated_ssps.append(ssp)
308
+
309
+ result = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/privacy")
310
+ if len(result) == 0:
311
+ logger.error(f"Could not retrieve privacy for CSAM ID {system_id}. RegScale SSP id: {ssp}")
312
+ continue
313
+
314
+ pia_date = result.get("privacyImpactAssessmentDateCompleted")
315
+ pta_date = result.get("privacyThresholdAnalysisDateCompleted")
316
+
317
+ # Get SORN Status
318
+ result = retrieve_from_csam(
319
+ csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/sorn",
320
+ )
321
+ if len(result) == 0:
322
+ logger.debug(f"Could not retrieve SORN for CSAM ID {system_id}. RegScale SSP id: {ssp}")
323
+ continue
324
+ sorn_date = 0
325
+ sorn_id = ""
326
+ for sorn_status in result:
327
+ if date_obj(sorn_status.get("publishedDate")) > date_obj(sorn_date):
328
+ sorn_date = sorn_status.get("publishedDate")
329
+ sorn_id = sorn_status.get("systemOfRecordsNoticeId").strip()
330
+
331
+ # Set the records
332
+ record = {"pia_date": pia_date, "pta_date": pta_date, "sorn_date": sorn_date, "sorn_id": sorn_id}
333
+ save_privacy_records(regscale_id=ssp, record=record)
334
+
335
+ logger.info(f"Updated {len(updated_ssps)} Security Plans with privacy data")
336
+
337
+
338
+ def save_privacy_records(regscale_id: int, record: dict):
339
+
340
+ custom_fields_privacy_list = ["PIA Date", "PTA Date", "SORN Date", "SORN Id"]
341
+
342
+ # Check for custom fields
343
+ custom_fields_map = FormFieldValue.check_custom_fields(custom_fields_privacy_list, "securityplans", SSP_PRIVACY_TAB)
344
+
345
+ privacy_fields = []
346
+ if record.get("pia_date"):
347
+ privacy_fields.append(
348
+ {
349
+ "record_id": regscale_id,
350
+ "record_module": "securityplans",
351
+ "form_field_id": custom_fields_map["PIA Date"],
352
+ "field_value": format_to_regscale_iso(record.get("pia_date")),
353
+ }
354
+ )
355
+ if record.get("pta_date"):
356
+ privacy_fields.append(
357
+ {
358
+ "record_id": regscale_id,
359
+ "record_module": "securityplans",
360
+ "form_field_id": custom_fields_map["PTA Date"],
361
+ "field_value": format_to_regscale_iso(record.get("pta_date")),
362
+ }
363
+ )
364
+ if record.get("sorn_date"):
365
+ privacy_fields.append(
366
+ {
367
+ "record_id": regscale_id,
368
+ "record_module": "securityplans",
369
+ "form_field_id": custom_fields_map["SORN Date"],
370
+ "field_value": format_to_regscale_iso(record.get("sorn_date")),
371
+ }
372
+ )
373
+ if record.get("sorn_id"):
374
+ privacy_fields.append(
375
+ {
376
+ "record_id": regscale_id,
377
+ "record_module": "securityplans",
378
+ "form_field_id": custom_fields_map["SORN Id"],
379
+ "field_value": str(record.get("sorn_id")),
380
+ }
381
+ )
382
+ if len(privacy_fields) > 0:
383
+ privacy_fields = fix_form_field_value(privacy_fields)
384
+ FormFieldValue.save_custom_fields(privacy_fields)
385
+
386
+
387
+ def import_csam_authorization(import_ids: Optional[List[int]] = None):
388
+ """
389
+ Update the Authorization of the SSPs
390
+ This requires a call to the /system/{id}/securityauthorization
391
+ endpoint
392
+
393
+ :param list import_ids: Filtered list of SSPs
394
+ :return: None
395
+ """
396
+ # Get existing ssps by CSAM Id
397
+ custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
398
+ ssp_map = retrieve_ssps_custom_form_map(
399
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
400
+ )
401
+
402
+ ssps = import_ids if import_ids else list(ssp_map.keys())
403
+
404
+ updated_ssps = []
405
+ field_values = []
406
+ if len(ssps) == 0:
407
+ return
408
+ for index in track(
409
+ range(len(ssps)),
410
+ description=f"Importing {len(ssps)} SSP authorization...",
411
+ ):
412
+ ssp = ssps[index]
413
+ csam_id = ssp_map.get(ssp)
414
+ if not csam_id:
415
+ logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
416
+ continue
417
+ else:
418
+ updated_ssps.append(ssp)
419
+
420
+ result = retrieve_from_csam(
421
+ csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/securityauthorization",
422
+ )
423
+ if len(result) == 0:
424
+ logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
425
+ continue
426
+ # Set the authorization expiration date
427
+ ssp_obj = SecurityPlan.get_object(object_id=ssp)
428
+ if ssp_obj:
429
+ ssp_obj.authorizationTerminationDate = result.get("authorizationExpirationDate")
430
+ ssp_obj.save()
431
+ else:
432
+ logger.debug(f"Failed to retrieve Security Plan id: {ssp}")
433
+ # Get the custom fields
434
+ field_values.append(
435
+ {
436
+ "record_id": ssp,
437
+ "record_module": "securityplans",
438
+ "form_field_id": custom_fields_basic_map["Authorization Process"],
439
+ "field_value": str(result.get("authorizationProcess")),
440
+ }
441
+ )
442
+ field_values.append(
443
+ {
444
+ "record_id": ssp,
445
+ "record_module": "securityplans",
446
+ "form_field_id": custom_fields_basic_map["ATO Date"],
447
+ "field_value": str(result.get("lastAuthorizationDate")),
448
+ }
449
+ )
450
+ field_values.append(
451
+ {
452
+ "record_id": ssp,
453
+ "record_module": "securityplans",
454
+ "form_field_id": custom_fields_basic_map["ATO Status"],
455
+ "field_value": str(result.get("authorizationStatus")),
456
+ }
457
+ )
458
+ # Save the Custom Fields
459
+ if len(field_values) > 0:
460
+ field_values = fix_form_field_value(field_values)
461
+ FormFieldValue.save_custom_fields(field_values)
462
+
463
+ logger.info(f"Updated {len(updated_ssps)} Security Plans with authorization data")
464
+
465
+
466
+ def import_csam_contingency(import_ids: Optional[List[int]] = None):
467
+ """
468
+ Update the Contingency & IR of the SSPs
469
+ This requires a call to the /systems/<system_id>/continuityresponse
470
+ endpoint
471
+
472
+ :param list import_ids: Filtered list of SSPs
473
+ :return: None
474
+ """
475
+ # Continuity & IR Fields
476
+ # Goes to "Continuity and Incident Response" Tab
477
+ # /CSAM/api/v1/systems/<system_id>/continuityresponse
478
+ continuity_map = {
479
+ "maximumTolerableDowntime": "MTD",
480
+ "recoveryTimeObjective": "RTO",
481
+ "recoveryPointObjective": "RPO",
482
+ "businessImpactAnalysisDateCompleted": "BIA Completed",
483
+ "businessImpactAnalysisNextDueDate": "BIA Next Due Date",
484
+ "contingencyPlanDateCompleted": "CP Completed",
485
+ "contingencyPlanNextDueDate": "CP Next Due Date",
486
+ "contingencyPlanTrainingDateCompleted": "CP Training Completed",
487
+ "contingencyPlanTrainingNextDueDate": "CP Training Next Due Date",
488
+ "contingencyPlanTestNextDueDate": "CP Test Next Due Date",
489
+ "incidentResponsePlanDateCompleted": "IRP Completed",
490
+ "incidentResponsePlanNextDueDate": "IRP Next Due Date",
491
+ "incidentResponsePlanTrainingDateCompleted": "IRP Training Completed",
492
+ "incidentResponsePlanTrainingNextDueDate": "IRP Training Next Due Date",
493
+ "incidentResponsePlanTestNextDueDate": "IRP Test Next Due Date",
494
+ }
495
+
496
+ # /CSAM/api/v1/systems/<system_id>/continuitytest
497
+ # testItem == "Contingency Plan (CP)""
498
+ continuity_test_map = {"testType": "CP Test Type", "dateTested": "CP Date Tested", "outcome": "CP Test Outcome"}
499
+ # testItem == "Incident Response Plan (IRP)"
500
+ irp_test_map = {"testType": "IRP Test Type", "dateTested": "IRP Date Tested", "outcome": "IRP Test Outcome"}
501
+
502
+ # /CSAM/api/v1/systems/{system_id}/additionalstatus
503
+ # name == "Contingency Plan Review"
504
+ contingency_plan_map = {
505
+ "dateCompleted": "CPR Completed",
506
+ "nextDueDate": "CPR Next Due Date",
507
+ "expirationDate": "CPR Expiration Date",
508
+ }
509
+ continuity_ir_fields = []
510
+ continuity_ir_fields = continuity_ir_fields + list(continuity_map.values())
511
+ continuity_ir_fields = continuity_ir_fields + list(continuity_test_map.values())
512
+ continuity_ir_fields = continuity_ir_fields + list(irp_test_map.values())
513
+ continuity_ir_fields = continuity_ir_fields + list(contingency_plan_map.values())
514
+ continuity_ir_fields_map = FormFieldValue.check_custom_fields(
515
+ continuity_ir_fields, "securityplans", "Continuity and Incident Response"
516
+ )
517
+
518
+ # name == "Document Review Approval"
519
+ # goes to "Document Review" Tab
520
+ doc_approv_map = {
521
+ "dateCompleted": "Doc Review Completed",
522
+ "nextDueDate": "Doc Review Next Due Date",
523
+ "expirationDate": "Doc Review Expiration Date",
524
+ }
525
+ doc_approv_fields = list(doc_approv_map.values())
526
+ doc_approv_fields_map = FormFieldValue.check_custom_fields(doc_approv_fields, "securityplans", "Document Review")
527
+
528
+ # Get existing ssps by CSAM Id
529
+ custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
530
+ ssp_map = retrieve_ssps_custom_form_map(
531
+ tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
532
+ )
533
+
534
+ ssps = import_ids if import_ids else list(ssp_map.keys())
535
+
536
+ updated_ssps = []
537
+ field_values = []
538
+ if len(ssps) == 0:
539
+ return
540
+ for index in track(
541
+ range(len(ssps)),
542
+ description=f"Importing {len(ssps)} SSP contingency data...",
543
+ ):
544
+ ssp = ssps[index]
545
+ csam_id = ssp_map.get(ssp)
546
+ if not csam_id:
547
+ logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
548
+ continue
549
+ else:
550
+ updated_ssps.append(ssp)
551
+
552
+ continuity_response = get_continuity_response_fields(
553
+ ssp=ssp, csam_id=csam_id, continuity_ir_fields_map=continuity_ir_fields_map, continuity_map=continuity_map
554
+ )
555
+ field_values = field_values + continuity_response
556
+
557
+ continuity_test = get_continuity_test_fields(
558
+ ssp=ssp,
559
+ csam_id=csam_id,
560
+ continuity_ir_fields_map=continuity_ir_fields_map,
561
+ continuity_test_map=continuity_test_map,
562
+ irp_test_map=irp_test_map,
563
+ )
564
+ field_values = field_values + continuity_test
565
+
566
+ additional_status = get_additional_status_fields(
567
+ ssp=ssp,
568
+ csam_id=csam_id,
569
+ continuity_ir_fields_map=continuity_ir_fields_map,
570
+ contingency_plan_map=contingency_plan_map,
571
+ doc_approv_fields_map=doc_approv_fields_map,
572
+ doc_approv_map=doc_approv_map,
573
+ )
574
+ field_values = field_values + additional_status
575
+
576
+ # Save the Custom Fields
577
+ if len(field_values) > 0:
578
+ field_values = fix_form_field_value(field_values)
579
+ FormFieldValue.save_custom_fields(field_values)
580
+
581
+ logger.info(f"Updated {len(updated_ssps)} Security Plans with contingency data")
582
+
583
+
584
+ def get_continuity_response_fields(ssp: int, csam_id: int, continuity_map: dict, continuity_ir_fields_map: dict):
585
+ # Get the data from /continuityresponse
586
+ result = retrieve_from_csam(
587
+ csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuityresponse",
588
+ )
589
+
590
+ if not result:
591
+ logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
592
+ return []
593
+
594
+ response_data = result[0]
595
+
596
+ # Pre-compute the field mapping to avoid nested lookups
597
+ field_mapping = {
598
+ field: continuity_ir_fields_map[mapped_name]
599
+ for field, mapped_name in continuity_map.items()
600
+ if mapped_name in continuity_ir_fields_map
601
+ }
602
+
603
+ # Build field values with single-level lookup
604
+ return [
605
+ {
606
+ "record_id": ssp,
607
+ "record_module": "securityplans",
608
+ "form_field_id": field_id,
609
+ "field_value": str(response_data[field]),
610
+ }
611
+ for field, field_id in field_mapping.items()
612
+ if field in response_data
613
+ ]
614
+
615
+
616
+ def get_continuity_test_fields(
617
+ ssp: int, csam_id: int, continuity_ir_fields_map: dict, continuity_test_map: dict, irp_test_map: dict
618
+ ):
619
+ # Get the data from continuitytest
620
+ results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuitytest")
621
+
622
+ if not results:
623
+ logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
624
+ return []
625
+
626
+ # Pre-compute field mappings to avoid nested lookups
627
+ cp_field_mapping = {
628
+ field: continuity_ir_fields_map[mapped_name]
629
+ for field, mapped_name in continuity_test_map.items()
630
+ if mapped_name in continuity_ir_fields_map
631
+ }
632
+
633
+ irp_field_mapping = {
634
+ field: continuity_ir_fields_map[mapped_name]
635
+ for field, mapped_name in irp_test_map.items()
636
+ if mapped_name in continuity_ir_fields_map
637
+ }
638
+
639
+ field_values = []
640
+ for result in results:
641
+ test_item = result.get("testItem")
642
+
643
+ # Process Contingency Plan (CP) fields
644
+ if test_item == "Contingency Plan (CP)":
645
+ field_values.extend(
646
+ [
647
+ {
648
+ "record_id": ssp,
649
+ "record_module": "securityplans",
650
+ "form_field_id": field_id,
651
+ "field_value": str(result[field]),
652
+ }
653
+ for field, field_id in cp_field_mapping.items()
654
+ if field in result
655
+ ]
656
+ )
657
+
658
+ # Process Incident Response Plan (IRP) fields
659
+ elif test_item == "Incident Response Plan (IRP)":
660
+ field_values.extend(
661
+ [
662
+ {
663
+ "record_id": ssp,
664
+ "record_module": "securityplans",
665
+ "form_field_id": field_id,
666
+ "field_value": str(result[field]),
667
+ }
668
+ for field, field_id in irp_field_mapping.items()
669
+ if field in result
670
+ ]
671
+ )
672
+
673
+ return field_values
674
+
675
+
676
+ def get_additional_status_fields(
677
+ ssp: int,
678
+ csam_id: int,
679
+ continuity_ir_fields_map: dict,
680
+ contingency_plan_map: dict,
681
+ doc_approv_fields_map: dict,
682
+ doc_approv_map: dict,
683
+ ):
684
+ # Get the data from additional status
685
+ results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/additionalstatus")
686
+
687
+ if not results:
688
+ logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
689
+ return []
690
+
691
+ # Pre-compute field mappings to avoid nested lookups
692
+ cp_field_mapping = {
693
+ field: continuity_ir_fields_map[mapped_name]
694
+ for field, mapped_name in contingency_plan_map.items()
695
+ if mapped_name in continuity_ir_fields_map
696
+ }
697
+
698
+ doc_field_mapping = {
699
+ field: doc_approv_fields_map[mapped_name]
700
+ for field, mapped_name in doc_approv_map.items()
701
+ if mapped_name in doc_approv_fields_map
702
+ }
703
+
704
+ field_values = []
705
+ for result in results:
706
+ result_name = result.get("name")
707
+
708
+ # Process Contingency Plan Review fields
709
+ if result_name == "Contingency Plan Review":
710
+ field_values.extend(
711
+ [
712
+ {
713
+ "record_id": ssp,
714
+ "record_module": "securityplans",
715
+ "form_field_id": field_id,
716
+ "field_value": str(result[field]),
717
+ }
718
+ for field, field_id in cp_field_mapping.items()
719
+ if field in result
720
+ ]
721
+ )
722
+
723
+ # Process Document Review Approval fields
724
+ elif result_name == "Document Review Approval":
725
+ field_values.extend(
726
+ [
727
+ {
728
+ "record_id": ssp,
729
+ "record_module": "securityplans",
730
+ "form_field_id": field_id,
731
+ "field_value": str(result[field]),
732
+ }
733
+ for field, field_id in doc_field_mapping.items()
734
+ if field in result
735
+ ]
736
+ )
737
+
738
+ return field_values
739
+
740
+
741
+ def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> SecurityPlan:
742
+ """
743
+ Update or Create the SSP Record
744
+ Based upon the values in Record
745
+
746
+ :param SecurityPlan ssp: RegScale Security Plan
747
+ :param dict record: record of values
748
+ :param dict org_map: map of org names to orgId
749
+ :return: SecurityPlan Object
750
+ :return_type: SecurityPlan
751
+ """
752
+
753
+ ssp.otherIdentifier = record["id"]
754
+ ssp.overallCategorization = record["categorization"]
755
+ ssp.confidentiality = record["categorization"]
756
+ ssp.integrity = record["categorization"]
757
+ ssp.availability = record["categorization"]
758
+ ssp.status = record["operationalStatus"]
759
+ ssp.systemType = record["systemType"]
760
+ ssp.description = record["purpose"]
761
+ ssp.defaultAssessmentDays = 0
762
+ if record["organization"] and org_map.get(record["organization"]):
763
+ ssp.orgId = org_map.get(record["organization"])
764
+
765
+ if ssp.id == 0:
766
+ new_ssp = ssp.create()
767
+ else:
768
+ new_ssp = ssp.save()
769
+
770
+ return new_ssp
771
+
772
+
773
+ def save_ssp_front_matter(results: list, ssp_map: dict, custom_fields_basic_map: dict, org_map: dict) -> list:
774
+ """
775
+ Save the SSP data from the /systems endpoint
776
+
777
+ :param list results: list of results from CSAM
778
+ :param dict ssp_map: map of existing SSPs in RegScale
779
+ :param dict custom_fields_basic_map: map of custom fields in RegScale
780
+ :param dict org_map: map of existing orgs in RegScale
781
+ :return: list of updated SSPs
782
+ :return_type: List[SecurityPlan]
783
+ """
784
+
785
+ updated_ssps = []
786
+ for index in track(
787
+ range(len(results)),
788
+ description=f"Importing {len(results)} SSP front matter...",
789
+ ):
790
+ result = results[index]
791
+
792
+ # Get the existing SSP:
793
+ ssp_id = ssp_map.get(result["externalId"])
794
+ if ssp_id:
795
+ ssp = SecurityPlan.get_object(ssp_id)
796
+ else:
797
+ ssp = SecurityPlan(systemName=result["name"])
798
+ # Update the SSP
799
+ ssp = update_ssp_general(ssp, result, org_map)
800
+
801
+ # Grab the Custom Fields
802
+ field_values = set_front_matter_fields(ssp=ssp, result=result, custom_fields_basic_map=custom_fields_basic_map)
803
+
804
+ # System Custom Fields
805
+ field_values = fix_form_field_value(field_values)
806
+ FormFieldValue.save_custom_fields(field_values)
807
+ updated_ssps.append(ssp)
808
+ logger.info(f"Updated {len(results)} Security Plans Front Matter")
809
+ return updated_ssps
810
+
811
+
812
+ def set_front_matter_fields(ssp: SecurityPlan, result: dict, custom_fields_basic_map: dict) -> list:
813
+ """
814
+ parse the front matter custom fields
815
+ and return a list of field values to be saved
816
+
817
+ :param SecurityPlan ssp: RegScale Security Plan object
818
+ :param dict result: response from CSAM
819
+ :param dict custom_fields_basic_map: map of basic custom fields
820
+ :return: list of dictionaries with field values
821
+ :return_type: list
822
+ """
823
+ custom_fields_financial_list = [
824
+ "Financial System",
825
+ "omb Exhibit",
826
+ "Investment Name",
827
+ "Portfolio",
828
+ "Prior Fy Funding",
829
+ "Current Fy Funding",
830
+ "Next Fy Funding",
831
+ "Funding Import Status",
832
+ ]
833
+
834
+ custom_fields_financial_map = FormFieldValue.check_custom_fields(
835
+ custom_fields_financial_list, "securityplans", SSP_FINANCIAL_TAB
836
+ )
837
+
838
+ custom_fields_map = {
839
+ "acronym": "acronym",
840
+ "classification": "Classification",
841
+ "fismaReportable": "FISMA Reportable",
842
+ "contractorSystem": "Contractor System",
843
+ "criticalInfrastructure": "Critical Infrastructure",
844
+ "missionCritical": "Mission Essential",
845
+ "uiiCode": "uiiCode",
846
+ }
847
+ custom_fields_fin_map = {
848
+ "financialSystem": "Financial System",
849
+ "ombExhibit": "omb Exhibit",
850
+ "investmentName": "Investment Name",
851
+ "portfolio": "Portfolio",
852
+ "priorFyFunding": "Prior Fy Funding",
853
+ "currentFyFunding": "Current Fy Funding",
854
+ "nextFyFunding": "Next Fy Funding",
855
+ "fundingImportStatus": "Funding Import Status",
856
+ }
857
+
858
+ # Pre-compute field mappings to avoid nested lookups
859
+ basic_field_mapping = {
860
+ field: custom_fields_basic_map[mapped_name]
861
+ for field, mapped_name in custom_fields_map.items()
862
+ if mapped_name in custom_fields_basic_map and field in result
863
+ }
864
+
865
+ financial_field_mapping = {
866
+ field: custom_fields_financial_map[mapped_name]
867
+ for field, mapped_name in custom_fields_fin_map.items()
868
+ if mapped_name in custom_fields_financial_map and field in result
869
+ }
870
+
871
+ # Start with required ID fields
872
+ field_values = [
873
+ {
874
+ "record_id": ssp.id,
875
+ "record_module": "securityplans",
876
+ "form_field_id": custom_fields_basic_map[FISMA_FIELD_NAME],
877
+ "field_value": str(result["externalId"]),
878
+ },
879
+ {
880
+ "record_id": ssp.id,
881
+ "record_module": "securityplans",
882
+ "form_field_id": custom_fields_basic_map[CSAM_FIELD_NAME],
883
+ "field_value": str(result["id"]),
884
+ },
885
+ ]
886
+
887
+ # Process basic tab fields
888
+ field_values.extend(_create_basic_field_values(ssp.id, result, basic_field_mapping))
889
+
890
+ # Process financial tab fields
891
+ field_values.extend(_create_financial_field_values(ssp.id, result, financial_field_mapping))
892
+
893
+ return field_values
894
+
895
+
896
+ def _create_basic_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
897
+ """Helper function to create basic field values with proper type handling"""
898
+ field_values = []
899
+ for field, field_id in field_mapping.items():
900
+ value = result.get(field)
901
+ if isinstance(value, bool):
902
+ field_value = "Yes" if value else "No"
903
+ else:
904
+ field_value = str(value)
905
+
906
+ field_values.append(
907
+ {
908
+ "record_id": record_id,
909
+ "record_module": "securityplans",
910
+ "form_field_id": field_id,
911
+ "field_value": field_value,
912
+ }
913
+ )
914
+ return field_values
915
+
916
+
917
+ def _create_financial_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
918
+ """Helper function to create financial field values with proper handling of funding fields"""
919
+ funding_fields = ["priorFyFunding", "currentFyFunding", "nextFyFunding"]
920
+
921
+ field_values = []
922
+ for field, field_id in field_mapping.items():
923
+ value = result.get(field)
924
+ # Handle blank dollar values
925
+ if field in funding_fields:
926
+ field_value = str(value) if value else "0"
927
+ else:
928
+ field_value = str(value)
929
+
930
+ field_values.append(
931
+ {
932
+ "record_id": record_id,
933
+ "record_module": "securityplans",
934
+ "form_field_id": field_id,
935
+ "field_value": field_value,
936
+ }
937
+ )
938
+ return field_values