regscale-cli 6.16.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (481) hide show
  1. regscale/__init__.py +1 -0
  2. regscale/airflow/__init__.py +9 -0
  3. regscale/airflow/azure/__init__.py +9 -0
  4. regscale/airflow/azure/cli.py +89 -0
  5. regscale/airflow/azure/upload_dags.py +116 -0
  6. regscale/airflow/click_dags.py +127 -0
  7. regscale/airflow/click_mixins.py +82 -0
  8. regscale/airflow/config.py +25 -0
  9. regscale/airflow/factories/__init__.py +0 -0
  10. regscale/airflow/factories/connections.py +58 -0
  11. regscale/airflow/factories/workflows.py +78 -0
  12. regscale/airflow/hierarchy.py +88 -0
  13. regscale/airflow/operators/__init__.py +0 -0
  14. regscale/airflow/operators/click.py +36 -0
  15. regscale/airflow/sensors/__init__.py +0 -0
  16. regscale/airflow/sensors/sql.py +107 -0
  17. regscale/airflow/sessions/__init__.py +0 -0
  18. regscale/airflow/sessions/sql/__init__.py +3 -0
  19. regscale/airflow/sessions/sql/queries.py +64 -0
  20. regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
  21. regscale/airflow/tasks/__init__.py +0 -0
  22. regscale/airflow/tasks/branches.py +22 -0
  23. regscale/airflow/tasks/cli.py +116 -0
  24. regscale/airflow/tasks/click.py +73 -0
  25. regscale/airflow/tasks/debugging.py +9 -0
  26. regscale/airflow/tasks/groups.py +116 -0
  27. regscale/airflow/tasks/init.py +60 -0
  28. regscale/airflow/tasks/states.py +47 -0
  29. regscale/airflow/tasks/workflows.py +36 -0
  30. regscale/ansible/__init__.py +9 -0
  31. regscale/core/__init__.py +0 -0
  32. regscale/core/app/__init__.py +3 -0
  33. regscale/core/app/api.py +571 -0
  34. regscale/core/app/application.py +665 -0
  35. regscale/core/app/internal/__init__.py +136 -0
  36. regscale/core/app/internal/admin_actions.py +230 -0
  37. regscale/core/app/internal/assessments_editor.py +873 -0
  38. regscale/core/app/internal/catalog.py +316 -0
  39. regscale/core/app/internal/comparison.py +459 -0
  40. regscale/core/app/internal/control_editor.py +571 -0
  41. regscale/core/app/internal/encrypt.py +79 -0
  42. regscale/core/app/internal/evidence.py +1240 -0
  43. regscale/core/app/internal/file_uploads.py +151 -0
  44. regscale/core/app/internal/healthcheck.py +66 -0
  45. regscale/core/app/internal/login.py +305 -0
  46. regscale/core/app/internal/migrations.py +240 -0
  47. regscale/core/app/internal/model_editor.py +1701 -0
  48. regscale/core/app/internal/poam_editor.py +632 -0
  49. regscale/core/app/internal/workflow.py +105 -0
  50. regscale/core/app/logz.py +74 -0
  51. regscale/core/app/utils/XMLIR.py +258 -0
  52. regscale/core/app/utils/__init__.py +0 -0
  53. regscale/core/app/utils/api_handler.py +358 -0
  54. regscale/core/app/utils/app_utils.py +1110 -0
  55. regscale/core/app/utils/catalog_utils/__init__.py +0 -0
  56. regscale/core/app/utils/catalog_utils/common.py +91 -0
  57. regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
  58. regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
  59. regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
  60. regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
  61. regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
  62. regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
  63. regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
  64. regscale/core/app/utils/file_utils.py +238 -0
  65. regscale/core/app/utils/parser_utils.py +81 -0
  66. regscale/core/app/utils/pickle_file_handler.py +57 -0
  67. regscale/core/app/utils/regscale_utils.py +319 -0
  68. regscale/core/app/utils/report_utils.py +119 -0
  69. regscale/core/app/utils/variables.py +226 -0
  70. regscale/core/decorators.py +31 -0
  71. regscale/core/lazy_group.py +65 -0
  72. regscale/core/login.py +63 -0
  73. regscale/core/server/__init__.py +0 -0
  74. regscale/core/server/flask_api.py +473 -0
  75. regscale/core/server/helpers.py +373 -0
  76. regscale/core/server/rest.py +64 -0
  77. regscale/core/server/static/css/bootstrap.css +6030 -0
  78. regscale/core/server/static/css/bootstrap.min.css +6 -0
  79. regscale/core/server/static/css/main.css +176 -0
  80. regscale/core/server/static/images/regscale-cli.svg +49 -0
  81. regscale/core/server/static/images/regscale.svg +38 -0
  82. regscale/core/server/templates/base.html +74 -0
  83. regscale/core/server/templates/index.html +43 -0
  84. regscale/core/server/templates/login.html +28 -0
  85. regscale/core/server/templates/make_base64.html +22 -0
  86. regscale/core/server/templates/upload_STIG.html +109 -0
  87. regscale/core/server/templates/upload_STIG_result.html +26 -0
  88. regscale/core/server/templates/upload_ssp.html +144 -0
  89. regscale/core/server/templates/upload_ssp_result.html +128 -0
  90. regscale/core/static/__init__.py +0 -0
  91. regscale/core/static/regex.py +14 -0
  92. regscale/core/utils/__init__.py +117 -0
  93. regscale/core/utils/click_utils.py +13 -0
  94. regscale/core/utils/date.py +238 -0
  95. regscale/core/utils/graphql.py +254 -0
  96. regscale/core/utils/urls.py +23 -0
  97. regscale/dev/__init__.py +6 -0
  98. regscale/dev/analysis.py +454 -0
  99. regscale/dev/cli.py +235 -0
  100. regscale/dev/code_gen.py +492 -0
  101. regscale/dev/dirs.py +69 -0
  102. regscale/dev/docs.py +384 -0
  103. regscale/dev/monitoring.py +26 -0
  104. regscale/dev/profiling.py +216 -0
  105. regscale/exceptions/__init__.py +4 -0
  106. regscale/exceptions/license_exception.py +7 -0
  107. regscale/exceptions/validation_exception.py +9 -0
  108. regscale/integrations/__init__.py +1 -0
  109. regscale/integrations/commercial/__init__.py +486 -0
  110. regscale/integrations/commercial/ad.py +433 -0
  111. regscale/integrations/commercial/amazon/__init__.py +0 -0
  112. regscale/integrations/commercial/amazon/common.py +106 -0
  113. regscale/integrations/commercial/aqua/__init__.py +0 -0
  114. regscale/integrations/commercial/aqua/aqua.py +91 -0
  115. regscale/integrations/commercial/aws/__init__.py +6 -0
  116. regscale/integrations/commercial/aws/cli.py +322 -0
  117. regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
  118. regscale/integrations/commercial/aws/inventory/base.py +64 -0
  119. regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
  120. regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
  121. regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
  122. regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
  123. regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
  124. regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
  125. regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
  126. regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
  127. regscale/integrations/commercial/aws/scanner.py +823 -0
  128. regscale/integrations/commercial/azure/__init__.py +0 -0
  129. regscale/integrations/commercial/azure/common.py +32 -0
  130. regscale/integrations/commercial/azure/intune.py +488 -0
  131. regscale/integrations/commercial/azure/scanner.py +49 -0
  132. regscale/integrations/commercial/burp.py +78 -0
  133. regscale/integrations/commercial/cpe.py +144 -0
  134. regscale/integrations/commercial/crowdstrike.py +1117 -0
  135. regscale/integrations/commercial/defender.py +1511 -0
  136. regscale/integrations/commercial/dependabot.py +210 -0
  137. regscale/integrations/commercial/durosuite/__init__.py +0 -0
  138. regscale/integrations/commercial/durosuite/api.py +1546 -0
  139. regscale/integrations/commercial/durosuite/process_devices.py +101 -0
  140. regscale/integrations/commercial/durosuite/scanner.py +637 -0
  141. regscale/integrations/commercial/durosuite/variables.py +21 -0
  142. regscale/integrations/commercial/ecr.py +90 -0
  143. regscale/integrations/commercial/gcp/__init__.py +237 -0
  144. regscale/integrations/commercial/gcp/auth.py +96 -0
  145. regscale/integrations/commercial/gcp/control_tests.py +238 -0
  146. regscale/integrations/commercial/gcp/variables.py +18 -0
  147. regscale/integrations/commercial/gitlab.py +332 -0
  148. regscale/integrations/commercial/grype.py +165 -0
  149. regscale/integrations/commercial/ibm.py +90 -0
  150. regscale/integrations/commercial/import_all/__init__.py +0 -0
  151. regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
  152. regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
  153. regscale/integrations/commercial/jira.py +1046 -0
  154. regscale/integrations/commercial/mappings/__init__.py +0 -0
  155. regscale/integrations/commercial/mappings/csf_controls.json +713 -0
  156. regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
  157. regscale/integrations/commercial/nessus/__init__.py +0 -0
  158. regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
  159. regscale/integrations/commercial/nessus/scanner.py +416 -0
  160. regscale/integrations/commercial/nexpose.py +90 -0
  161. regscale/integrations/commercial/okta.py +798 -0
  162. regscale/integrations/commercial/opentext/__init__.py +0 -0
  163. regscale/integrations/commercial/opentext/click.py +99 -0
  164. regscale/integrations/commercial/opentext/scanner.py +143 -0
  165. regscale/integrations/commercial/prisma.py +91 -0
  166. regscale/integrations/commercial/qualys.py +1462 -0
  167. regscale/integrations/commercial/salesforce.py +980 -0
  168. regscale/integrations/commercial/sap/__init__.py +0 -0
  169. regscale/integrations/commercial/sap/click.py +31 -0
  170. regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
  171. regscale/integrations/commercial/sap/sysdig/click.py +57 -0
  172. regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
  173. regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
  174. regscale/integrations/commercial/sap/tenable/click.py +49 -0
  175. regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
  176. regscale/integrations/commercial/servicenow.py +1756 -0
  177. regscale/integrations/commercial/sicura/__init__.py +0 -0
  178. regscale/integrations/commercial/sicura/api.py +855 -0
  179. regscale/integrations/commercial/sicura/commands.py +73 -0
  180. regscale/integrations/commercial/sicura/scanner.py +481 -0
  181. regscale/integrations/commercial/sicura/variables.py +16 -0
  182. regscale/integrations/commercial/snyk.py +90 -0
  183. regscale/integrations/commercial/sonarcloud.py +260 -0
  184. regscale/integrations/commercial/sqlserver.py +369 -0
  185. regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
  186. regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
  187. regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
  188. regscale/integrations/commercial/stigv2/__init__.py +0 -0
  189. regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
  190. regscale/integrations/commercial/stigv2/click_commands.py +95 -0
  191. regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
  192. regscale/integrations/commercial/synqly/__init__.py +0 -0
  193. regscale/integrations/commercial/synqly/assets.py +46 -0
  194. regscale/integrations/commercial/synqly/ticketing.py +132 -0
  195. regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
  196. regscale/integrations/commercial/synqly_jira.py +840 -0
  197. regscale/integrations/commercial/tenablev2/__init__.py +0 -0
  198. regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
  199. regscale/integrations/commercial/tenablev2/click.py +1584 -0
  200. regscale/integrations/commercial/tenablev2/scanner.py +504 -0
  201. regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
  202. regscale/integrations/commercial/tenablev2/utils.py +78 -0
  203. regscale/integrations/commercial/tenablev2/variables.py +17 -0
  204. regscale/integrations/commercial/trivy.py +162 -0
  205. regscale/integrations/commercial/veracode.py +96 -0
  206. regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
  207. regscale/integrations/commercial/wizv2/__init__.py +0 -0
  208. regscale/integrations/commercial/wizv2/click.py +429 -0
  209. regscale/integrations/commercial/wizv2/constants.py +1001 -0
  210. regscale/integrations/commercial/wizv2/issue.py +361 -0
  211. regscale/integrations/commercial/wizv2/models.py +112 -0
  212. regscale/integrations/commercial/wizv2/parsers.py +339 -0
  213. regscale/integrations/commercial/wizv2/sbom.py +115 -0
  214. regscale/integrations/commercial/wizv2/scanner.py +416 -0
  215. regscale/integrations/commercial/wizv2/utils.py +796 -0
  216. regscale/integrations/commercial/wizv2/variables.py +39 -0
  217. regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
  218. regscale/integrations/commercial/xray.py +91 -0
  219. regscale/integrations/integration/__init__.py +2 -0
  220. regscale/integrations/integration/integration.py +26 -0
  221. regscale/integrations/integration/inventory.py +17 -0
  222. regscale/integrations/integration/issue.py +100 -0
  223. regscale/integrations/integration_override.py +149 -0
  224. regscale/integrations/public/__init__.py +103 -0
  225. regscale/integrations/public/cisa.py +641 -0
  226. regscale/integrations/public/criticality_updater.py +70 -0
  227. regscale/integrations/public/emass.py +411 -0
  228. regscale/integrations/public/emass_slcm_import.py +697 -0
  229. regscale/integrations/public/fedramp/__init__.py +0 -0
  230. regscale/integrations/public/fedramp/appendix_parser.py +548 -0
  231. regscale/integrations/public/fedramp/click.py +479 -0
  232. regscale/integrations/public/fedramp/components.py +714 -0
  233. regscale/integrations/public/fedramp/docx_parser.py +259 -0
  234. regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
  235. regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
  236. regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
  237. regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
  238. regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
  239. regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
  240. regscale/integrations/public/fedramp/import_workbook.py +495 -0
  241. regscale/integrations/public/fedramp/inventory_items.py +244 -0
  242. regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
  243. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
  244. regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
  245. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
  246. regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
  247. regscale/integrations/public/fedramp/mappings/user.py +175 -0
  248. regscale/integrations/public/fedramp/mappings/values.py +141 -0
  249. regscale/integrations/public/fedramp/markdown_parser.py +150 -0
  250. regscale/integrations/public/fedramp/metadata.py +689 -0
  251. regscale/integrations/public/fedramp/models/__init__.py +59 -0
  252. regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
  253. regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
  254. regscale/integrations/public/fedramp/parts_mapper.py +107 -0
  255. regscale/integrations/public/fedramp/poam/__init__.py +0 -0
  256. regscale/integrations/public/fedramp/poam/scanner.py +851 -0
  257. regscale/integrations/public/fedramp/properties.py +201 -0
  258. regscale/integrations/public/fedramp/reporting.py +84 -0
  259. regscale/integrations/public/fedramp/resources.py +496 -0
  260. regscale/integrations/public/fedramp/rosetta.py +110 -0
  261. regscale/integrations/public/fedramp/ssp_logger.py +87 -0
  262. regscale/integrations/public/fedramp/system_characteristics.py +922 -0
  263. regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
  264. regscale/integrations/public/fedramp/system_implementation.py +190 -0
  265. regscale/integrations/public/fedramp/xml_utils.py +87 -0
  266. regscale/integrations/public/nist_catalog.py +275 -0
  267. regscale/integrations/public/oscal.py +1946 -0
  268. regscale/integrations/public/otx.py +169 -0
  269. regscale/integrations/scanner_integration.py +2692 -0
  270. regscale/integrations/variables.py +25 -0
  271. regscale/models/__init__.py +7 -0
  272. regscale/models/app_models/__init__.py +5 -0
  273. regscale/models/app_models/catalog_compare.py +213 -0
  274. regscale/models/app_models/click.py +252 -0
  275. regscale/models/app_models/datetime_encoder.py +21 -0
  276. regscale/models/app_models/import_validater.py +321 -0
  277. regscale/models/app_models/mapping.py +260 -0
  278. regscale/models/app_models/pipeline.py +37 -0
  279. regscale/models/click_models.py +413 -0
  280. regscale/models/config.py +154 -0
  281. regscale/models/email_style.css +67 -0
  282. regscale/models/hierarchy.py +8 -0
  283. regscale/models/inspect_models.py +79 -0
  284. regscale/models/integration_models/__init__.py +0 -0
  285. regscale/models/integration_models/amazon_models/__init__.py +0 -0
  286. regscale/models/integration_models/amazon_models/inspector.py +262 -0
  287. regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
  288. regscale/models/integration_models/aqua.py +247 -0
  289. regscale/models/integration_models/azure_alerts.py +255 -0
  290. regscale/models/integration_models/base64.py +23 -0
  291. regscale/models/integration_models/burp.py +433 -0
  292. regscale/models/integration_models/burp_models.py +128 -0
  293. regscale/models/integration_models/cisa_kev_data.json +19333 -0
  294. regscale/models/integration_models/defender_data.py +93 -0
  295. regscale/models/integration_models/defenderimport.py +143 -0
  296. regscale/models/integration_models/drf.py +443 -0
  297. regscale/models/integration_models/ecr_models/__init__.py +0 -0
  298. regscale/models/integration_models/ecr_models/data.py +69 -0
  299. regscale/models/integration_models/ecr_models/ecr.py +239 -0
  300. regscale/models/integration_models/flat_file_importer.py +1079 -0
  301. regscale/models/integration_models/grype_import.py +247 -0
  302. regscale/models/integration_models/ibm.py +126 -0
  303. regscale/models/integration_models/implementation_results.py +85 -0
  304. regscale/models/integration_models/nexpose.py +140 -0
  305. regscale/models/integration_models/prisma.py +202 -0
  306. regscale/models/integration_models/qualys.py +720 -0
  307. regscale/models/integration_models/qualys_scanner.py +160 -0
  308. regscale/models/integration_models/sbom/__init__.py +0 -0
  309. regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
  310. regscale/models/integration_models/send_reminders.py +620 -0
  311. regscale/models/integration_models/snyk.py +155 -0
  312. regscale/models/integration_models/synqly_models/__init__.py +0 -0
  313. regscale/models/integration_models/synqly_models/capabilities.json +1 -0
  314. regscale/models/integration_models/synqly_models/connector_types.py +22 -0
  315. regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
  316. regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
  317. regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
  318. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
  319. regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
  320. regscale/models/integration_models/synqly_models/param.py +72 -0
  321. regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
  322. regscale/models/integration_models/synqly_models/tenants.py +39 -0
  323. regscale/models/integration_models/tenable_models/__init__.py +0 -0
  324. regscale/models/integration_models/tenable_models/integration.py +187 -0
  325. regscale/models/integration_models/tenable_models/models.py +513 -0
  326. regscale/models/integration_models/trivy_import.py +231 -0
  327. regscale/models/integration_models/veracode.py +217 -0
  328. regscale/models/integration_models/xray.py +135 -0
  329. regscale/models/locking.py +100 -0
  330. regscale/models/platform.py +110 -0
  331. regscale/models/regscale_models/__init__.py +67 -0
  332. regscale/models/regscale_models/assessment.py +570 -0
  333. regscale/models/regscale_models/assessment_plan.py +52 -0
  334. regscale/models/regscale_models/asset.py +567 -0
  335. regscale/models/regscale_models/asset_mapping.py +190 -0
  336. regscale/models/regscale_models/case.py +42 -0
  337. regscale/models/regscale_models/catalog.py +261 -0
  338. regscale/models/regscale_models/cci.py +46 -0
  339. regscale/models/regscale_models/change.py +167 -0
  340. regscale/models/regscale_models/checklist.py +372 -0
  341. regscale/models/regscale_models/comment.py +49 -0
  342. regscale/models/regscale_models/compliance_settings.py +112 -0
  343. regscale/models/regscale_models/component.py +412 -0
  344. regscale/models/regscale_models/component_mapping.py +65 -0
  345. regscale/models/regscale_models/control.py +38 -0
  346. regscale/models/regscale_models/control_implementation.py +1128 -0
  347. regscale/models/regscale_models/control_objective.py +261 -0
  348. regscale/models/regscale_models/control_parameter.py +100 -0
  349. regscale/models/regscale_models/control_test.py +34 -0
  350. regscale/models/regscale_models/control_test_plan.py +75 -0
  351. regscale/models/regscale_models/control_test_result.py +52 -0
  352. regscale/models/regscale_models/custom_field.py +245 -0
  353. regscale/models/regscale_models/data.py +109 -0
  354. regscale/models/regscale_models/data_center.py +40 -0
  355. regscale/models/regscale_models/deviation.py +203 -0
  356. regscale/models/regscale_models/email.py +97 -0
  357. regscale/models/regscale_models/evidence.py +47 -0
  358. regscale/models/regscale_models/evidence_mapping.py +40 -0
  359. regscale/models/regscale_models/facility.py +59 -0
  360. regscale/models/regscale_models/file.py +382 -0
  361. regscale/models/regscale_models/filetag.py +37 -0
  362. regscale/models/regscale_models/form_field_value.py +94 -0
  363. regscale/models/regscale_models/group.py +169 -0
  364. regscale/models/regscale_models/implementation_objective.py +335 -0
  365. regscale/models/regscale_models/implementation_option.py +275 -0
  366. regscale/models/regscale_models/implementation_role.py +33 -0
  367. regscale/models/regscale_models/incident.py +177 -0
  368. regscale/models/regscale_models/interconnection.py +43 -0
  369. regscale/models/regscale_models/issue.py +1176 -0
  370. regscale/models/regscale_models/leveraged_authorization.py +125 -0
  371. regscale/models/regscale_models/line_of_inquiry.py +52 -0
  372. regscale/models/regscale_models/link.py +205 -0
  373. regscale/models/regscale_models/meta_data.py +64 -0
  374. regscale/models/regscale_models/mixins/__init__.py +0 -0
  375. regscale/models/regscale_models/mixins/parent_cache.py +124 -0
  376. regscale/models/regscale_models/module.py +224 -0
  377. regscale/models/regscale_models/modules.py +191 -0
  378. regscale/models/regscale_models/objective.py +14 -0
  379. regscale/models/regscale_models/parameter.py +87 -0
  380. regscale/models/regscale_models/ports_protocol.py +81 -0
  381. regscale/models/regscale_models/privacy.py +89 -0
  382. regscale/models/regscale_models/profile.py +50 -0
  383. regscale/models/regscale_models/profile_link.py +68 -0
  384. regscale/models/regscale_models/profile_mapping.py +124 -0
  385. regscale/models/regscale_models/project.py +63 -0
  386. regscale/models/regscale_models/property.py +278 -0
  387. regscale/models/regscale_models/question.py +85 -0
  388. regscale/models/regscale_models/questionnaire.py +87 -0
  389. regscale/models/regscale_models/questionnaire_instance.py +177 -0
  390. regscale/models/regscale_models/rbac.py +132 -0
  391. regscale/models/regscale_models/reference.py +86 -0
  392. regscale/models/regscale_models/regscale_model.py +1643 -0
  393. regscale/models/regscale_models/requirement.py +29 -0
  394. regscale/models/regscale_models/risk.py +274 -0
  395. regscale/models/regscale_models/sbom.py +54 -0
  396. regscale/models/regscale_models/scan_history.py +436 -0
  397. regscale/models/regscale_models/search.py +53 -0
  398. regscale/models/regscale_models/security_control.py +132 -0
  399. regscale/models/regscale_models/security_plan.py +204 -0
  400. regscale/models/regscale_models/software_inventory.py +159 -0
  401. regscale/models/regscale_models/stake_holder.py +64 -0
  402. regscale/models/regscale_models/stig.py +647 -0
  403. regscale/models/regscale_models/supply_chain.py +152 -0
  404. regscale/models/regscale_models/system_role.py +188 -0
  405. regscale/models/regscale_models/system_role_external_assignment.py +40 -0
  406. regscale/models/regscale_models/tag.py +37 -0
  407. regscale/models/regscale_models/tag_mapping.py +19 -0
  408. regscale/models/regscale_models/task.py +133 -0
  409. regscale/models/regscale_models/threat.py +196 -0
  410. regscale/models/regscale_models/user.py +175 -0
  411. regscale/models/regscale_models/user_group.py +55 -0
  412. regscale/models/regscale_models/vulnerability.py +242 -0
  413. regscale/models/regscale_models/vulnerability_mapping.py +162 -0
  414. regscale/models/regscale_models/workflow.py +55 -0
  415. regscale/models/regscale_models/workflow_action.py +34 -0
  416. regscale/models/regscale_models/workflow_instance.py +269 -0
  417. regscale/models/regscale_models/workflow_instance_step.py +114 -0
  418. regscale/models/regscale_models/workflow_template.py +58 -0
  419. regscale/models/regscale_models/workflow_template_step.py +45 -0
  420. regscale/regscale.py +815 -0
  421. regscale/utils/__init__.py +7 -0
  422. regscale/utils/b64conversion.py +14 -0
  423. regscale/utils/click_utils.py +118 -0
  424. regscale/utils/decorators.py +48 -0
  425. regscale/utils/dict_utils.py +59 -0
  426. regscale/utils/files.py +79 -0
  427. regscale/utils/fxns.py +30 -0
  428. regscale/utils/graphql_client.py +113 -0
  429. regscale/utils/lists.py +16 -0
  430. regscale/utils/numbers.py +12 -0
  431. regscale/utils/shell.py +148 -0
  432. regscale/utils/string.py +121 -0
  433. regscale/utils/synqly_utils.py +165 -0
  434. regscale/utils/threading/__init__.py +8 -0
  435. regscale/utils/threading/threadhandler.py +131 -0
  436. regscale/utils/threading/threadsafe_counter.py +47 -0
  437. regscale/utils/threading/threadsafe_dict.py +242 -0
  438. regscale/utils/threading/threadsafe_list.py +83 -0
  439. regscale/utils/version.py +104 -0
  440. regscale/validation/__init__.py +0 -0
  441. regscale/validation/address.py +37 -0
  442. regscale/validation/record.py +48 -0
  443. regscale/visualization/__init__.py +5 -0
  444. regscale/visualization/click.py +34 -0
  445. regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
  446. regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
  447. regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
  448. regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
  449. regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
  450. regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
  451. tests/fixtures/__init__.py +2 -0
  452. tests/fixtures/api.py +87 -0
  453. tests/fixtures/models.py +91 -0
  454. tests/fixtures/test_fixture.py +144 -0
  455. tests/mocks/__init__.py +0 -0
  456. tests/mocks/objects.py +3 -0
  457. tests/mocks/response.py +32 -0
  458. tests/mocks/xml.py +13 -0
  459. tests/regscale/__init__.py +0 -0
  460. tests/regscale/core/__init__.py +0 -0
  461. tests/regscale/core/test_api.py +232 -0
  462. tests/regscale/core/test_app.py +406 -0
  463. tests/regscale/core/test_login.py +37 -0
  464. tests/regscale/core/test_logz.py +66 -0
  465. tests/regscale/core/test_sbom_generator.py +87 -0
  466. tests/regscale/core/test_validation_utils.py +163 -0
  467. tests/regscale/core/test_version.py +78 -0
  468. tests/regscale/models/__init__.py +0 -0
  469. tests/regscale/models/test_asset.py +71 -0
  470. tests/regscale/models/test_config.py +26 -0
  471. tests/regscale/models/test_control_implementation.py +27 -0
  472. tests/regscale/models/test_import.py +97 -0
  473. tests/regscale/models/test_issue.py +36 -0
  474. tests/regscale/models/test_mapping.py +52 -0
  475. tests/regscale/models/test_platform.py +31 -0
  476. tests/regscale/models/test_regscale_model.py +346 -0
  477. tests/regscale/models/test_report.py +32 -0
  478. tests/regscale/models/test_tenable_integrations.py +118 -0
  479. tests/regscale/models/test_user_model.py +121 -0
  480. tests/regscale/test_about.py +19 -0
  481. tests/regscale/test_authorization.py +65 -0
