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,70 @@
1
+ import click
2
+ from regscale.models.regscale_models.security_control import SecurityControl
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ # Function to update the security control's criticality using the API
9
+ def update_security_control_criticality(control_id: str, criticality: str, control_dict: dict):
10
+ """
11
+ Update the security control's criticality using the API.
12
+ :param control_dict:
13
+ :param str control_id:
14
+ :param str criticality:
15
+ """
16
+ control_id = control_id.lower().strip().replace(" ", "")
17
+ matching_control: SecurityControl = control_dict.get(control_id.lower().strip())
18
+ logger.info(
19
+ f"Updating control {control_id} == {matching_control.controlId.lower().strip() if matching_control else None} with criticality {criticality}"
20
+ )
21
+ if control := control_dict.get(control_id):
22
+ control.criticality = criticality
23
+ control.save()
24
+
25
+
26
+ @click.group(name="criticality_updater")
27
+ def criticality_updater():
28
+ """
29
+ Update the criticality of security controls in the catalog.
30
+ """
31
+
32
+
33
+ @criticality_updater.command(name="import")
34
+ @click.option("--file_path", "-f", help="Path to the Excel file", required=True, type=click.Path(exists=True))
35
+ @click.option("--catalog_id", "-cat", help="Catalog ID", required=True, type=int)
36
+ @click.option(
37
+ "--column_header_control_id",
38
+ "-ci",
39
+ help="Column header for the Security Control ID",
40
+ default="Security Control #",
41
+ required=True,
42
+ )
43
+ @click.option(
44
+ "--column_header_criticality",
45
+ "-c",
46
+ help="Column header for the Criticality Rating",
47
+ default="Security Control Criticality Rating",
48
+ required=True,
49
+ )
50
+ def update_control_criticality(
51
+ file_path: str, catalog_id: int, column_header_control_id: str, column_header_criticality: str
52
+ ):
53
+ """
54
+ Update the criticality of security controls in the catalog.
55
+ """
56
+ import pandas as pd # Optimize import performance
57
+
58
+ df = pd.read_excel(file_path)
59
+ controls = SecurityControl.get_all_by_parent(parent_id=catalog_id, parent_module="catalogs") # Get all the controls
60
+ logger.info(f"Found {len(controls)} controls")
61
+ control_dict = {control.controlId.lower(): control for control in controls} # Create a dictionary for easy access
62
+ # Loop through the DataFrame and map the controls
63
+ for index, row in df.iterrows():
64
+ control_id = row.get(column_header_control_id) # Column A - Security Control
65
+ criticality = row.get(column_header_criticality) # Column D - Criticality Rating
66
+
67
+ if pd.notna(control_id) and pd.notna(criticality): # Ensure the values are not empty
68
+ # Update the security control with the new criticality
69
+ update_security_control_criticality(control_id, criticality, control_dict)
70
+ logger.info("Finished processing all records.")
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """eMASS integration to the CLI to allow support for eMASS documents"""
4
+
5
+ # standard python imports
6
+ import os
7
+ from typing import Any
8
+
9
+ import click
10
+ from openpyxl import load_workbook
11
+ from openpyxl.comments import Comment
12
+ from openpyxl.styles import PatternFill
13
+ from pathlib import Path
14
+
15
+ from regscale.core.app.api import Api
16
+ from regscale.integrations.public.emass_slcm_import import import_emass_slcm_file
17
+ from regscale.core.app.utils.app_utils import (
18
+ check_file_path,
19
+ create_progress_object,
20
+ error_and_exit,
21
+ get_current_datetime,
22
+ get_file_type,
23
+ reformat_str_date,
24
+ )
25
+ from regscale.models import regscale_id
26
+
27
+ SKIP_ROWS: int = 7
28
+ COLUMNS = ["M", "N", "O", "P"]
29
+
30
+
31
+ @click.group()
32
+ def emass():
33
+ """Performs bulk processing of eMASS files (Upload trusted data only)."""
34
+
35
+
36
+ @emass.command("populate_controls")
37
+ @click.option(
38
+ "--file_name",
39
+ type=click.Path(exists=True, dir_okay=False, file_okay=True),
40
+ required=True,
41
+ prompt="Enter the full file path of the eMASS controls document.",
42
+ help="Enter the full file path of the eMASS controls document to populate with RegScale data.",
43
+ )
44
+ @regscale_id(help="Enter the desired SSP ID # from RegScale.")
45
+ def populate_workbook(file_name: click.Path, regscale_id: int) -> None:
46
+ """
47
+ [BETA] Populate controls from a System Security Plan in RegScale into an eMASS formatted excel workbook.
48
+ """
49
+ populate_emass_workbook(file_name=file_name, regscale_id=regscale_id)
50
+
51
+
52
+ @emass.command("import_slcm")
53
+ @click.option(
54
+ "--file_name",
55
+ type=click.Path(exists=True, dir_okay=False, file_okay=True),
56
+ required=True,
57
+ prompt="Enter the full file path of the eMASS controls document.",
58
+ help="Enter the full file path of the eMASS controls document to populate with RegScale data.",
59
+ )
60
+ @regscale_id(help="Enter the desired SSP ID # from RegScale.")
61
+ @click.option(
62
+ "--catalogue_id",
63
+ "-c",
64
+ type=click.INT,
65
+ help="The RegScale ID # of the catalogue to use for controls in the profile.",
66
+ required=True,
67
+ )
68
+ @click.option(
69
+ "--tenant_id",
70
+ "-t",
71
+ type=click.INT,
72
+ help="The RegScale ID # of the tenant to use for this security plan.",
73
+ required=True,
74
+ )
75
+ def import_slcm(file_name: click.Path, regscale_id: int, catalogue_id: int, tenant_id: int) -> None:
76
+ """
77
+ [BETA] Populate controls from a System Security Plan in RegScale into an eMASS formatted excel workbook.
78
+ """
79
+ import_emass_slcm_file(file_name=file_name, regscale_id=regscale_id, catalogue_id=catalogue_id, tenant_id=tenant_id)
80
+
81
+
82
+ def fetch_template_from_blob() -> None:
83
+ """
84
+ Fetch a template for the eMASS controls document
85
+
86
+ :rtype: None
87
+ """
88
+ api = Api()
89
+
90
+ # check if the artifacts folder exists
91
+ check_file_path("artifacts")
92
+
93
+ # get the template from the API
94
+ template = api.get(
95
+ url="https://regscaleblob.blob.core.windows.net/blob/eMASS_Control_Template.xlsx",
96
+ headers={},
97
+ )
98
+
99
+ # write the template to a file
100
+ with open(f".{os.sep}artifacts{os.sep}eMASS_Template.xlsx", "wb") as f:
101
+ f.write(template.content)
102
+ api.logger.info(f"Template saved to .{os.sep}artifacts{os.sep}eMASS_Template.xlsx")
103
+
104
+
105
+ def populate_emass_workbook(file_name: Path, regscale_id: int) -> None:
106
+ """
107
+ Function to populate an eMASS workbook with control assessments from RegScale
108
+
109
+ :param Path file_name: Path to the eMASS control workbook
110
+ :param int regscale_id: ID of the SSP in RegScale to get the controls & assessments from
111
+ :rtype: None
112
+ """
113
+ # make sure the user gave a path to an Excel workbook
114
+ if get_file_type(file_name) not in [".xlsx", ".xls"]:
115
+ error_and_exit("Please provide a file path to an Excel workbook in .xlsx or .xls format.")
116
+
117
+ # convert file_name to a Path object
118
+ file_name = Path(file_name)
119
+
120
+ # initialize the Application and API classes
121
+ api = Api()
122
+
123
+ # populate the controls in the Excel workbook
124
+ output_name = populate_assessment_results(file_name=file_name, ssp_id=regscale_id, api=api)
125
+ api.logger.info("Please open %s and verify the data before uploading into eMASS.", output_name)
126
+
127
+
128
+ def map_ccis(file_data_dict: dict, file_name: str) -> dict:
129
+ """
130
+ Function to map each cci and its row number in the eMASS workbook
131
+
132
+ :param dict file_data_dict: Dictionary of an Excel file column
133
+ :param str file_name: Name of the file file_data_dict is from
134
+ :return: dictionary of ccis and their row numbers
135
+ :rtype: dict
136
+ """
137
+ # convert the control names to match RegScale control names
138
+ try:
139
+ formatted_ccis = {
140
+ # create a dictionary with the key as the control name and the value as the row number
141
+ # cci has a prefix of CCI- and must be a 6-digit number
142
+ f"CCI-{val:06}": {
143
+ "cci": f"CCI-{val:06}",
144
+ "row": key + SKIP_ROWS,
145
+ }
146
+ for key, val in file_data_dict["CCI"].items()
147
+ }
148
+ except KeyError:
149
+ error_and_exit(
150
+ f"{file_name} doesn't match the expected eMASS format.\nPlease view an example "
151
+ "template here: https://regscale.readme.io/docs/emass-beta#template"
152
+ )
153
+
154
+ return formatted_ccis or {}
155
+
156
+
157
+ def fetch_assessments_and_controls(ssp_id: int, api: Api) -> list:
158
+ """
159
+ Fetch assessments and controls from RegScale
160
+
161
+ :param int ssp_id: SSP ID from RegScale
162
+ :param Api api: API Object
163
+ :return: List of controls with assessments
164
+ :rtype: list
165
+ """
166
+ # create the GraphQL query
167
+ query = f"""
168
+ query {{
169
+ controls:controlImplementations(
170
+ take: 50
171
+ skip: 0
172
+ where: {{
173
+ parentId: {{ eq: {ssp_id} }}
174
+ parentModule: {{ eq: "securityplans" }}
175
+ assessments: {{ any: true }}
176
+ }}
177
+ ) {{
178
+ items {{
179
+ id
180
+ control {{
181
+ controlId
182
+ cci {{
183
+ name
184
+ description
185
+ }}
186
+ }}
187
+ assessments {{
188
+ id
189
+ actualFinish
190
+ assessmentResult
191
+ summaryOfResults
192
+ leadAssessor {{
193
+ firstName
194
+ lastName
195
+ }}
196
+ }}
197
+ }}
198
+ totalCount
199
+ pageInfo {{
200
+ hasNextPage
201
+ }}
202
+ }}
203
+ }}
204
+ """
205
+
206
+ # get the data from GraphQL
207
+ response = api.graph(query=query)
208
+
209
+ # try to get the items from the GraphQL response
210
+ try:
211
+ controls = response["controls"]["items"]
212
+ except KeyError:
213
+ controls = []
214
+
215
+ total_controls = api.get(
216
+ f"{api.config['domain']}/api/controlImplementation/getCountByParent/{ssp_id}/securityplans"
217
+ )
218
+
219
+ if not total_controls.ok:
220
+ error_and_exit(f"Received unexpected response: {total_controls.status_code}\n{total_controls.text}")
221
+
222
+ if controls:
223
+ api.logger.info(
224
+ "Received %s/%s controls with Assessments. Total control count for SSP #%s in RegScale: %s.",
225
+ len(controls),
226
+ response["controls"]["totalCount"],
227
+ ssp_id,
228
+ total_controls.text,
229
+ )
230
+ for control in controls:
231
+ try:
232
+ control["ccis"] = [cci["name"] for cci in control["control"]["cci"]]
233
+ except (KeyError, TypeError):
234
+ control["ccis"] = []
235
+ else:
236
+ error_and_exit(
237
+ "The RegScale SSP provided has no assessments associated with the controls. "
238
+ + "Please add assessments to the controls and try again."
239
+ )
240
+ return controls
241
+
242
+
243
+ def populate_assessment_results(file_name: Path, ssp_id: int, api: Api) -> Path:
244
+ """
245
+ Populate assessment results from a System Security Plan in RegScale into an eMASS formatted excel workbook
246
+
247
+ :param Path file_name: path to the Excel workbook to populate with assessments from SSP
248
+ :param int ssp_id: ID for a System Security Plan from RegScale
249
+ :param Api api: API Object
250
+ :return: Path to output file
251
+ :rtype: Path
252
+ """
253
+ import pandas as pd # Optimize import performance
254
+
255
+ author = "RegScale CLI"
256
+ job_progress = create_progress_object()
257
+ logger = api.logger
258
+ controls = fetch_assessments_and_controls(ssp_id=ssp_id, api=api)
259
+ # load the Excel file in pandas to find row # to update the data
260
+ file_data = pd.read_excel(file_name, skiprows=SKIP_ROWS - 2)
261
+
262
+ # load the workbook using openpyxl to retain worksheet styling
263
+ wb = load_workbook(file_name)
264
+
265
+ # set the sheet to the first sheet in the provided workbook
266
+ sheet = wb.active
267
+
268
+ # convert to a dictionary
269
+ file_data_dict = file_data.to_dict()
270
+
271
+ # format the controls
272
+ cci_mappings = map_ccis(file_data_dict=file_data_dict, file_name=file_name.name)
273
+
274
+ # create variable to count number of rows updated and skipped
275
+ update_counter: int = 0
276
+ skipped_counter: int = 0
277
+
278
+ # create a dictionary of all ccis and their assessments
279
+ regscale_cci_assessments = {
280
+ cci: {"assessment": ctrl["assessments"][0]}
281
+ for ctrl in controls
282
+ for cci in ctrl.get("ccis", [])
283
+ if "ccis" in ctrl
284
+ }
285
+
286
+ # create comment & fill attribute for columns with missing data
287
+ comment = Comment(
288
+ text=f"SSP #{ssp_id} doesn't contain an assessment associated with this control.",
289
+ author=author,
290
+ height=150,
291
+ )
292
+ yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
293
+
294
+ with job_progress:
295
+ populating_controls = job_progress.add_task(
296
+ f"[#21a5bb]Analyzing controls in {file_name.name}...",
297
+ total=len(cci_mappings),
298
+ )
299
+ # iterate through the ccis
300
+ for cci in cci_mappings.values():
301
+ row_number = cci["row"]
302
+ cci_id = cci["cci"]
303
+
304
+ # see if the cci has an assessment by comparing it to our GraphQL query results
305
+ if cci_id not in regscale_cci_assessments or not regscale_cci_assessments[cci_id].get("assessment"):
306
+ # increment the skip counter
307
+ skipped_counter += 1
308
+
309
+ # highlight and add a comment
310
+ for column in COLUMNS:
311
+ sheet[f"{column}{row_number}"].comment = comment
312
+ sheet[f"{column}{row_number}"].fill = yellow_fill
313
+ job_progress.update(populating_controls, advance=1)
314
+ continue
315
+ # get the assessment for the cci
316
+ assessment = regscale_cci_assessments[cci_id]["assessment"]
317
+
318
+ map_finish_date(
319
+ assessment=assessment,
320
+ sheet=sheet,
321
+ row_number=row_number,
322
+ author=author,
323
+ yellow_fill=yellow_fill,
324
+ )
325
+
326
+ sheet[f"O{row_number}"] = (
327
+ f'{assessment["leadAssessor"]["firstName"]} {assessment["leadAssessor"]["lastName"]}'
328
+ )
329
+ if assessment["summaryOfResults"]:
330
+ sheet[f"P{row_number}"] = assessment["summaryOfResults"]
331
+ else:
332
+ sheet[f"P{row_number}"].comment = Comment(
333
+ text=f"RegScale assessment #{assessment['id']} doesn't have any information in Summary of Results.",
334
+ author=author,
335
+ height=150,
336
+ )
337
+ sheet[f"P{row_number}"].fill = yellow_fill
338
+
339
+ # update the counter
340
+ update_counter += 1
341
+ # update the progress bar
342
+ job_progress.update(populating_controls, advance=1)
343
+
344
+ # add the date and time to the output filename
345
+ output_name = Path(
346
+ os.path.join(
347
+ file_name.parent,
348
+ file_name.stem + get_current_datetime("_Updated_%Y%m%d_%H%M%S") + file_name.suffix,
349
+ )
350
+ )
351
+
352
+ # save the updated workbook
353
+ wb.save(output_name)
354
+
355
+ logger.info(
356
+ "%s has been created with %i update(s). %i row(s) were skipped because of missing controls in SSP #%i.",
357
+ output_name.name,
358
+ update_counter,
359
+ skipped_counter,
360
+ ssp_id,
361
+ )
362
+ # return the output path
363
+ return output_name
364
+
365
+
366
+ def determine_assessment_result(assessment: dict) -> str:
367
+ """
368
+ Determine the assessment result based on the assessment data
369
+
370
+ :param dict assessment: Assessment data
371
+ :return: Assessment result
372
+ :rtype: str
373
+ """
374
+ if assessment["assessmentResult"] == "Pass":
375
+ return "Compliant"
376
+ elif assessment["assessmentResult"] in ["Fail", "Partial Pass"]:
377
+ return "Non-Compliant"
378
+ else:
379
+ return "Not Applicable"
380
+
381
+
382
+ def map_finish_date(
383
+ assessment: dict,
384
+ sheet: Any,
385
+ row_number: int,
386
+ author: str,
387
+ yellow_fill: PatternFill,
388
+ ) -> None:
389
+ """
390
+ Map the finish date of the assessment
391
+
392
+ :param dict assessment: Assessment data
393
+ :param Any sheet: Excel sheet object
394
+ :param int row_number: Row number
395
+ :param str author: Author of the comment
396
+ :param PatternFill yellow_fill: Yellow fill object
397
+ :rtype: None
398
+ """
399
+ finish_date = reformat_str_date(assessment["actualFinish"], "%d-%b-%Y") if assessment["actualFinish"] else None
400
+
401
+ # map the control to the Excel spreadsheet
402
+ sheet[f"M{row_number}"] = determine_assessment_result(assessment)
403
+ if finish_date:
404
+ sheet[f"N{row_number}"] = finish_date
405
+ else:
406
+ sheet[f"N{row_number}"].comment = Comment(
407
+ text=f"Assessment #{assessment['id']} in RegScale doesn't have a finish date.",
408
+ author=author,
409
+ height=150,
410
+ )
411
+ sheet[f"N{row_number}"].fill = yellow_fill