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
@@ -4,12 +4,13 @@
4
4
  # standard python imports
5
5
  import logging
6
6
  from enum import Enum
7
- from typing import Any, Callable, Dict, List, Optional, Union, TypeVar
7
+ from functools import lru_cache
8
+ from typing import Any, Callable, Dict, List, Optional, Union
8
9
  from urllib.parse import urljoin
9
10
 
10
11
  import requests
11
12
  from lxml.etree import Element
12
- from pydantic import ConfigDict, Field
13
+ from pydantic import ConfigDict, Field, field_validator
13
14
 
14
15
  from regscale.core.app.api import Api
15
16
  from regscale.core.app.application import Application
@@ -19,7 +20,6 @@ from regscale.models.regscale_models.implementation_role import ImplementationRo
19
20
  from regscale.models.regscale_models.regscale_model import RegScaleModel
20
21
  from regscale.models.regscale_models.security_control import SecurityControl
21
22
 
22
-
23
23
  logger = logging.getLogger("regscale")
24
24
  PATCH_CONTENT_TYPE = "application/json-patch+json"
25
25
 
@@ -41,6 +41,8 @@ class ControlImplementationStatus(str, Enum):
41
41
 
42
42
 
43
43
  class ImplementationControlOrigin(str, Enum):
44
+ """Control Implementation Origination"""
45
+
44
46
  SERVICE_PROVIDER_CORPORATE = "Service Provider Corporate"
45
47
  SERVICE_PROVIDER_SYSTEM = "Service Provider System Specific"
46
48
  SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid (Corporate and System Specific)"
@@ -63,6 +65,7 @@ class ControlImplementationOrigin(str, Enum):
63
65
  CustomerConfigured = "Customer Configured"
64
66
  CustomerProvided = "Customer"
65
67
  Inherited = "Inherited"
68
+ NotApplicable = "Not Applicable"
66
69
 
67
70
 
68
71
  class ControlImplementation(RegScaleModel):
@@ -74,9 +77,10 @@ class ControlImplementation(RegScaleModel):
74
77
  _get_objects_for_list = True
75
78
 
76
79
  controlOwnerId: str = Field(default_factory=RegScaleModel.get_user_id)
80
+ controlOwnersIds: Optional[List[str]] = Field(default=None)
77
81
  status: str # Required
78
82
  controlID: int # Required foreign key to Security Control
79
- status_lst: List[ControlImplementationStatus] = []
83
+ status_lst: List[ControlImplementationStatus] = Field(default=[], exclude=True)
80
84
  id: int = 0
81
85
  parentId: Optional[int] = None
82
86
  parentModule: Optional[str] = None
@@ -84,7 +88,7 @@ class ControlImplementation(RegScaleModel):
84
88
  createdById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
85
89
  uuid: Optional[str] = None
86
90
  policy: Optional[str] = None
87
- implementation: Optional[str] = None
91
+ implementation: Optional[str] = Field(default="N/A")
88
92
  dateLastAssessed: Optional[str] = None
89
93
  lastAssessmentResult: Optional[str] = None
90
94
  practiceLevel: Optional[str] = None
@@ -104,7 +108,9 @@ class ControlImplementation(RegScaleModel):
104
108
  qiVendorCompliance: Optional[str] = None
105
109
  qiIssues: Optional[str] = None
106
110
  qiOverall: Optional[str] = None
107
- responsibility: Optional[str] = None
111
+ responsibility: str = Field(
112
+ default_factory=lambda: ControlImplementation.get_default_responsibility()
113
+ ) # Required field - Control Origination
108
114
  inheritedControlId: Optional[int] = None
109
115
  inheritedRequirementId: Optional[int] = None
110
116
  inheritedSecurityPlanId: Optional[int] = None
@@ -112,7 +118,7 @@ class ControlImplementation(RegScaleModel):
112
118
  dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
113
119
  lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
114
120
  dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
115
- weight: Optional[int] = None
121
+ weight: Optional[float] = None
116
122
  isPublic: Optional[bool] = True