@@ -0,0 +1,1128 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Model for a RegScale Security Control Implementation"""
4
+ # standard python imports
5
+ import logging
6
+ from enum import Enum
7
+ from typing import Any, Callable, Dict, List, Optional, Union
8
+ from urllib.parse import urljoin
9
+
10
+ import requests
11
+ from lxml.etree import Element
12
+ from pydantic import ConfigDict, Field
13
+
14
+ from regscale.core.app.api import Api
15
+ from regscale.core.app.application import Application
16
+ from regscale.core.app.utils.app_utils import get_current_datetime, remove_keys
17
+ from regscale.core.app.utils.catalog_utils.common import parentheses_to_dot
18
+ from regscale.models.regscale_models.implementation_role import ImplementationRole
19
+ from regscale.models.regscale_models.regscale_model import RegScaleModel
20
+ from regscale.models.regscale_models.security_control import SecurityControl
21
+
22
+ logger = logging.getLogger("regscale")
23
+ PATCH_CONTENT_TYPE = "application/json-patch+json"
24
+
25
+
26
+ class ControlImplementationStatus(str, Enum):
27
+ """Control Implementation Status"""
28
+
29
+ FullyImplemented = "Fully Implemented"
30
+ Implemented = "Implemented"
31
+ NotImplemented = "Not Implemented"
32
+ PartiallyImplemented = "Partially Implemented"
33
+ InRemediation = "In Remediation"
34
+ Inherited = "Inherited"
35
+ NA = "Not Applicable"
36
+ Planned = "Planned"
37
+ Archived = "Archived"
38
+ RiskAccepted = "Risk Accepted"
39
+ Alternative = "Alternate Implementation"
40
+
41
+
42
+ class ControlImplementationOrigin(str, Enum):
43
+ """Control Origination"""
44
+
45
+ Provider = "Provider"
46
+ ProviderSS = "Provider (System Specific)"
47
+ CustomerConfigured = "Customer Configured"
48
+ CustomerProvided = "Customer"
49
+ Inherited = "Inherited"
50
+
51
+
52
+ class ControlImplementation(RegScaleModel):
53
+ """Control Implementation"""
54
+
55
+ _module_slug = "controlImplementation"
56
+ _module_string = "controls"
57
+ _get_objects_for_list = True
58
+
59
+ controlOwnerId: str = Field(default_factory=RegScaleModel.get_user_id)
60
+ status: str # Required
61
+ controlID: int # Required foreign key to Security Control
62
+ status_lst: List[ControlImplementationStatus] = []
63
+ id: int = 0
64
+ parentId: Optional[int] = None
65
+ parentModule: Optional[str] = None
66
+ control: Union[SecurityControl, dict, None] = None # Security Control object
67
+ createdById: Optional[str] = Field(default_factory=get_current_datetime)
68
+ uuid: Optional[str] = None
69
+ policy: Optional[str] = None
70
+ implementation: Optional[str] = None
71
+ dateLastAssessed: Optional[str] = None
72
+ lastAssessmentResult: Optional[str] = None
73
+ practiceLevel: Optional[str] = None
74
+ processLevel: Optional[str] = None
75
+ cyberFunction: Optional[str] = None
76
+ implementationType: Optional[str] = None
77
+ implementationMethod: Optional[str] = None
78
+ qdWellDesigned: Optional[str] = None
79
+ qdProcedures: Optional[str] = None
80
+ qdSegregation: Optional[str] = None
81
+ qdFlowdown: Optional[str] = None
82
+ qdAutomated: Optional[str] = None
83
+ qdOverall: Optional[str] = None
84
+ qiResources: Optional[str] = None
85
+ qiMaturity: Optional[str] = None
86
+ qiReporting: Optional[str] = None
87
+ qiVendorCompliance: Optional[str] = None
88
+ qiIssues: Optional[str] = None
89
+ qiOverall: Optional[str] = None
90
+ responsibility: Optional[str] = None
91
+ inheritedControlId: Optional[int] = None
92
+ inheritedRequirementId: Optional[int] = None
93
+ inheritedSecurityPlanId: Optional[int] = None
94
+ inheritedPolicyId: Optional[int] = None
95
+ dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
96
+ lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
97
+ dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
98
+ weight: Optional[int] = None
99
+ isPublic: Optional[bool] = True
100
+ inheritable: Optional[bool] = False
101
+ systemRoleId: Optional[int] = None
102
+ plannedImplementationDate: Optional[str] = None
103
+ stepsToImplement: Optional[str] = None
104
+ exclusionJustification: Optional[str] = None
105
+ bBaseline: Optional[bool] = False
106
+ bInherited: Optional[bool] = False
107
+ bOverlay: Optional[bool] = False
108
+ bTailored: Optional[bool] = False
109
+ bStatusImplemented: Optional[bool] = False
110
+ bStatusPartiallyImplemented: Optional[bool] = False
111
+ bStatusPlanned: Optional[bool] = False
112
+ bStatusAlternative: Optional[bool] = False
113
+ bStatusNotApplicable: Optional[bool] = False
114
+ bServiceProviderCorporate: Optional[bool] = False
115
+ bServiceProviderSystemSpecific: Optional[bool] = False
116
+ bServiceProviderHybrid: Optional[bool] = False
117
+ bConfiguredByCustomer: Optional[bool] = False
118
+ bProvidedByCustomer: Optional[bool] = False
119
+ bShared: Optional[bool] = False
120
+ bInheritedFedrampAuthorization: Optional[bool] = False
121
+ cloudImplementation: Optional[str] = None
122
+ customerImplementation: Optional[str] = None
123
+ controlSource: Optional[str] = "Baseline"
124
+ maturityLevel: Optional[str] = None
125
+ assessmentFrequency: int = 0
126
+
127
+ def __str__(self):
128
+ return f"Control Implementation {self.id}: {self.controlID}"
129
+
130
+ def model_post_init(self, __context: Any) -> None:
131
+ """
132
+ Model post init method
133
+
134
+ :param Any __context: The context
135
+ :return: None
136
+ """
137
+ self.status_lst = self._get_status_enum()
138
+
139
+ def __setattr__(self, name: str, value: Any) -> None:
140
+ """
141
+ Override __setattr__ to update status_lst when status changes.
142
+
143
+ :param str name: The attribute name
144
+ :param Any value: The attribute value
145
+ :return: None
146
+ """
147
+ super().__setattr__(name, value)
148
+ if name == "status":
149
+ self.status_lst = self._get_status_enum()
150
+
151
+ @classmethod
152
+ def _get_additional_endpoints(cls) -> ConfigDict:
153
+ """
154
+ Get additional endpoints for the API.
155
+
156
+ :return: A dictionary of additional endpoints
157
+ :rtype: ConfigDict
158
+ """
159
+ return ConfigDict( # type: ignore
160
+ get_all_count="/api/{model_slug}/getAllCount",
161
+ get_filtered_list="/api/{model_slug}/getFilteredList/{str_find}",
162
+ get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}/{strModule}",
163
+ get_all_by_plan="/api/{model_slug}/getAllByPlan/{int_security_plan}",
164
+ get_all_by_plan_with_controls="/api/{model_slug}/getAllByPlanWithControls/{int_security_plan}",
165
+ get_compliance_history_by_plan="/api/{model_slug}/GetComplianceHistoryByPlan/{int_parent}/{str_module}",
166
+ save_compliance_history_by_plan="/api/{model_slug}/SaveComplianceHistoryByPlan",
167
+ get_all_by_plan_with_objectives="/api/{model_slug}/getAllByPlanWithObjectives/{int_security_plan}",
168
+ get_all_by_component_list="/api/{model_slug}/getAllByComponentList",
169
+ get_mappings_by_security_plan="/api/{model_slug}/getMappingsBySecurityPlan/{int_security_plan}",
170
+ get_list_by_plan="/api/{model_slug}/getListByPlan/{int_security_plan}",
171
+ get_list_by_parent="/api/{model_slug}/getListByParent/{int_id}/{str_module}",
172
+ get_master_assessment_list="/api/{model_slug}/getMasterAssessmentList/{int_parent}/{str_module}",
173
+ get_list_by_parent_control="/api/{model_slug}/getListByParentControl/{parent_control_id}",
174
+ get_sc_list_by_plan="/api/{model_slug}/getSCListByPlan/{int_security_plan}",
175
+ get_inheritance_list_by_plan="/api/{model_slug}/getInheritanceListByPlan/{int_security_plan}",
176
+ get_sc_list_by_component="/api/{model_slug}/getSCListByComponent/{int_component}",
177
+ graph_main_dashboard="/api/{model_slug}/graphMainDashboard/{str_group_by}/{str_mod}",
178
+ export="/api/{model_slug}/export/{int_id}",
179
+ wizard="/api/{model_slug}/wizard/{int_id}/{str_module}",
180
+ get_date_last_assessed_by_parent="/api/{model_slug}/getDateLastAssessedByParent/{int_record}",
181
+ get_date_last_assessed_by_parent_and_module="/api/{model_slug}/getDateLastAssessedByParentAndModule/{str_module}/{int_record}",
182
+ get_date_last_assessed_for_all_assets="/api/{model_slug}/getDateLastAssessedForAllAssets/{int_record}",
183
+ graph_controls_by_date="/api/{model_slug}/graphControlsByDate/{year}",
184
+ get_date_last_assessed_by_control="/api/{model_slug}/getDateLastAssessedByControl/{int_control}",
185
+ get_by_status_and_parent="/api/{model_slug}/getByStatusAndParent/{int_id}",
186
+ get_by_status_and_parent_control="/api/{model_slug}/getByStatusAndParentControl/{int_id}",
187
+ get_by_owner_and_parent="/api/{model_slug}/getByOwnerAndParent/{int_id}",
188
+ get_by_owner_and_parent_control="/api/{model_slug}/getByOwnerAndParentControl/{int_id}",
189
+ get_by_result_and_parent="/api/{model_slug}/getByResultAndParent/{int_id}",
190
+ get_by_result_and_parent_control="/api/{model_slug}/getByResultAndParentControl/{int_id}",
191
+ get_by_process_and_parent="/api/{model_slug}/getByProcessAndParent/{int_id}",
192
+ get_by_practice_and_parent="/api/{model_slug}/getByPracticeAndParent/{int_id}",
193
+ get_by_practice_and_control="/api/{model_slug}/getByPracticeAndControl/{int_id}",
194
+ get_by_process_and_control="/api/{model_slug}/getByProcessAndControl/{int_id}",
195
+ graph="/api/{model_slug}/graph",
196
+ filter_control_implementations="/api/{model_slug}/filterControlImplementations",
197
+ filter_scorecard="/api/{model_slug}/filterScorecard",
198
+ scorecard_count="/api/{model_slug}/ScorecardCount",
199
+ query_by_custom_field="/api/{model_slug}/queryByCustomField/{str_field_name}/{str_value}",
200
+ insert="/api/controlImplementation",
201
+ batch_create="/api/{model_slug}/batchCreate",
202
+ batch_update="/api/{model_slug}/batchUpdate",
203
+ quick_update="/api/{model_slug}/quickUpdate/{id}/{str_status}/{int_weight}/{str_user}",
204
+ dashboard_by_parent="/api/{model_slug}/dashboardByParent/{str_group_by}/{int_id}/{str_module}",
205
+ security_control_dashboard="/api/{model_slug}/securityControlDashboard/{str_group_by}/{int_id}",
206
+ dashboard_by_parent_and_catalogue="/api/{model_slug}/dashboardByParentAndCatalogue/{str_group_by}/{int_id}/{int_cat_id}",
207
+ group_by_family="/api/{model_slug}/groupByFamily/{int_security_plan}",
208
+ dashboard_by_sp="/api/{model_slug}/dashboardBySP/{str_group_by}/{int_security_plan}",
209
+ report="/api/{model_slug}/report/{str_report}",
210
+ get_by_parent="/api/{model_slug}/getByParent/{int_id}/{str_module}",
211
+ get_count_by_parent="/api/{model_slug}/getCountByParent/{int_id}/{str_module}",
212
+ get_all_asset_controls_by_component="/api/{model_slug}/getAllAssetControlsByComponent/{int_id}",
213
+ drilldown_asset_controls_by_component="/api/{model_slug}/drilldownAssetControlsByComponent/{component_id}/{str_field}/{str_value}",
214
+ get_control_context="/api/{model_slug}/getControlContext/{int_control_id}/{int_parent_id}/{str_module}",
215
+ )
216
+
217
+ def find_by_unique(self, **kwargs: dict) -> Optional["ControlImplementation"]:
218
+ """
219
+ Find an object by unique query.
220
+
221
+ :param dict **kwargs: The unique query parameters
222
+ :return: The object or None if not found
223
+ :rtype: Optional[ControlImplementation]
224
+ """
225
+
226
+ for instance in self.get_by_security_control_id(security_control_id=self.controlID):
227
+ return instance
228
+ return None
229
+
230
+ def _get_status_enum(self) -> List["ControlImplementationStatus"]:
231
+ """
232
+ A method to pull the RegScale multiselect status as a list of ControlImplementationStatus.
233
+
234
+ :return: A list of control implementation status
235
+ :rtype: List["ControlImplementationStatus"]
236
+ """
237
+ if not self.status:
238
+ return []
239
+ try:
240
+ return [ControlImplementationStatus(status.strip()) for status in self.status.split(",")]
241
+ except ValueError:
242
+ return []
243
+
244
+ @classmethod
245
+ def get_by_security_control_id(cls, security_control_id: int) -> List["ControlImplementation"]:
246
+ """
247
+ Get a list of control implementations by security control ID.
248
+
249
+ :param int security_control_id: The ID of the security control
250
+ :return: A list of control implementations
251
+ :rtype: List[ControlImplementation]
252
+ """
253
+ response = cls._get_api_handler().get(
254
+ endpoint=cls.get_endpoint("get_by_security_control_id").format(int_security_plan=security_control_id)
255
+ )
256
+ security_controls = []
257
+ if response and response.ok:
258
+ for ci in response.json():
259
+ if ci := cls.get_object(object_id=ci["id"]):
260
+ security_controls.append(ci)
261
+ return security_controls
262
+
263
+ @classmethod
264
+ def get_list_by_plan(cls, plan_id: int) -> List["ControlImplementation"]:
265
+ """
266
+ Get a list of control implementations by plan ID.
267
+
268
+ :param int plan_id: The ID of the plan
269
+ :return: A list of control implementations
270
+ :rtype: List[ControlImplementation]
271
+ """
272
+ response = cls._get_api_handler().get(
273
+ endpoint=cls.get_endpoint("get_list_by_plan").format(int_security_plan=plan_id)
274
+ )
275
+ security_controls = []
276
+ if response and response.ok:
277
+ for ci in response.json():
278
+ if ci := cls.get_object(object_id=ci["id"]):
279
+ security_controls.append(ci)
280
+ return security_controls
281
+
282
+ @classmethod
283
+ def get_control_label_map_by_plan(cls, plan_id: int) -> Dict[str, int]:
284
+ """
285
+ Get a map of control labels to control implementations by plan ID.
286
+
287
+ :param int plan_id: The ID of the plan
288
+ :return: A dictionary mapping control IDs to implementation IDs
289
+ :rtype: Dict[str, int]
290
+ """
291
+ logger.info("Getting control label map by plan...")
292
+ response = cls._get_api_handler().get(
293
+ endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
294
+ )
295
+ if response and response.ok:
296
+ logger.info("Fetched control label map by plan successfully.")
297
+ return {parentheses_to_dot(ci["control"]["controlId"]): ci["id"] for ci in response.json()}
298
+ logger.info("Unable to get control label map by plan.")
299
+ return {}
300
+
301
+ @classmethod
302
+ def fetch_implementation_ids_by_cci(cls, parent_id: int, cci_name: str, skip: int = 0, take: int = 50) -> list[int]:
303
+ """
304
+ Fetch control implementation ids by CCI.
305
+
306
+ :param int parent_id: The ID of the parent
307
+ :param str cci_name: The name of the CCI
308
+ :param int skip: The number of items to skip
309
+ :param int take: The number of items to take
310
+ :return: A list of control implementation IDs
311
+ :rtype: list[int]
312
+ """
313
+
314
+ query = f"""
315
+ query GetControlImplementations() {{
316
+ controlImplementations(
317
+ skip: {skip}, take: {take}, where: {{
318
+ parentId: {{eq: {parent_id}}},
319
+ control: {{
320
+ controlObjectives: {{
321
+ some: {{
322
+ otherId: {{
323
+ contains: "{cci_name}"
324
+ }}
325
+ }}
326
+ }}
327
+ }}
328
+ }}
329
+ ) {{
330
+ items {{
331
+ id
332
+ }}
333
+ pageInfo {{
334
+ hasNextPage
335
+ }}
336
+ totalCount
337
+ }}
338
+ }}
339
+ """
340
+
341
+ response = cls._get_api_handler().graph(query)
342
+ if "controlImplementations" in response:
343
+ return [item["id"] for item in response["controlImplementations"]["items"]]
344
+ return []
345
+
346
+ @classmethod
347
+ def get_control_id_map_by_plan(cls, plan_id: int) -> Dict[int, int]:
348
+ """
349
+ Get a map of control ids to control implementations by plan ID.
350
+
351
+ :param int plan_id: The ID of the plan
352
+ :return: A dictionary mapping control IDs to implementation IDs
353
+ :rtype: Dict[int, int]
354
+ """
355
+ logger.info("Getting control id map by plan...")
356
+ response = cls._get_api_handler().get(
357
+ endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
358
+ )
359
+ if response and response.ok:
360
+ logger.info("Fetched control id map by plan successfully.")
361
+ return {ci["control"]["id"]: ci["id"] for ci in response.json()}
362
+ logger.info("Unable to get control id map by plan.")
363
+ return {}
364
+
365
+ @staticmethod
366
+ def post_implementation(
367
+ app: Application, implementation: "ControlImplementation"
368
+ ) -> Union[requests.Response, Dict]:
369
+ """
370
+ Post a control implementation to RegScale via API
371
+
372
+ :param Application app:
373
+ :param ControlImplementation implementation:
374
+ :return: Response from RegScale API or the response if the response is not ok
375
+ :rtype: Union[requests.Response, Dict]
376
+ """
377
+ api = Api()
378
+ headers = {
379
+ "accept": "*/*",
380
+ "Authorization": app.config["token"],
381
+ "Content-Type": PATCH_CONTENT_TYPE,
382
+ }
383
+
384
+ res = api.post(
385
+ app.config["domain"] + "/api/controlimplementation",
386
+ headers=headers,
387
+ json=implementation.dict(),
388
+ )
389
+ if not res.raise_for_status() and res.status_code == 200:
390
+ return res.json()
391
+ else:
392
+ return res
393
+
394
+ @staticmethod
395
+ def update(app: Application, implementation: "ControlImplementation") -> Union[requests.Response, Dict]:
396
+ """
397
+ Update Method for ControlImplementation
398
+
399
+ :param Application app: Application instance
400
+ :param ControlImplementation implementation: ControlImplementation instance
401
+ :return: A control implementation dict or a response object
402
+ :rtype: Union[requests.Response, Dict]
403
+ """
404
+ api = Api()
405
+
406
+ res = api.put(
407
+ app.config["domain"] + f"/api/controlimplementation/{implementation.id}",
408
+ json=implementation.dict(),
409
+ )
410
+ if not res.raise_for_status() and res.status_code == 200:
411
+ return res.json()
412
+ else:
413
+ return res
414
+
415
+ @staticmethod
416
+ def fetch_existing_implementations(app: Application, regscale_parent_id: int, regscale_module: str) -> List[Dict]:
417
+ """
418
+ Fetch existing implementations for the provided id and module from RegScale
419
+
420
+ :param Application app: Application instance
421
+ :param int regscale_parent_id: RegScale Parent ID
422
+ :param str regscale_module: RegScale Parent Module
423
+ :return: list of existing implementations
424
+ :rtype: List[Dict]
425
+ """
426
+ api = Api()
427
+ existing_implementations = []
428
+ existing_implementations_response = api.get(
429
+ url=app.config["domain"]
430
+ + "/api/controlimplementation"
431
+ + f"/getAllByParent/{regscale_parent_id}/{regscale_module}"
432
+ )
433
+ if existing_implementations_response.ok:
434
+ existing_implementations = existing_implementations_response.json()
435
+ return existing_implementations
436
+
437
+ @staticmethod
438
+ def _extract_text_and_log(
439
+ element: Element, imp: "ControlImplementation", debug_logger: logging.Logger
440
+ ) -> Optional[str]:
441
+ """
442
+ Extracts and logs text from an XML element.
443
+
444
+ :param Element element: The XML element.
445
+ :param ControlImplementation imp: The control implementation instance.
446
+ :param logging.Logger debug_logger: Logger for debugging.
447
+ :return: Stripped text from the element.
448
+ :rtype: Optional[str]
449
+ """
450
+
451
+ text = element.text.strip() if element.text else None
452
+ if text:
453
+ debug_logger.debug("Text: %s", text)
454
+ imp.implementation = text
455
+ return text
456
+
457
+ @staticmethod
458
+ def _update_implementation_status(element: Element, imp: "ControlImplementation") -> None:
459
+ """
460
+ Updates the implementation statuses and control origin of the control based on the element.
461
+
462
+ :param Element element: The XML element.
463
+ :param ControlImplementation imp: The control implementation instance.
464
+ :rtype: None
465
+ """
466
+
467
+ def update_status_from_value(value: str, status_map: Dict[str, tuple]) -> Optional[tuple]:
468
+ """
469
+ Updates the implementation status based on the value
470
+
471
+ :param str value: The value to update the status from
472
+ :param Dict[str, tuple] status_map: The status mapping
473
+ :return: The status and the flag attribute
474
+ :rtype: Optional[tuple]
475
+ """
476
+ if value in status_map:
477
+ status, flag_attr = status_map[value]
478
+ setattr(imp, flag_attr, True)
479
+ return status
480
+ logger.warning(f"Invalid value: {value}")
481
+ return None
482
+
483
+ status_mapping = {
484
+ "implemented": (ControlImplementationStatus.FullyImplemented, "bStatusImplemented"),
485
+ "partial": (ControlImplementationStatus.PartiallyImplemented, "bStatusPartiallyImplemented"),
486
+ "not-applicable": (ControlImplementationStatus.NA, "bStatusNotApplicable"),
487
+ "planned": (ControlImplementationStatus.Planned, "bStatusPlanned"),
488
+ "alternative": (ControlImplementationStatus.Alternative, "bStatusAlternative"),
489
+ }
490
+
491
+ responsibility_mapping = {
492
+ "sp-corporate": (ControlImplementationOrigin.Provider, "bServiceProviderCorporate"),
493
+ "sp-system": (ControlImplementationOrigin.ProviderSS, "bServiceProviderSystemSpecific"),
494
+ "customer-configured": (ControlImplementationOrigin.CustomerConfigured, "bConfiguredByCustomer"),
495
+ "customer-provided": (ControlImplementationOrigin.CustomerProvided, "bProvidedByCustomer"),
496
+ "inherited": (ControlImplementationOrigin.Inherited, "bInherited"),
497
+ }
498
+
499
+ if "name" in element.attrib:
500
+ if element.attrib["name"] == "implementation-status":
501
+ imp.status = update_status_from_value(element.attrib.get("value"), status_mapping)
502
+
503
+ if element.attrib["name"] == "control-origination":
504
+ imp.responsibility = update_status_from_value(element.attrib.get("value"), responsibility_mapping)
505
+
506
+ @staticmethod
507
+ def from_oscal_element(app: Application, obj: Element, control: dict) -> "ControlImplementation":
508
+ """
509
+ Create RegScale ControlImplementation from XML element.
510
+
511
+ :param Application app: RegScale CLI Application object.
512
+ :param Element obj: Element object.
513
+ :param dict control: Control dictionary.
514
+ :return: ControlImplementation class.
515
+ :rtype: ControlImplementation
516
+ """
517
+ user = app.config["userId"]
518
+ imp = ControlImplementation(controlOwnerId=user, status="notimplemented", controlID=control["id"])
519
+
520
+ for element in obj.iter():
521
+ ControlImplementation._extract_text_and_log(element, imp, logger)
522
+
523
+ # This try catch is tied to modification to catalogs object returned by above API call
524
+ # The otherId field is to be added to new OSCAL catalogs which will be migrated for existing customers.
525
+ # If otherId exists use it to match to control otherwise use original controlId
526
+ # Handle case where otherId does not exist in catalog object and do not throw an error
527
+
528
+ # if otherid exists in catalog object make sure it has something in it before matching
529
+ # this case may exist while new catalogs are being migrated to for customers
530
+ if len(control.get("otherId", [])) > 0:
531
+ imp.control = control["otherId"]
532
+ else:
533
+ logger.debug("Warning: OtherId (machine readable) not found on record.")
534
+ imp.control = control["controlId"]
535
+
536
+ for name, value in element.attrib.items():
537
+ logger.debug(f"Property: {name}, Value: {value}")
538
+ ControlImplementation._update_implementation_status(element, imp)
539
+
540
+ return imp
541
+
542
+ @staticmethod
543
+ def from_dict(obj: Any) -> "ControlImplementation":
544
+ """
545
+ Create ControlImplementation from dictionary
546
+
547
+ :param Any obj: Object to create ControlImplementation from
548
+ :return: ControlImplementation class
549
+ :rtype: ControlImplementation
550
+ """
551
+ if "id" in obj:
552
+ del obj["id"]
553
+ return ControlImplementation(**obj)
554
+
555
+ def __hash__(self) -> int:
556
+ """
557
+ Hash function for ControlImplementation
558
+
559
+ :return: Hash of ControlImplementation
560
+ :rtype: int
561
+ """
562
+ return hash(
563
+ (
564
+ self.controlID,
565
+ self.controlOwnerId,
566
+ self.status,
567
+ )
568
+ )
569
+
570
+ @staticmethod
571
+ def post_batch_implementation(
572
+ app: Application, implementations: List[Dict]
573
+ ) -> Optional[Union[requests.Response, Dict]]:
574
+ """
575
+ Post a batch of control implementations to the RegScale API
576
+
577
+ :param Application app: RegScale CLI Application object
578
+ :param List[Dict] implementations: list of control implementations to post to RegScale
579
+ :return: Response from RegScale API or the response content if the response is not ok
580
+ :rtype: Optional[Union[requests.Response, Dict]]
581
+ """
582
+ if len(implementations) > 0:
583
+ api = Api()
584
+ headers = {
585
+ "accept": "*/*",
586
+ "Authorization": app.config["token"],
587
+ "Content-Type": PATCH_CONTENT_TYPE,
588
+ }
589
+ res = api.post(
590
+ url=urljoin(app.config["domain"], "/api/controlImplementation/batchCreate"),
591
+ json=implementations,
592
+ headers=headers,
593
+ )
594
+ if not res.raise_for_status() and res.status_code == 200:
595
+ app.logger.info(f"Created {len(implementations)} Control Implementations, Successfully!")
596
+ return res.json()
597
+ else:
598
+ return res
599
+
600
+ @staticmethod
601
+ def put_batch_implementation(
602
+ app: Application, implementations: List[Dict]
603
+ ) -> Optional[Union[requests.Response, Dict]]:
604
+ """
605
+ Put a batch of control implementations to the RegScale API
606
+
607
+ :param Application app: RegScale CLI Application object
608
+ :param List[Dict] implementations: list of control implementations to post to RegScale
609
+ :return: Response from RegScale API or the response content if the response is not ok
610
+ :rtype: Optional[Union[requests.Response, Dict]]
611
+ """
612
+ if len(implementations) > 0:
613
+ api = Api()
614
+ headers = {
615
+ "accept": "*/*",
616
+ "Authorization": app.config["token"],
617
+ "Content-Type": PATCH_CONTENT_TYPE,
618
+ }
619
+ res = api.post(
620
+ url=urljoin(app.config["domain"], "/api/controlImplementation/batchUpdate"),
621
+ json=implementations,
622
+ headers=headers,
623
+ )
624
+ if not res.raise_for_status() and res.status_code == 200:
625
+ app.logger.info(f"Updated {len(implementations)} Control Implementations, Successfully!")
626
+ return res.json()
627
+ else:
628
+ return res
629
+
630
+ @staticmethod
631
+ def get_existing_control_implementations(parent_id: int) -> Dict:
632
+ """
633
+ Fetch existing control implementations as dict with control id as the key used for
634
+ automating control implementation creation
635
+
636
+ :param int parent_id: parent control id
637
+ :return: Dictionary of existing control implementations
638
+ :rtype: Dict
639
+ """
640
+ app = Application()
641
+ api = Api()
642
+ domain = app.config.get("domain")
643
+ existing_implementation_dict = {}
644
+ get_url = urljoin(domain, f"/api/controlImplementation/getAllByPlan/{parent_id}")
645
+ response = api.get(get_url)
646
+ if response.ok:
647
+ existing_control_implementations_json = response.json()
648
+ for cim in existing_control_implementations_json:
649
+ existing_implementation_dict[cim.get("controlName")] = cim
650
+ logger.info(f"Found {len(existing_implementation_dict)} existing control implementations")
651
+ elif response.status_code == 404:
652
+ logger.info(f"No existing control implementations found for {parent_id}")
653
+ else:
654
+ logger.warning(f"Unable to get existing control implementations. {response.content}")
655
+ return existing_implementation_dict
656
+
657
+ @classmethod
658
+ def create_control_implementations(
659
+ cls,
660
+ controls: list,
661
+ parent_id: int,
662
+ parent_module: str,
663
+ existing_implementation_dict: dict,
664
+ full_controls: dict,
665
+ partial_controls: dict,
666
+ failing_controls: dict,
667
+ include_not_implemented: Optional[bool] = False,
668
+ ) -> None:
669
+ """
670
+ Creates and updates control implementations based on given controls
671
+
672
+ :param list controls: List of control details
673
+ :param int parent_id: Identifier for the parent control
674
+ :param str parent_module: Name of the parent module
675
+ :param dict existing_implementation_dict: Dictionary of existing implementations
676
+ :param dict full_controls: Dictionary of fully implemented controls
677
+ :param dict partial_controls: Dictionary of partially implemented controls
678
+ :param dict failing_controls: Dictionary of failing controls
679
+ :param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
680
+ :rtype: None
681
+ """
682
+ app = Application()
683
+ user_id = app.config.get("userId")
684
+
685
+ to_create, to_update = cls.process_controls(
686
+ controls,
687
+ parent_id,
688
+ parent_module,
689
+ existing_implementation_dict,
690
+ full_controls,
691
+ partial_controls,
692
+ failing_controls,
693
+ user_id,
694
+ include_not_implemented,
695
+ )
696
+
697
+ cls.post_batch_if_needed(app, to_create, ControlImplementation.post_batch_implementation)
698
+ cls.put_batch_if_needed(app, to_update, ControlImplementation.put_batch_implementation)
699
+
700
+ @classmethod
701
+ def process_controls(
702
+ cls,
703
+ controls: list,
704
+ parent_id: int,
705
+ parent_module: str,
706
+ existing_implementation_dict: dict,
707
+ full_controls: dict,
708
+ partial_controls: dict,
709
+ failing_controls: dict,
710
+ user_id: Optional[str] = None,
711
+ include_not_implemented: Optional[bool] = False,
712
+ ) -> tuple[list, list]:
713
+ """
714
+ Processes each control for creation or update
715
+
716
+ :param list controls: List of control details
717
+ :param int parent_id: Identifier for the parent control
718
+ :param str parent_module: Name of the parent module
719
+ :param dict existing_implementation_dict: Dictionary of existing implementations
720
+ :param dict full_controls: Dictionary of fully implemented controls
721
+ :param dict partial_controls: Dictionary of partially implemented controls
722
+ :param dict failing_controls: Dictionary of failing controls
723
+ :param Optional[str] user_id: ID of the user performing the operation, defaults to None
724
+ :param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
725
+ :return: Tuple containing lists of controls to create and update
726
+ :rtype: tuple[list, list]
727
+ """
728
+ to_create = []
729
+ to_update = []
730
+
731
+ for control in controls:
732
+ # if otherid exists in catalog object make sure it has something in it before matching
733
+ # this case may exist while new catalogs are being migrated to for customers
734
+ if len(control.get("otherId", [])) > 0:
735
+ lower_case_control_id = control["otherId"].lower()
736
+ else:
737
+ lower_case_control_id = control["controlId"].lower()
738
+
739
+ status = cls.check_implementation(full_controls, partial_controls, failing_controls, lower_case_control_id)
740
+ if not include_not_implemented and status == ControlImplementationStatus.NotImplemented.value:
741
+ continue
742
+
743
+ if len(control.get("otherId", [])) > 0:
744
+ controlid = control["otherId"]
745
+ else:
746
+ controlid = control["controlId"]
747
+
748
+ if controlid not in existing_implementation_dict:
749
+ cim = cls.create_new_control_implementation(control, parent_id, parent_module, status, user_id)
750
+ to_create.append(cim)
751
+ else:
752
+ cls.update_existing_control_implementation(
753
+ control, existing_implementation_dict, status, to_update, user_id
754
+ )
755
+
756
+ return to_create, to_update
757
+
758
+ @staticmethod
759
+ def create_new_control_implementation(
760
+ control: dict,
761
+ parent_id: int,
762
+ parent_module: str,
763
+ status: str,
764
+ user_id: Optional[str] = None,
765
+ ) -> "ControlImplementation":
766
+ """
767
+ Creates a new control implementation object
768
+
769
+ :param dict control: Control details
770
+ :param int parent_id: Identifier for the parent control
771
+ :param str parent_module: Name of the parent module
772
+ :param str status: Status of the control implementation
773
+ :param Optional[str] user_id: ID of the user performing the operation, defaults to None
774
+ :return: New control implementation object
775
+ :rtype: ControlImplementation
776
+ """
777
+ cim = ControlImplementation(
778
+ controlOwnerId=user_id,
779
+ dateLastAssessed=get_current_datetime(),
780
+ implementation=control.get("implementation", None),
781
+ status=status,
782
+ controlID=control["id"],
783
+ parentId=parent_id,
784
+ parentModule=parent_module,
785
+ createdById=user_id,
786
+ dateCreated=get_current_datetime(),
787
+ lastUpdatedById=user_id,
788
+ dateLastUpdated=get_current_datetime(),
789
+ ).dict()
790
+ cim["controlSource"] = "Baseline"
791
+ return cim
792
+
793
+ @classmethod
794
+ def update_existing_control_implementation(
795
+ cls,
796
+ control: dict,
797
+ existing_implementation_dict: dict,
798
+ status: str,
799
+ to_update: list,
800
+ user_id: Optional[str] = None,
801
+ ):
802
+ """
803
+ Updates an existing control implementation
804
+
805
+ :param dict control: Control details
806
+ :param dict existing_implementation_dict: Dictionary of existing implementations
807
+ :param str status: Status of the control implementation
808
+ :param list to_update: List of controls to update
809
+ :param Optional[str] user_id: ID of the user performing the operation, defaults to None
810
+ """
811
+ existing_imp = existing_implementation_dict[control["controlId"]]
812
+ existing_imp.update(
813
+ {
814
+ "implementation": control.get("implementation"),
815
+ "status": status,
816
+ "dateLastAssessed": get_current_datetime(),
817
+ "lastUpdatedById": user_id,
818
+ "dateLastUpdated": get_current_datetime(),
819
+ }
820
+ )
821
+
822
+ remove_keys(existing_imp, ["createdBy", "systemRole", "controlOwner", "lastUpdatedBy"])
823
+
824
+ if existing_imp not in to_update:
825
+ to_update.append(existing_imp)
826
+
827
+ @staticmethod
828
+ def post_batch_if_needed(
829
+ app: Application,
830
+ to_create: list,
831
+ post_function: Callable[[Application, list], None],
832
+ ) -> None:
833
+ """
834
+ Posts a batch of new implementations if the list is not empty
835
+
836
+ :param Application app: RegScale CLI application object
837
+ :param list to_create: List of new implementations to post
838
+ :param Callable[[Application, list], None] post_function: The function to call for posting the batch, if needed
839
+ :rtype: None
840
+ """
841
+ if to_create:
842
+ post_function(app, to_create)
843
+
844
+ @staticmethod
845
+ def put_batch_if_needed(
846
+ app: Application,
847
+ to_update: list,
848
+ put_function: Callable[[Application, list], None],
849
+ ) -> None:
850
+ """
851
+ Puts a batch of updated implementations if the list is not empty
852
+
853
+ :param Application app: RegScale CLI application object
854
+ :param list to_update: List of implementations to update
855
+ :param Callable[[Application, list], None] put_function: The function to call for putting the batch, if needed
856
+ """
857
+ if to_update:
858
+ put_function(app, to_update)
859
+
860
+ @staticmethod
861
+ def check_implementation(
862
+ full_controls: dict,
863
+ partial_controls: dict,
864
+ failing_controls: dict,
865
+ control_id: str,
866
+ ) -> str:
867
+ """
868
+ Checks the status of a control implementation
869
+
870
+ :param dict full_controls: Dictionary of passing controls
871
+ :param dict partial_controls: Dictionary of partially implemented controls
872
+ :param dict failing_controls: Dictionary of failing control implementations
873
+ :param str control_id: control id
874
+ :return: status of control implementation
875
+ :rtype: str
876
+ """
877
+ if control_id in full_controls.keys():
878
+ logger.debug(f"Found fully implemented control: {control_id}")
879
+ return ControlImplementationStatus.FullyImplemented.value
880
+ elif control_id in partial_controls.keys():
881
+ logger.debug(f"Found partially implemented control: {control_id}")
882
+ return ControlImplementationStatus.PartiallyImplemented.value
883
+ elif control_id in failing_controls.keys():
884
+ logger.debug(f"Found failing control: {control_id}")
885
+ return ControlImplementationStatus.InRemediation.value
886
+ else:
887
+ logger.debug(f"Found not implemented control: {control_id}")
888
+ return ControlImplementationStatus.NotImplemented.value
889
+
890
+ @classmethod
891
+ def get_sort_position_dict(cls) -> dict:
892
+ """
893
+ Overrides the base method.
894
+
895
+ :return: dict The sort position in the list of properties
896
+ :rtype: dict
897
+ """
898
+ return {
899
+ "id": 1,
900
+ "controlOwnerId": 2,
901
+ "status": 3,
902
+ "controlID": 4,
903
+ "parentId": 5,
904
+ "parentModule": 6,
905
+ "control": 7,
906
+ "controlName": 8,
907
+ "controlTitle": 9,
908
+ "description": 10,
909
+ "createdById": -1,
910
+ "uuid": -1,
911
+ "policy": 11,
912
+ "implementation": 12,
913
+ "dateLastAssessed": 13,
914
+ "lastAssessmentResult": 14,
915
+ "practiceLevel": 15,
916
+ "processLevel": 16,
917
+ "cyberFunction": 17,
918
+ "implementationType": 18,
919
+ "implementationMethod": 19,
920
+ "qdWellDesigned": 20,
921
+ "qdProcedures": 21,
922
+ "qdSegregation": 22,
923
+ "qdFlowdown": 23,
924
+ "qdAutomated": 24,
925
+ "qdOverall": 25,
926
+ "qiResources": 26,
927
+ "qiMaturity": 27,
928
+ "qiReporting": 28,
929
+ "qiVendorCompliance": 29,
930
+ "qiIssues": 30,
931
+ "qiOverall": 31,
932
+ "responsibility": 32,
933
+ "inheritedControlId": 33,
934
+ "inheritedRequirementId": 34,
935
+ "inheritedSecurityPlanId": 35,
936
+ "inheritedPolicyId": 36,
937
+ "dateCreated": -1,
938
+ "lastUpdatedById": -1,
939
+ "dateLastUpdated": -1,
940
+ "weight": 37,
941
+ "isPublic": -1,
942
+ "inheritable": 38,
943
+ "systemRoleId": 39,
944
+ "plannedImplementationDate": 40,
945
+ "stepsToImplement": 41,
946
+ }
947
+
948
+ @classmethod
949
+ def get_enum_values(cls, field_name: str) -> list:
950
+ """
951
+ Overrides the base method.
952
+
953
+ :param str field_name: The property name to provide enum values for
954
+ :return: list of strings
955
+ :rtype: list
956
+ """
957
+ if field_name == "status":
958
+ return [imp_status.value for imp_status in ControlImplementationStatus]
959
+ if field_name == "responsibility":
960
+ return ["Provider", "Customer", "Shared", "Not Applicable"]
961
+ return cls.get_bool_enums(field_name)
962
+
963
+ @classmethod
964
+ def get_lookup_field(cls, field_name: str) -> str:
965
+ """
966
+ Overrides the base method.
967
+
968
+ :param str field_name: The property name to provide enum values for
969
+ :return: str the field name to look up
970
+ :rtype: str
971
+ """
972
+ lookup_fields = {
973
+ "controlOwnerId": "user",
974
+ "controlID": "",
975
+ "inheritedControlId": "",
976
+ "inheritedRequirementId": "",
977
+ "inheritedSecurityPlanId": "",
978
+ "inheritedPolicyId": "",
979
+ "systemRoleId": "",
980
+ }
981
+ if field_name in lookup_fields.keys():
982
+ return lookup_fields[field_name]
983
+ return ""
984
+
985
+ @classmethod
986
+ def is_date_field(cls, field_name: str) -> bool:
987
+ """
988
+ Overrides the base method.
989
+
990
+ :param str field_name: The property name to provide enum values for
991
+ :return: bool if the field should be formatted as a date
992
+ :rtype: bool
993
+ """
994
+ return field_name in ["dateLastAssessed", "plannedImplementationDate"]
995
+
996
+ @classmethod
997
+ def get_export_query(cls, app: Application, parent_id: int, parent_module: str) -> list:
998
+ """
999
+ Overrides the base method.
1000
+
1001
+ :param Application app: RegScale Application object
1002
+ :param int parent_id: RegScale ID of parent
1003
+ :param str parent_module: Module of parent
1004
+ :return: list GraphQL response from RegScale
1005
+ :rtype: list
1006
+ """
1007
+ body = """
1008
+ query{
1009
+ controlImplementations (skip: 0, take: 50, where: {parentId: {eq: parent_id} parentModule: {eq: "parent_module"}}) {
1010
+ items {
1011
+ id
1012
+ controlID
1013
+ controlOwner {
1014
+ firstName
1015
+ lastName
1016
+ userName
1017
+ }
1018
+ control {
1019
+ title
1020
+ description
1021
+ controlId
1022
+ }
1023
+ status
1024
+ policy
1025
+ implementation
1026
+ responsibility
1027
+ inheritable
1028
+ parentId
1029
+ parentModule
1030
+ }
1031
+ totalCount
1032
+ pageInfo {
1033
+ hasNextPage
1034
+ }
1035
+ }
1036
+ }""".replace(
1037
+ "parent_module", parent_module
1038
+ ).replace(
1039
+ "parent_id", str(parent_id)
1040
+ )
1041
+
1042
+ api = Api()
1043
+ existing_implementation_data = api.graph(query=body)
1044
+
1045
+ if existing_implementation_data["controlImplementations"]["totalCount"] > 0:
1046
+ raw_data = existing_implementation_data["controlImplementations"]["items"]
1047
+ moded_data = []
1048
+ for item in raw_data:
1049
+ moded_item = {}
1050
+ moded_item["id"] = item["id"]
1051
+ moded_item["controlID"] = item["controlID"]
1052
+ moded_item["controlOwnerId"] = (
1053
+ str(item["controlOwner"]["lastName"]).strip()
1054
+ + ", "
1055
+ + str(item["controlOwner"]["firstName"]).strip()
1056
+ + " ("
1057
+ + str(item["controlOwner"]["userName"]).strip()
1058
+ + ")"
1059
+ )
1060
+ moded_item["controlName"] = item["control"]["controlId"]
1061
+ moded_item["controlTitle"] = item["control"]["title"]
1062
+ moded_item["description"] = item["control"]["description"]
1063
+ moded_item["status"] = item["status"]
1064
+ moded_item["policy"] = item["policy"]
1065
+ moded_item["implementation"] = item["implementation"]
1066
+ moded_item["responsibility"] = item["responsibility"]
1067
+ moded_item["inheritable"] = item["inheritable"]
1068
+ moded_data.append(moded_item)
1069
+ return moded_data
1070
+ return []
1071
+
1072
+ @classmethod
1073
+ def use_query(cls) -> bool:
1074
+ """
1075
+ Overrides the base method.
1076
+
1077
+ :return: bool
1078
+ :rtype: bool
1079
+ """
1080
+ return True
1081
+
1082
+ @classmethod
1083
+ def get_extra_fields(cls) -> list:
1084
+ """
1085
+ Overrides the base method.
1086
+
1087
+ :return: list of extra field names
1088
+ :rtype: list
1089
+ """
1090
+ return ["controlName", "controlTitle", "description"]
1091
+
1092
+ @classmethod
1093
+ def get_include_fields(cls) -> list:
1094
+ """
1095
+ Overrides the base method.
1096
+
1097
+ :return: list of field names
1098
+ :rtype: list
1099
+ """
1100
+ return [
1101
+ "dateLastAssessed",
1102
+ "lastAssessmentResult",
1103
+ "practiceLevel",
1104
+ "processLevel",
1105
+ "cyberFunction",
1106
+ "implementationType",
1107
+ "implementationMethod",
1108
+ ]
1109
+
1110
+ @classmethod
1111
+ def is_new_excel_record_allowed(cls) -> bool:
1112
+ """
1113
+ Overrides the base method.
1114
+
1115
+ :return: bool indicating if the field is required
1116
+ :rtype: bool
1117
+ """
1118
+ return False
1119
+
1120
+ def add_role(self, role_id: int):
1121
+ """
1122
+ Add role to the control implementation
1123
+ """
1124
+ if not self.id or self.id == 0:
1125
+ logger.error("Control Implementation ID is required to add role")
1126
+ ImplementationRole.add_role(
1127
+ role_id=role_id, control_implementation_id=self.id, parent_module=self._module_string
1128
+ )