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,798 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """RegScale Okta integration"""
4
+
5
+ # standard python imports
6
+ import json
7
+ import time
8
+ from datetime import datetime, timedelta
9
+ from json import JSONDecodeError
10
+ from pathlib import Path
11
+ from typing import Any, Tuple
12
+
13
+ import click
14
+ import jwcrypto.jwk as jwk
15
+ import python_jwt
16
+
17
+ from regscale.core.app.api import Api
18
+ from regscale.core.app.application import Application
19
+ from regscale.core.app.internal.login import is_valid
20
+ from regscale.core.app.logz import create_logger
21
+ from regscale.core.app.utils.app_utils import (
22
+ check_file_path,
23
+ check_license,
24
+ create_progress_object,
25
+ error_and_exit,
26
+ get_current_datetime,
27
+ parse_url_for_pagination,
28
+ remove_nested_dict,
29
+ save_data_to,
30
+ )
31
+ from regscale.utils.threading.threadhandler import create_threads, thread_assignment
32
+ from regscale.models.app_models.click import file_types, save_output_to
33
+
34
+ LOGIN_ERROR = "Login Error: Invalid RegScale credentials. Please log in for a new token."
35
+ job_progress = create_progress_object()
36
+ logger = create_logger()
37
+ admin_users = []
38
+
39
+
40
+ #####################################################################################################
41
+ #
42
+ # Okta Core API Documentation: https://developer.okta.com/docs/reference/core-okta-api/
43
+ # Okta API Postman Collections: https://developer.okta.com/docs/reference/postman-collections/
44
+ #
45
+ #####################################################################################################
46
+
47
+
48
+ @click.group()
49
+ def okta():
50
+ """Okta integration to pull Okta users via API."""
51
+
52
+
53
+ @okta.command()
54
+ @click.option(
55
+ "--type",
56
+ type=click.Choice(["SSWS", "Bearer"], case_sensitive=False),
57
+ help="The type of authentication method to use for Okta API.",
58
+ prompt="Choose SSWS or Bearer",
59
+ required=True,
60
+ )
61
+ def authenticate(type: str):
62
+ """
63
+ Authenticate with Okta API by choosing SSWS or Bearer. SSWS is a security token created
64
+ within Okta admin portal and Bearer token needs a private JWK from Okta Admin portal.
65
+ """
66
+ app = check_license()
67
+ api = Api()
68
+ authenticate_with_okta(app, api, type)
69
+
70
+
71
+ @okta.command(name="get_active_users")
72
+ @save_output_to()
73
+ @file_types([".csv", ".xlsx"])
74
+ def get_active_users(save_output_to: Path, file_type: str):
75
+ """
76
+ Get active users from Okta API and save them to a .csv or .xlsx file.
77
+ """
78
+ save_active_users_from_okta(save_output_to=save_output_to, file_type=file_type)
79
+
80
+
81
+ @okta.command(name="get_inactive_users")
82
+ @click.option(
83
+ "--days",
84
+ type=click.INT,
85
+ help="The number of days to see if a user hasn't logged in since, default is 30.",
86
+ default=30,
87
+ required=True,
88
+ )
89
+ @save_output_to()
90
+ @file_types([".csv", ".xlsx"])
91
+ def get_inactive_users(days: int, save_output_to: Path, file_type: str):
92
+ """
93
+ Get users that haven't logged in X days from Okta API and save the output as a .csv or .xlsx file.
94
+ """
95
+ save_inactive_users_from_okta(days=days, save_output_to=save_output_to, file_type=file_type)
96
+
97
+
98
+ @okta.command(name="get_all_users")
99
+ @save_output_to()
100
+ @file_types([".csv", ".xlsx"])
101
+ def get_all_users(save_output_to: Path, file_type: str):
102
+ """
103
+ Get All users from Okta API and save the output to a .csv or .xlsx file.
104
+ """
105
+ save_all_users_from_okta(save_output_to=save_output_to, file_type=file_type)
106
+
107
+
108
+ @okta.command(name="get_new_users")
109
+ @click.option(
110
+ "--days",
111
+ type=click.INT,
112
+ help="The number of days to see if a user has been added to Okta, default is 30",
113
+ default=30,
114
+ required=True,
115
+ )
116
+ @save_output_to()
117
+ @file_types([".csv", ".xlsx"])
118
+ def get_recent_users(days: int, save_output_to: Path, file_type: str):
119
+ """
120
+ Get users that were added to Okta in X days.
121
+ """
122
+ save_recently_added_users_from_okta(days=days, save_output_to=save_output_to, file_type=file_type)
123
+
124
+
125
+ @okta.command(name="get_admin_users")
126
+ @save_output_to()
127
+ @file_types([".csv", ".xlsx"])
128
+ def get_admin_users(save_output_to: Path, file_type: str) -> None:
129
+ """
130
+ Get all admin users from Okta API and save the output to .csv or .xlsx file.
131
+ """
132
+ save_admin_users_from_okta(save_output_to=save_output_to, file_type=file_type)
133
+
134
+
135
+ def save_active_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
136
+ """
137
+ Function to get active users from Okta via API and save them to a .csv or .xlsx file
138
+ :param Path save_output_to: The path to save the output file to
139
+ :param str file_type: The file type to save the output file as, default is .csv, options are .csv or .xlsx
140
+ :rtype: None
141
+ """
142
+ if file_type.lower() not in [".csv", ".xlsx"]:
143
+ error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
144
+
145
+ # Get Status of Client Application
146
+ app = check_license()
147
+ api = Api()
148
+
149
+ # check if RegScale token is valid:
150
+ if is_valid(app=app):
151
+ # get the token type from init.yaml
152
+ auth_type = app.config["oktaApiToken"].split(" ")
153
+
154
+ # authenticate with Okta
155
+ authenticate_with_okta(app, api, auth_type[0])
156
+
157
+ # check file path exists, if not create it
158
+ check_file_path(save_output_to)
159
+
160
+ # start progress bar to let user know tasks are working
161
+ with job_progress:
162
+ logger.info("Fetching active users from Okta.")
163
+ # add task for fetching users from Okta
164
+ fetching_users = job_progress.add_task("[#f8b737]Fetching active users from Okta...", total=1)
165
+ # fetch active users from Okta
166
+ users = get_okta_data(
167
+ api=api,
168
+ url=f"{api.config['oktaUrl']}/api/v1/users",
169
+ headers={
170
+ "Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
171
+ "Accept": "application/json",
172
+ "Authorization": api.config["oktaApiToken"],
173
+ },
174
+ params=(("filter", 'status eq "ACTIVE"'), ("limit", "200")),
175
+ task=fetching_users,
176
+ )
177
+ # notify user of how many active users we found
178
+ logger.info("Found %s active user(s).", len(users))
179
+
180
+ check_and_save_data(
181
+ data=users,
182
+ file_name="okta_active_users",
183
+ file_path=save_output_to,
184
+ file_type=file_type,
185
+ data_desc="active user(s)",
186
+ )
187
+ # Notify user the RegScale token needs to be updated
188
+ else:
189
+ error_and_exit(LOGIN_ERROR)
190
+
191
+
192
+ def save_inactive_users_from_okta(save_output_to: Path, file_type: str = ".csv", days: int = 30) -> None:
193
+ """
194
+ Function to get users that haven't logged in X days from Okta API
195
+ and saves the output as a .csv or .xlsx file.
196
+ :param Path save_output_to: The path to save the output file to
197
+ :param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
198
+ :param int days: The number of days to check for inactive users
199
+ :rtype: None
200
+ """
201
+ if file_type.lower() not in [".csv", ".xlsx"]:
202
+ error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
203
+
204
+ # Get Status of Client Application
205
+ app = check_license()
206
+ api = Api()
207
+
208
+ # check if RegScale token is valid:
209
+ if is_valid(app=app):
210
+ # get the token type from init.yaml
211
+ auth_type = app.config["oktaApiToken"].split(" ")
212
+
213
+ # authenticate with Okta
214
+ authenticate_with_okta(app, api, auth_type[0])
215
+
216
+ # use job_progress for live task progress
217
+ with job_progress:
218
+ # check file path exists, if not create it
219
+ check_file_path(save_output_to)
220
+
221
+ # calculate last login date criteria with days provided
222
+ since_date = datetime.now() - timedelta(days=days)
223
+
224
+ # get all users from Okta
225
+ users = get_all_okta_users(api)
226
+
227
+ # analyze the users
228
+ inactive_users = analyze_okta_users(
229
+ user_list=users,
230
+ key="lastLogin",
231
+ filter_value=since_date,
232
+ user_type="inactive",
233
+ )
234
+
235
+ # check, clean and save the data from okta to provided save_output_to and file_type
236
+ check_and_save_data(
237
+ data=inactive_users,
238
+ file_name="okta_inactive_users",
239
+ file_path=save_output_to,
240
+ file_type=file_type,
241
+ data_desc="inactive user(s)",
242
+ )
243
+ # Notify user the RegScale token needs to be updated
244
+ else:
245
+ error_and_exit(LOGIN_ERROR)
246
+
247
+
248
+ def save_all_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
249
+ """
250
+ Function to get all users from Okta via API and saves the output to a .csv or .xlsx file
251
+ :param Path save_output_to: The path to save the output file to
252
+ :param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
253
+ :rtype: None
254
+ """
255
+ if file_type.lower() not in [".csv", ".xlsx"]:
256
+ error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
257
+
258
+ # Get status of client application
259
+ app = check_license()
260
+ api = Api()
261
+
262
+ # check if RegScale token is valid:
263
+ if is_valid(app=app):
264
+ # get the token type from init.yaml
265
+ auth_type = app.config["oktaApiToken"].split(" ")
266
+
267
+ # authenticate with Okta
268
+ authenticate_with_okta(app, api, auth_type[0])
269
+
270
+ # check file path exists, if not create it
271
+ check_file_path(save_output_to)
272
+
273
+ # get all users from Okta
274
+ users = get_all_okta_users(api)
275
+
276
+ # check, clean and save the data from okta to provided save_output_to and file_type
277
+ check_and_save_data(
278
+ data=users,
279
+ file_name="okta_users",
280
+ file_path=save_output_to,
281
+ file_type=file_type,
282
+ data_desc="Okta users",
283
+ )
284
+ # Notify user the RegScale token needs to be updated
285
+ else:
286
+ error_and_exit(LOGIN_ERROR)
287
+
288
+
289
+ def save_recently_added_users_from_okta(save_output_to: Path, file_type: str = ".csv", days: int = 30) -> None:
290
+ """
291
+ Function to download users added in the last X days from Okta via API, defaults to last 30 days
292
+ and saves the output to a .csv or .xlsx file
293
+ :param Path save_output_to: The path to save the output file to
294
+ :param str file_type: The file type to save the output file as, .csv or .xlsx, defaults to .csv
295
+ :param int days: The number of days to check for recently added users, defaults to 30
296
+ :rtype: None
297
+ """
298
+ if file_type.lower() not in [".csv", ".xlsx"]:
299
+ error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
300
+ # Get Status of Client Application
301
+ app = check_license()
302
+ api = Api()
303
+
304
+ # check if RegScale token is valid:
305
+ if is_valid(app=app):
306
+ # get the token type from init.yaml
307
+ auth_type = app.config["oktaApiToken"].split(" ")
308
+
309
+ # authenticate with Okta
310
+ authenticate_with_okta(app, api, auth_type[0])
311
+
312
+ # use job_progress for live task progress
313
+ with job_progress:
314
+ # check file path exists, if not create it
315
+ check_file_path(save_output_to)
316
+
317
+ # calculate last login date criteria with days provided
318
+ since_date = datetime.now() - timedelta(days=days)
319
+
320
+ # get all users from Okta
321
+ users = get_all_okta_users(api)
322
+
323
+ # analyze the users
324
+ created_users = analyze_okta_users(
325
+ user_list=users,
326
+ key="created",
327
+ filter_value=since_date,
328
+ user_type="new",
329
+ )
330
+
331
+ # check, clean and save the data from Okta to provided save_output_to and file_type
332
+ check_and_save_data(
333
+ data=created_users,
334
+ file_name="okta_new_users",
335
+ file_path=save_output_to,
336
+ file_type=file_type,
337
+ data_desc="new user(s)",
338
+ )
339
+ # Notify user the RegScale token needs to be updated
340
+ else:
341
+ error_and_exit(LOGIN_ERROR)
342
+
343
+
344
+ def save_admin_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
345
+ """
346
+ Function to get all admin users from Okta via API and save the output to .csv or .xlsx file
347
+ :param Path save_output_to: The path to save the output file to
348
+ :param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
349
+ :rtype: None
350
+ """
351
+ if file_type.lower() not in [".csv", ".xlsx"]:
352
+ error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
353
+
354
+ # Get Status of Client Application
355
+ app = check_license()
356
+ api = Api()
357
+
358
+ # check if RegScale token is valid:
359
+ if is_valid(app=app):
360
+ # get the token type from init.yaml
361
+ auth_type = app.config["oktaApiToken"].split(" ")
362
+
363
+ # authenticate with Okta
364
+ authenticate_with_okta(app, api, auth_type[0])
365
+
366
+ # use job_progress for live task progress
367
+ with job_progress:
368
+ # check file path exists, if not create it
369
+ check_file_path(save_output_to)
370
+
371
+ # get all users from Okta
372
+ users = get_all_okta_users(api)
373
+
374
+ # create task for fetching user roles
375
+ fetch_user_roles = job_progress.add_task(
376
+ f"[#ef5d23]Fetching user roles for {len(users)} user(s)...",
377
+ total=len(users),
378
+ )
379
+
380
+ # create threads to get user roles for each user
381
+ create_threads(
382
+ process=get_user_roles,
383
+ args=(api, users, fetch_user_roles),
384
+ thread_count=len(users),
385
+ )
386
+
387
+ # check, clean and save the data from Okta to provided save_output_to and file_type
388
+ check_and_save_data(
389
+ data=admin_users,
390
+ file_name="okta_admin_users",
391
+ file_path=save_output_to,
392
+ file_type=file_type,
393
+ data_desc="admin user(s)",
394
+ )
395
+ # Notify user the RegScale token needs to be updated
396
+ else:
397
+ error_and_exit(LOGIN_ERROR)
398
+
399
+
400
+ def get_user_roles(args: Tuple, thread: int) -> None:
401
+ """
402
+ Function used by threads to get the roles for each Okta user
403
+ :param Tuple args: args for the threads to use during the function
404
+ :param int thread: Number of the current thread
405
+ :rtype: None
406
+ """
407
+ # set up my args from the args tuple
408
+ api, all_users, task = args
409
+
410
+ # set the headers for the Okta API Call
411
+ headers = {
412
+ "Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
413
+ "Accept": "application/json",
414
+ "Authorization": api.config["oktaApiToken"],
415
+ }
416
+
417
+ # get the thread assignment for the current thread
418
+ threads = thread_assignment(thread=thread, total_items=len(all_users))
419
+
420
+ # fetch the roles from Okta to get all the user's roles
421
+ for i in range(len(threads)):
422
+ user = all_users[threads[i]]
423
+
424
+ # get all the roles for the user
425
+ user_roles = get_okta_data(
426
+ api=api,
427
+ task=task,
428
+ url=f"{api.config['oktaUrl']}/api/v1/users/{user['id']}/roles",
429
+ headers=headers,
430
+ )
431
+ roles = [role["label"] for role in user_roles]
432
+ # add concatenated user roles to their entry in all_users
433
+ user["roles"] = ", ".join(roles)
434
+
435
+ # add user to global admin_user list if, admin is in their concatenated role list
436
+ (admin_users.append(user) if any("admin" in val.lower() for val in roles) else None)
437
+
438
+ # update the progress bar
439
+ job_progress.update(task, advance=1)
440
+
441
+
442
+ def analyze_okta_users(user_list: list, key: str, filter_value: Any, user_type: str) -> list:
443
+ """
444
+ Function to analyze users with the provided key and value
445
+ and returns users that match the criteria in a list
446
+ :param list user_list: Initial list of Okta users before being filtered
447
+ :param str key: Key to use to filter the Okta users
448
+ :param Any filter_value: Value used to filter Okta users with provided key
449
+ :param str user_type: Type of user we are filtering for, used for log outputs
450
+ :return: List of filtered Okta users using provided key and value
451
+ :rtype: list
452
+ """
453
+ logger.info("Analyzing %s Okta user(s).", len(user_list))
454
+ # create list to store the users that match the provided criteria
455
+ filtered_users = []
456
+
457
+ # create task for analyzing user's data
458
+ analyze_login = job_progress.add_task(f"[#ef5d23]Analyzing {len(user_list)} user(s) data...", total=len(user_list))
459
+ # iterate through all users and check user's with the provided criteria
460
+ for user in user_list:
461
+ if data_filter := user[key]:
462
+ # verify comparing filter_value date against a string
463
+ if isinstance(filter_value, datetime) and isinstance(data_filter, str):
464
+ # try to convert it
465
+ try:
466
+ data_filter = datetime.strptime(data_filter, "%Y-%m-%dT%H:%M:%S.%fZ")
467
+ # if an error is encountered, make it an old date
468
+ except (TypeError, KeyError, AttributeError):
469
+ error_and_exit("Incorrect date format. Please follow this format: '%Y-%m-%dT%H:%M:%S.%fZ'")
470
+ # compare the values as date objects instead of datetime objects
471
+ # if the user_type provided is inactive, make sure the provided date
472
+ # is between the correct date field and today
473
+ compare_dates_and_user_type(user, filtered_users, filter_value, user_type, data_filter, datetime.now())
474
+ elif data_filter is None:
475
+ filtered_users.append(user)
476
+ # update analyzing user task
477
+ job_progress.update(analyze_login, advance=1)
478
+ # notify user of how many inactive users we found
479
+ logger.info("Found %s %s user(s) in Okta.", len(filtered_users), user_type)
480
+
481
+ # return the users that match the provided criteria
482
+ return filtered_users
483
+
484
+
485
+ def compare_dates_and_user_type(
486
+ user: dict, filtered_users: list, filter_value: datetime, user_type: str, data_filter: Any, today: datetime
487
+ ):
488
+ """
489
+ Function to determine if the user matches the provided criteria and adds them to the filtered_users list
490
+
491
+ :param dict user: User to determine if they match the provided criteria
492
+ :param list filtered_users: List of users that match the provided criteria
493
+ :param datetime filter_value: Date to compare the user's date with
494
+ :param str user_type: Type of user we are filtering for, used to determine the correct date logic
495
+ :param Any data_filter: Date to compare the user's date with
496
+ :param datetime today: Today's date
497
+ """
498
+ # compare the values as date objects instead of datetime objects
499
+ # if the user_type provided is inactive, make sure the provided date
500
+ # is between the correct date field and today
501
+ if user_type == "inactive" and filter_value.date() >= data_filter.date() <= today.date():
502
+ # add user to filtered users list
503
+ filtered_users.append(user)
504
+ elif user_type == "new" and filter_value.date() <= data_filter.date() <= today.date():
505
+ # add user to filtered users list
506
+ filtered_users.append(user)
507
+
508
+
509
+ def check_and_save_data(data: list, file_name: str, file_path: Path, file_type: str, data_desc: str) -> None:
510
+ """
511
+ Function to check data returned from Okta API and cleans the response data and will save it
512
+ to the provided file_path as the provided file_type
513
+ :param list data: Raw data returned from Okta API
514
+ :param str file_name: Desired name of the output file
515
+ :param Path file_path: Directory to save the cleaned data to
516
+ :param str file_type: Desired file type to output the data to
517
+ :param str data_desc: Description of the data, used in output and logs
518
+ :rtype: None
519
+ """
520
+ logger.info("Starting to clean and format data from Okta.")
521
+
522
+ # check Okta data has data
523
+ if len(data) >= 1:
524
+ # use job_progress for live task progress
525
+ with job_progress:
526
+ # generate file name with today's date
527
+ file_name = f"{file_name}_{get_current_datetime('%m%d%Y')}"
528
+
529
+ # create task for cleaning the data response from Okta
530
+ clean_data_task = job_progress.add_task("[#21a5bb]Cleaning data from Okta...", total=1)
531
+ # clean the data from Okta
532
+ clean_data = clean_okta_output(data=data, skip_keys=["_links"])
533
+
534
+ # update the task as complete
535
+ job_progress.update(clean_data_task, advance=1)
536
+
537
+ # create task for saving file
538
+ saving_file_task = job_progress.add_task(
539
+ f"[#0866b4]Saving {len(clean_data)} {data_desc} to {file_path}/{file_name}{file_type}...",
540
+ total=1,
541
+ )
542
+ # save the output to the provided file_path
543
+ save_data_to(
544
+ file=Path(f"{file_path}/{file_name}{file_type}"),
545
+ data=clean_data,
546
+ )
547
+ # mark saving_file_task as complete
548
+ job_progress.update(saving_file_task, advance=1)
549
+ logger.info(
550
+ "Saved %s %s successfully to %s%s!",
551
+ len(clean_data),
552
+ data_desc,
553
+ file_path,
554
+ file_type,
555
+ )
556
+ else:
557
+ logger.info("No %s to save to %s!", data_desc, file_path)
558
+
559
+
560
+ def get_all_okta_users(api: Api) -> list:
561
+ """
562
+ Function to get all Okta users using the Okta API
563
+
564
+ :param Api api: API object
565
+ :return: List of all Okta users
566
+ :rtype: list
567
+ """
568
+ logger.info("Fetching all users from Okta.")
569
+ # add task for fetching users from Okta
570
+ fetching_users = job_progress.add_task("[#f8b737]Fetching all users from Okta...", total=1)
571
+
572
+ # fetch active users from Okta
573
+ users = get_okta_data(
574
+ api=api,
575
+ url=f"{api.config['oktaUrl']}/api/v1/users",
576
+ headers={
577
+ "Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
578
+ "Accept": "application/json",
579
+ "Authorization": api.config["oktaApiToken"],
580
+ },
581
+ task=fetching_users,
582
+ )
583
+ # notify user of how many active users we found
584
+ logger.info("Found %s Okta user(s).", len(users))
585
+ # return all users from Okta
586
+ return users
587
+
588
+
589
+ def get_okta_data(api: Api, task: int, url: str, headers: dict, params: Tuple = None) -> list:
590
+ """
591
+ Function to use the Okta core API to get data, also handles pagination for Okta requests
592
+
593
+ :param Api api: API object
594
+ :param int task: task to update to show user live progress
595
+ :param str url: URL to use while using Okta API
596
+ :param dict headers: Headers for the Okta API call
597
+ :param Tuple params: Parameters for Okta API call, defaults to None
598
+ :return: List of data received from the API call to Okta
599
+ :rtype: list
600
+ """
601
+ # get data from Okta with provided information
602
+ okta_response = api.get(url=url, headers=headers, params=params)
603
+ # check the response
604
+ if okta_response.status_code == 403:
605
+ error_and_exit(
606
+ "RegScale CLI wasn't granted the necessary permissions for this action."
607
+ + "Please verify permissions in Okta admin portal and try again."
608
+ )
609
+ elif okta_response.status_code != 200:
610
+ error_and_exit(
611
+ f"Received unexpected response from Okta API.\n{okta_response.status_code}: {okta_response.text}"
612
+ )
613
+ try:
614
+ # try to read the response and convert it to a JSON object
615
+ okta_data = okta_response.json()
616
+ except JSONDecodeError as ex:
617
+ # notify user if there was a json decode error from API response and exit
618
+ error_and_exit(f"JSON decode error.\n{ex}")
619
+ # check if pagination required to fetch all data
620
+ response_links = okta_response.headers.get("link")
621
+ if okta_response.status_code == 200 and "next" in response_links:
622
+ # get the url for the next pagination
623
+ url = parse_url_for_pagination(response_links)
624
+
625
+ # get the next page of data
626
+ okta_data.extend(get_okta_data(api=api, task=task, url=url, headers=headers))
627
+ elif okta_response.status_code != 200:
628
+ error_and_exit(
629
+ f"Received unexpected response from Okta!\nReceived: {okta_response.status_code}\n{okta_response.text}"
630
+ )
631
+ # mark the provided task as complete
632
+ job_progress.update(task, advance=1)
633
+
634
+ # return the Okta data
635
+ return okta_data
636
+
637
+
638
+ def clean_okta_output(data: list, skip_keys: list = None) -> dict:
639
+ """
640
+ Function to remove nested dictionaries and the provided skip_key of the
641
+ list data from Okta API response and returns a clean dictionary without any nested dictionaries
642
+
643
+ :param list data: List of raw data from Okta
644
+ :param list skip_keys: List of Keys to skip while cleaning the raw data
645
+ :return: Dictionary of clean Okta data
646
+ :rtype: dict
647
+ """
648
+ logger.info("Cleaning Okta data.")
649
+ logger.debug("\nRaw data: %s\n", data)
650
+ # create empty dictionary to store clean data
651
+ clean_data = {}
652
+ # iterate through each item in the provided list
653
+ for row in data:
654
+ # get a row of data with nested dictionaries as key value pairs
655
+ # while also remove the _links nested dictionary
656
+ new_row_data = remove_nested_dict(data=row, skip_keys=skip_keys)
657
+ # iterate through the original data of nested dicts and remove them
658
+ # from our clean data
659
+ for item in reversed(row):
660
+ # check if the item is a nested dictionary and exists in our clean data
661
+ if isinstance(row[item], dict) and item in new_row_data:
662
+ # remove the nested dictionary from the clean data
663
+ del new_row_data[item]
664
+ # update the old data with the new data
665
+ clean_data[data.index(row)] = new_row_data
666
+ logger.info("Okta data has been cleaned successfully.")
667
+ logger.debug("\nClean data: %s\n", clean_data)
668
+ # return the clean data set without nested dictionaries
669
+ return clean_data
670
+
671
+
672
+ def authenticate_with_okta(app: Application, api: Api, type: str) -> None:
673
+ """
674
+ Function to authenticate with Okta via API and the provided method in type
675
+
676
+ :param Application app: Application object
677
+ :param Api api: API object
678
+ :param str type: type of authentication for the Okta API
679
+ :rtype: None
680
+ """
681
+ config = app.config
682
+ if type.lower() == "ssws":
683
+ # verify the provided SSWS token from init.yaml
684
+ verify_response = api.get(
685
+ url=f"{config['oktaUrl']}/api/v1/users",
686
+ headers={
687
+ "Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
688
+ "Accept": "application/json",
689
+ "Authorization": config["oktaApiToken"],
690
+ },
691
+ )
692
+ if verify_response.ok:
693
+ logger.info("Okta SSWS Token has been verified.")
694
+ else:
695
+ error_and_exit(
696
+ "Please verify SSWS Token from Okta is entered correctly in init.yaml, "
697
+ + "and it has okta.users.read & okta.roles.read permissions granted and try again."
698
+ )
699
+ elif type.lower() == "bearer":
700
+ # check if secret key is in the init.yaml config
701
+ key = config.get("oktaSecretKey")
702
+
703
+ # if it exists try to get a bearer token from Okta API
704
+ if key:
705
+ token = get_okta_token(config=config, api=api, app=app)
706
+ logger.info("New Okta Token: %s", token)
707
+ else:
708
+ # create the init.yaml entry for the oktaSecretKey and prompt user to get it from admin portal
709
+ config["oktaSecretKey"] = {
710
+ "d": "get from Okta",
711
+ "p": "get from Okta",
712
+ "q": "get from Okta",
713
+ "dp": "get from Okta",
714
+ "dq": "get from Okta",
715
+ "qi": "get from Okta",
716
+ "kty": "get from Okta",
717
+ "e": "get from Okta",
718
+ "kid": "get from Okta",
719
+ "n": "get from Okta",
720
+ }
721
+ config["oktaScopes"] = "okta.users.read okta.roles.read"
722
+ app.save_config(config)
723
+ logger.info(
724
+ "Please enter the private key for the application created in Okta admin"
725
+ + "portal into init.yaml file and try again."
726
+ )
727
+ else:
728
+ error_and_exit(
729
+ "Please enter a valid authentication type for Okta API and try again. Please choose from SSWS or Bearer."
730
+ )
731
+
732
+
733
+ def get_okta_token(config: dict, api: Api, app: Application) -> str:
734
+ """
735
+ Function to create a JWT to get a bearer token from Okta via API
736
+
737
+ :param dict config: Application configuration (init.yaml)
738
+ :param Api api: API object
739
+ :param Application app: Application object
740
+ :return: JWT token for future requests
741
+ :rtype: str
742
+ """
743
+ okta_token = ""
744
+
745
+ # get the Okta private JWK
746
+ jwk_token = jwk.JWK.from_json(json.dumps(config["oktaSecretKey"]))
747
+
748
+ # get the url from config without any trailing /
749
+ url = config["oktaUrl"].strip("/") + "/oauth2/v1/token"
750
+
751
+ # set the payload for the to be signed JWT while setting the signed JWT to expire in 10 minutes
752
+ payload_data = {
753
+ "aud": url,
754
+ "iss": config["oktaClientId"],
755
+ "sub": config["oktaClientId"],
756
+ "exp": int(time.time()) + 600,
757
+ }
758
+
759
+ # create a signed JWT
760
+ token = python_jwt.generate_jwt(payload_data, jwk_token, "RS256", timedelta(minutes=5))
761
+
762
+ # set the headers for the API call
763
+ headers = {
764
+ "Accept": "application/json",
765
+ "Content-Type": "application/x-www-form-urlencoded",
766
+ }
767
+
768
+ # set up the data to the expected format for Okta API call
769
+ payload = (
770
+ f'grant_type=client_credentials&scope={config["oktaScopes"]}&client_assertion_type='
771
+ + f"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion={token}"
772
+ )
773
+
774
+ # post the data to get a bearer token for future requests
775
+ token_response = api.post(url=url, headers=headers, data=payload)
776
+
777
+ # see if the API call was successful
778
+ if token_response.status_code == 200:
779
+ try:
780
+ # convert response to a JSON object
781
+ token = token_response.json()
782
+
783
+ # format the bearer token returned
784
+ okta_token = f'{token["token_type"]} {token["access_token"]}'
785
+
786
+ # update the config with the newly received JWT
787
+ config["oktaApiToken"] = okta_token
788
+
789
+ # save it to init.yaml
790
+ app.save_config(config)
791
+ except JSONDecodeError:
792
+ # unable to convert the API response to a json object
793
+ error_and_exit("Unable to retrieve data from Okta API.")
794
+ else:
795
+ error_and_exit(
796
+ f"Received unexpected response from Okta API.\n{token_response.status_code}: {token_response.text}\n{token}"
797
+ )
798
+ return okta_token