117
123
  inheritable: Optional[bool] = False
118
124
  systemRoleId: Optional[int] = None
@@ -141,6 +147,20 @@ class ControlImplementation(RegScaleModel):
141
147
  maturityLevel: Optional[str] = None
142
148
  assessmentFrequency: int = 0
143
149
 
150
+ @field_validator("implementation", mode="before")
151
+ @classmethod
152
+ def validate_implementation(cls, v: Optional[str]) -> str:
153
+ """
154
+ Validate implementation field - convert empty strings to 'N/A'.
155
+
156
+ :param Optional[str] v: The implementation value
157
+ :return: The validated implementation value
158
+ :rtype: str
159
+ """
160
+ if v is None or (isinstance(v, str) and v.strip() == ""):
161
+ return "N/A"
162
+ return v
163
+
144
164
  def __str__(self):
145
165
  return f"Control Implementation {self.id}: {self.controlID}"
146
166
 
@@ -153,9 +173,29 @@ class ControlImplementation(RegScaleModel):
153
173
  """
154
174
  self.status_lst = self._get_status_enum()
155
175
 
176
+ # Backwards compatibility: Auto-populate controlOwnersIds if not set but controlOwnerId exists
177
+ if self.controlOwnersIds is None and self.controlOwnerId:
178
+ self.controlOwnersIds = [self.controlOwnerId]
179
+
180
+ # Check if responsibility needs to be set (empty string, None, or default value)
181
+ should_update_responsibility = (
182
+ not self.responsibility # Handles empty string or None
183
+ or self.responsibility == self.get_default_responsibility()
184
+ )
185
+
186
+ if should_update_responsibility:
187
+ if self.parentId and self.parentModule == "securityplans":
188
+ # Try to get a more specific default based on the actual security plan's compliance settings
189
+ better_default = self.get_default_responsibility(parent_id=self.parentId)
190
+ if better_default and better_default != self.responsibility:
191
+ self.responsibility = better_default
192
+ elif not self.responsibility:
193
+ # If still empty/None and no parent info, set to generic default
194
+ self.responsibility = self.get_default_responsibility()
195
+
156
196
  def __setattr__(self, name: str, value: Any) -> None:
157
197
  """
158
- Override __setattr__ to update status_lst when status changes.
198
+ Override __setattr__ to update status_lst when status changes and handle backwards compatibility.
159
199
 
160
200
  :param str name: The attribute name
161
201
  :param Any value: The attribute value
@@ -164,6 +204,130 @@ class ControlImplementation(RegScaleModel):
164
204
  super().__setattr__(name, value)
165
205
  if name == "status":
166
206
  self.status_lst = self._get_status_enum()
207
+ elif name == "controlOwnerId" and value:
208
+ # Backwards compatibility: Auto-populate controlOwnersIds when controlOwnerId is set
209
+ if hasattr(self, "controlOwnersIds") and (
210
+ not hasattr(self, "_controlOwnersIds") or self._controlOwnersIds is None
211
+ ):
212
+ super().__setattr__("controlOwnersIds", [value])
213
+
214
+ @classmethod
215
+ @lru_cache(maxsize=256)
216
+ def get_default_responsibility(
217
+ cls, parent_id: Optional[int] = None, compliance_setting_id: Optional[int] = None
218
+ ) -> str:
219
+ """
220
+ Get default responsibility (control origination) based on compliance settings.
221
+
222
+ Cached for high-performance bulk operations.
223
+
224
+ :param Optional[int] parent_id: The parent security plan ID to get compliance settings from
225
+ :param Optional[int] compliance_setting_id: Specific compliance setting ID override
226
+ :return: Default responsibility string
227
+ :rtype: str
228
+ """
229
+ actual_compliance_setting_id = compliance_setting_id or cls._get_compliance_setting_id_from_parent(parent_id)
230
+
231
+ if actual_compliance_setting_id:
232
+ if responsibility := cls._get_responsibility_from_compliance_settings(actual_compliance_setting_id):
233
+ return responsibility
234
+
235
+ return cls._get_fallback_responsibility(actual_compliance_setting_id)
236
+
237
+ @classmethod
238
+ @lru_cache(maxsize=128)
239
+ def _get_compliance_setting_id_from_parent(cls, parent_id: Optional[int]) -> Optional[int]:
240
+ """
241
+ Get compliance setting ID from parent security plan.
242
+
243
+ Cached to avoid repeated API calls for the same security plan.
244
+ """
245
+ if parent_id is None:
246
+ return None
247
+
248
+ try:
249
+ from regscale.models.regscale_models.security_plan import SecurityPlan
250
+
251
+ security_plan: SecurityPlan = SecurityPlan.get_object(parent_id)
252
+ return security_plan.complianceSettingsId if security_plan else None
253
+ except Exception:
254
+ return None
255
+
256
+ @classmethod
257
+ @lru_cache(maxsize=32)
258
+ def _get_responsibility_from_compliance_settings(cls, compliance_setting_id: int) -> Optional[str]:
259
+ """
260
+ Get default responsibility from compliance settings API using settingsList endpoint.
261
+
262
+ Cached to avoid repeated API calls for the same compliance setting.
263
+ """
264
+ responsibility = ControlImplementationOrigin.NotApplicable.value
265
+ try:
266
+ from regscale.models.regscale_models.compliance_settings import ComplianceSettings
267
+
268
+ if cs_responsibility := ComplianceSettings.get_default_responsibility_for_compliance_setting(
269
+ compliance_setting_id
270
+ ):
271
+ responsibility = cs_responsibility
272
+ except Exception:
273
+ return responsibility
274
+
275
+ return responsibility
276
+
277
+ @classmethod
278
+ def _get_fallback_responsibility(cls, compliance_setting_id: Optional[int] = None) -> str:
279
+ """
280
+ Get intelligent fallback responsibility using framework-specific defaults.
281
+
282
+ :param Optional[int] compliance_setting_id: Compliance setting ID to determine framework type
283
+ :return: Fallback responsibility string
284
+ :rtype: str
285
+ """
286
+ if compliance_setting_id:
287
+ return cls._get_framework_default_responsibility(compliance_setting_id)
288
+
289
+ # Ultimate fallback for unknown compliance settings
290
+ return ControlImplementationOrigin.NotApplicable.value
291
+
292
+ @classmethod
293
+ def _get_framework_default_responsibility(cls, compliance_setting_id: int) -> str:
294
+ """
295
+ Get default responsibility for a specific compliance framework.
296
+
297
+ :param int compliance_setting_id: The compliance setting ID (1=RegScale, 2=FedRAMP, 3=PCI, 4=DoD, 5=CMMC)
298
+ :return: Default responsibility string
299
+ :rtype: str
300
+ """
301
+ try:
302
+ from regscale.models.regscale_models.compliance_settings import ComplianceSettings
303
+
304
+ default_value = ComplianceSettings.get_default_responsibility_for_compliance_setting(compliance_setting_id)
305
+ if default_value:
306
+ return default_value
307
+ except Exception:
308
+ pass
309
+
310
+ # Framework-specific fallbacks if API fails
311
+ fallback_map = {
312
+ 1: "Provider", # RegScale Default
313
+ 2: ImplementationControlOrigin.SERVICE_PROVIDER_CORPORATE.value, # FedRAMP
314
+ 3: ImplementationControlOrigin.SERVICE_PROVIDER_CORPORATE.value, # PCI
315
+ 4: "System-Specific", # DoD
316
+ 5: "Provider", # CMMC
317
+ }
318
+ return fallback_map.get(compliance_setting_id, "Service Provider Corporate")
319
+
320
+ @classmethod
321
+ def clear_responsibility_cache(cls) -> None:
322
+ """
323
+ Clear the responsibility lookup cache.
324
+
325
+ Call this method when compliance settings have been updated to ensure
326
+ fresh data is retrieved from the API.
327
+ """
328
+ cls.get_default_responsibility.cache_clear()
329
+ cls._get_compliance_setting_id_from_parent.cache_clear()
330
+ cls._get_responsibility_from_compliance_settings.cache_clear()
167
331
 
168
332
  @classmethod
169
333
  def _get_additional_endpoints(cls) -> ConfigDict:
@@ -325,15 +489,15 @@ class ControlImplementation(RegScaleModel):
325
489
  :return: A dictionary mapping control IDs to implementation IDs
326
490
  :rtype: Dict[str, int]
327
491
  """
328
- logger.info("Getting control label map by parent...")
492
+ logger.debug("Getting control label map by parent...")
329
493
  response = cls._get_api_handler().get(
330
494
  endpoint=cls.get_endpoint("get_all_by_parent").format(intParentID=parent_id, strModule=parent_module)
331
495
  )
332
496
 
333
497
  if response and response.ok:
334
- logger.info("Fetched control label map by parent successfully.")
498
+ logger.debug("Fetched control label map by parent successfully.")
335
499
  return {parentheses_to_dot(ci["controlName"]): ci["id"] for ci in response.json()}
336
- logger.info("Unable to get control label map by parent.")
500
+ logger.debug("Unable to get control label map by parent.")
337
501
  return {}
338
502
 
339
503
  @classmethod
@@ -345,14 +509,14 @@ class ControlImplementation(RegScaleModel):
345
509
  :return: A dictionary mapping control IDs to implementation IDs
346
510
  :rtype: Dict[int, int]
347
511
  """
348
- logger.info("Getting control id map by parent...")
512
+ logger.debug("Getting control id map by parent...")
349
513
  response = cls._get_api_handler().get(
350
514
  endpoint=cls.get_endpoint("get_all_by_parent").format(intParentID=parent_id, strModule=parent_module)
351
515
  )
352
516
  if response and response.ok:
353
- logger.info("Fetched control id map by parent successfully.")
517
+ logger.debug("Fetched control id map by parent successfully.")
354
518
  return {ci["controlID"]: ci["id"] for ci in response.json()}
355
- logger.info("Unable to get control id map by parent.")
519
+ logger.debug("Unable to get control id map by parent.")
356
520
  return {}
357
521
 
358
522
  @classmethod
@@ -364,14 +528,14 @@ class ControlImplementation(RegScaleModel):
364
528
  :return: A dictionary mapping control IDs to implementation IDs
365
529
  :rtype: Dict[str, int]
366
530
  """
367
- logger.info("Getting control label map by plan...")
531
+ logger.debug("Getting control label map by plan...")
368
532
  response = cls._get_api_handler().get(
369
533
  endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
370
534
  )
371
535
  if response and response.ok:
372
- logger.info("Fetched control label map by plan successfully.")
536
+ logger.debug("Fetched control label map by plan successfully.")
373
537
  return {parentheses_to_dot(ci["control"]["controlId"]): ci["id"] for ci in response.json()}
374
- logger.info("Unable to get control label map by plan.")
538
+ logger.warning("Unable to get control label map by plan.")
375
539
  return {}
376
540
 
377
541
  @classmethod
@@ -428,14 +592,14 @@ class ControlImplementation(RegScaleModel):
428
592
  :return: A dictionary mapping control IDs to implementation IDs
429
593
  :rtype: Dict[int, int]
430
594
  """
431
- logger.info("Getting control id map by plan...")
595
+ logger.debug("Getting control id map by plan...")
432
596
  response = cls._get_api_handler().get(
433
597
  endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
434
598
  )
435
599
  if response and response.ok:
436
- logger.info("Fetched control id map by plan successfully.")
600
+ logger.debug("Fetched control id map by plan successfully.")
437
601
  return {ci["control"]["id"]: ci["id"] for ci in response.json()}
438
- logger.info("Unable to get control id map by plan.")
602
+ logger.warning("Unable to get control id map by plan.")
439
603
  return {}
440
604
 
441
605
  @staticmethod
@@ -723,9 +887,9 @@ class ControlImplementation(RegScaleModel):
723
887
  existing_control_implementations_json = response.json()
724
888
  for cim in existing_control_implementations_json:
725
889
  existing_implementation_dict[cim.get("controlName")] = cim
726
- logger.info(f"Found {len(existing_implementation_dict)} existing control implementations")
890
+ logger.debug(f"Found {len(existing_implementation_dict)} existing control implementations")
727
891
  elif response.status_code == 404:
728
- logger.info(f"No existing control implementations found for {parent_id}")
892
+ logger.debug(f"No existing control implementations found for {parent_id}")
729
893
  else:
730
894
  logger.warning(f"Unable to get existing control implementations. {response.content}")
731
895
  return existing_implementation_dict
@@ -1007,7 +1171,7 @@ class ControlImplementation(RegScaleModel):
1007
1171
  if field_name == "status":
1008
1172
  return [imp_status.value for imp_status in ControlImplementationStatus]
1009
1173
  if field_name == "responsibility":
1010
- return ["Provider", "Customer", "Shared", "Not Applicable"]
1174
+ return [origin.value for origin in ControlImplementationOrigin]
1011
1175
  return cls.get_bool_enums(field_name)
1012
1176
 
1013
1177
  @classmethod
@@ -1054,6 +1218,8 @@ class ControlImplementation(RegScaleModel):
1054
1218
  :return: list GraphQL response from RegScale
1055
1219
  :rtype: list
1056
1220
  """
1221
+ from regscale.core.app.internal.control_editor import _extract_control_owner_display
1222
+
1057
1223
  body = """
1058
1224
  query{
1059
1225
  controlImplementations (skip: 0, take: 50, where: {parentId: {eq: parent_id} parentModule: {eq: "parent_module"}}) {
@@ -1099,22 +1265,25 @@ class ControlImplementation(RegScaleModel):
1099
1265
  moded_item = {}
1100
1266
  moded_item["id"] = item["id"]
1101
1267
  moded_item["controlID"] = item["controlID"]
1102
- moded_item["controlOwnerId"] = (
1103
- str(item["controlOwner"]["lastName"]).strip()
1104
- + ", "
1105
- + str(item["controlOwner"]["firstName"]).strip()
1106
- + " ("
1107
- + str(item["controlOwner"]["userName"]).strip()
1108
- + ")"
1109
- )
1110
- moded_item["controlName"] = item["control"]["controlId"]
1111
- moded_item["controlTitle"] = item["control"]["title"]
1112
- moded_item["description"] = item["control"]["description"]
1113
- moded_item["status"] = item["status"]
1114
- moded_item["policy"] = item["policy"]
1115
- moded_item["implementation"] = item["implementation"]
1116
- moded_item["responsibility"] = item["responsibility"]
1117
- moded_item["inheritable"] = item["inheritable"]
1268
+
1269
+ # Extract control owner display using centralized method
1270
+ moded_item["controlOwnerId"] = _extract_control_owner_display(item)
1271
+
1272
+ # Handle case where control or its fields might be None
1273
+ if item.get("control") and item["control"] is not None:
1274
+ moded_item["controlName"] = item["control"].get("controlId", "")
1275
+ moded_item["controlTitle"] = item["control"].get("title", "")
1276
+ moded_item["description"] = item["control"].get("description", "")
1277
+ else:
1278
+ moded_item["controlName"] = ""
1279
+ moded_item["controlTitle"] = ""
1280
+ moded_item["description"] = ""
1281
+
1282
+ moded_item["status"] = item.get("status", "")
1283
+ moded_item["policy"] = item.get("policy", "")
1284
+ moded_item["implementation"] = item.get("implementation", "")
1285
+ moded_item["responsibility"] = item.get("responsibility", "")
1286
+ moded_item["inheritable"] = item.get("inheritable", False)
1118
1287
  moded_data.append(moded_item)
1119
1288
  return moded_data
1120
1289
  return []
@@ -1187,8 +1356,34 @@ class ControlImplementation(RegScaleModel):
1187
1356
  :return: list of control implementations, or None if not found
1188
1357
  :rtype: Optional[list[dict]]
1189
1358
  """
1190
- endpoint = cls.get_endpoint("get_list_by_parent").format(int_id=regscale_id, str_module=regscale_module)
1191
- response = cls._get_api_handler().get(endpoint=endpoint)
1359
+ query = {
1360
+ "parentID": 0,
1361
+ "module": "",
1362
+ "friendlyName": "",
1363
+ "workbench": "",
1364
+ "base": "",
1365
+ "sort": "sortId",
1366
+ "direction": "Ascending",
1367
+ "simpleSearch": "",
1368
+ "page": 1,
1369
+ "pageSize": 1000,
1370
+ "query": {
1371
+ "id": 0,
1372
+ "viewName": "",
1373
+ "module": "",
1374
+ "scope": "",
1375
+ "createdById": "",
1376
+ "dateCreated": None,
1377
+ "parameters": [],
1378
+ },
1379
+ "groupBy": "",
1380
+ "intDays": 0,
1381
+ "subTab": True,
1382
+ }
1383
+ query["parentId"] = regscale_id
1384
+ query["module"] = regscale_module
1385
+ endpoint = cls.get_endpoint("filter_control_implementations")
1386
+ response = cls._get_api_handler().post(endpoint=endpoint, data=query)
1192
1387
  if response and response.ok:
1193
1388
  return response.json()
1194
1389
  return None
@@ -250,13 +250,82 @@ class ControlObjective(RegScaleModel):
250
250
  return control_objectives
251
251
 
252
252
 
253
+ def _process_objective_ccis(objective: ControlObjective, ccis_to_control_ids: dict[str, set[int]]) -> None:
254
+ """
255
+ Process CCI IDs from a control objective.
256
+
257
+ :param ControlObjective objective: The control objective to process
258
+ :param dict ccis_to_control_ids: Dictionary to update with CCI mappings
259
+ :return: None
260
+ """
261
+ if not objective.otherId:
262
+ return
263
+
264
+ cci_ids = objective.otherId.split(",")
265
+ for cci_id in cci_ids:
266
+ cci_id = cci_id.strip()
267
+ if cci_id and cci_id.startswith("CCI-"):
268
+ ccis_to_control_ids[cci_id].add(objective.securityControlId)
269
+
270
+
271
+ def _fetch_cci_objectives_batch(parent_id: int, skip: int, take: int) -> list[ControlObjective]:
272
+ """
273
+ Fetch a batch of CCI objectives.
274
+
275
+ :param int parent_id: The parent ID
276
+ :param int skip: Number of items to skip
277
+ :param int take: Number of items to take
278
+ :return: List of control objectives
279
+ :rtype: list[ControlObjective]
280
+ """
281
+ return ControlObjective.fetch_control_objectives_by_other_id(
282
+ parent_id=parent_id, other_id_contains="CCI-", skip=skip, take=take
283
+ )
284
+
285
+
253
286
  def map_ccis_to_control_ids(parent_id: int) -> dict:
287
+ """
288
+ Map CCI IDs to control IDs with pagination support.
289
+
290
+ :param int parent_id: The parent ID to fetch objectives for
291
+ :return: Dictionary mapping CCI IDs to sets of control IDs
292
+ :rtype: dict
293
+ """
294
+ import logging
295
+
296
+ logger = logging.getLogger("regscale")
254
297
  ccis_to_control_ids: dict[str, set[int]] = defaultdict(set)
255
- objectives = ControlObjective.fetch_control_objectives_by_other_id(parent_id=parent_id, other_id_contains="CCI-")
256
298
 
257
- for objective in objectives:
258
- cci_ids = objective.otherId.split(",")
259
- for cci_id in cci_ids:
260
- ccis_to_control_ids[cci_id.strip()].add(objective.securityControlId)
299
+ try:
300
+ skip = 0
301
+ take = 50 # Use 50 as RegScale API limit
302
+ total_fetched = 0
303
+ max_iterations = 100 # Increase safety limit since batch size is smaller
304
+
305
+ for _ in range(max_iterations):
306
+ objectives = _fetch_cci_objectives_batch(parent_id, skip, take)
307
+ if not objectives:
308
+ break
309
+
310
+ # Process each objective
311
+ for objective in objectives:
312
+ _process_objective_ccis(objective, ccis_to_control_ids)
313
+
314
+ total_fetched += len(objectives)
315
+
316
+ # Check if we've reached the end
317
+ if len(objectives) < take:
318
+ break
319
+
320
+ skip += take
321
+ else:
322
+ logger.warning(f"Reached max iterations ({max_iterations}). Total fetched: {total_fetched}")
323
+
324
+ except Exception as e:
325
+ logger.debug(f"Error fetching CCI to control map: {e}")
326
+ return {}
327
+
328
+ if ccis_to_control_ids:
329
+ logger.debug(f"Mapped {len(ccis_to_control_ids)} unique CCIs to controls")
261
330
 
262
331
  return ccis_to_control_ids
@@ -385,6 +385,8 @@ class File(BaseModel):
385
385
  file_type_header = "application/gzip"
386
386
  elif file_type == ".msg":
387
387
  file_type_header = "application/vnd.ms-outlook"
388
+ elif file_type == ".jsonl":
389
+ file_type_header = "application/jsonl+json"
388
390
  else:
389
391
  logger = logging.getLogger("regscale")
390
392
  logger.warning(f"Unacceptable file type for upload: {file_type}")
@@ -122,7 +122,6 @@ class FormFieldValue(RegScaleModel):
122
122
  f"The following custom fields are missing:\n \
123
123
  {missing_custom_fields}\n \
124
124
  Load these custom fields in RegScale \
125
- using the file: security_plan_custom_fields.json\
126
125
  and run this command again"
127
126
  )
128
127
 
@@ -132,7 +131,8 @@ class FormFieldValue(RegScaleModel):
132
131
  def save_custom_fields(form_field_values: list):
133
132
  """
134
133
  Populate Custom Fields form a list of dict of
135
- record_id, form_field_id, and field_value
134
+ record_id: int, record_module: str, form_field_id: int,
135
+ and field_value: Any
136
136
 
137
137
  :param list form_field_values: list of custom form
138
138
  fields, values, and the record to which to post
@@ -148,5 +148,7 @@ class FormFieldValue(RegScaleModel):
148
148
  ]
149
149
  if data:
150
150
  FormFieldValue.save_custom_data(
151
- record_id=form_field_value["record_id"], module_name="securityplans", data=data
151
+ record_id=form_field_value.get("record_id"),
152
+ module_name=form_field_value.get("record_module", "securityplans"),
153
+ data=data,
152
154
  )
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Model for Inheritance in the application"""
4
+
5
+ from typing import Optional, Union
6
+
7
+ from pydantic import ConfigDict, Field, field_validator
8
+
9
+ from regscale.core.app.api import Api
10
+ from regscale.core.app.logz import create_logger
11
+ from regscale.core.app.utils.app_utils import get_current_datetime
12
+ from regscale.models.regscale_models.regscale_model import RegScaleModel
13
+
14
+
15
+ class Inheritance(RegScaleModel):
16
+ """
17
+ Inherited Control model
18
+ """
19
+
20
+ _module_slug = "inheritance"
21
+
22
+ id: int = 0
23
+ recordId: int = 0
24
+ recordModule: str
25
+ policyId: Optional[int] = None
26
+ planId: Optional[int] = None
27
+ dateInherited: Optional[str] = Field(default_factory=get_current_datetime)
28
+
29
+ @staticmethod
30
+ def _get_additional_endpoints() -> ConfigDict:
31
+ """
32
+ Get additional endpoints for the Inherited Controls model.
33
+
34
+ :return: A dictionary of additional endpoints
35
+ :rtype: ConfigDict
36
+ """
37
+ return ConfigDict( # type: ignore
38
+ get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}/{strModule}",
39
+ get_all_by_control="/api/{model_slug}/getAllByBaseControl/{control_id}",
40
+ )
41
+
42
+ ## Note: There are no endpoints in this module for
43
+ # get (get_object)
44
+ # post (save/update)