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,571 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """standard imports"""
4
+
5
+ import concurrent.futures
6
+ import logging
7
+ import os
8
+ import re
9
+ import warnings
10
+ from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
11
+
12
+ if TYPE_CHECKING:
13
+ from regscale.core.app.application import Application
14
+
15
+ import requests
16
+ from requests.adapters import HTTPAdapter, Retry
17
+ from rich.progress import Progress
18
+ from urllib3 import disable_warnings
19
+ from urllib3.exceptions import InsecureRequestWarning
20
+
21
+ from regscale.core.app.internal.login import login, verify_token
22
+
23
+
24
+ class Api:
25
+ """Wrapper for interacting with the RegScale API
26
+
27
+ :param Optional[Application] app: Application object, defaults to None
28
+ :param int timeout: timeout for API calls, defaults to 10
29
+ :param Union[int, str] retry: number of retries for API calls, defaults to 5
30
+ """
31
+
32
+ _app: "Application"
33
+ app: "Application"
34
+ _retry_log: str = "Retrying request with new token."
35
+ _no_res_text: str = "No response text available"
36
+
37
+ def __init__(
38
+ self,
39
+ timeout: int = int(os.getenv("REGSCALE_TIMEOUT", 10)),
40
+ retry: int = 5,
41
+ ):
42
+ from regscale.core.app.application import Application
43
+ from regscale.integrations.variables import ScannerVariables
44
+
45
+ if isinstance(timeout, str):
46
+ timeout = int(timeout)
47
+
48
+ self.verify = True
49
+ self.timeout = timeout
50
+ self.accept = "application/json"
51
+ self.content_type = "application/json"
52
+ r_session = requests.Session()
53
+ self.pool_connections = 200
54
+ self.pool_maxsize = 200
55
+ self.retries = Retry(total=retry, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
56
+ self.auth = None
57
+ super().__init__()
58
+ self.app = Application()
59
+ self.logger = logging.getLogger("regscale")
60
+ self.verify = ScannerVariables.sslVerify
61
+ if not self.verify:
62
+ self.logger.warning("SSL Verification has been disabled.")
63
+ r_session.verify = False
64
+ disable_warnings(InsecureRequestWarning)
65
+ if self.config and "timeout" in self.config:
66
+ self.timeout = self.config["timeout"]
67
+ # get the user's domain prefix eg https:// or http://
68
+ domain = self.config.get("domain") or self.app.retrieve_domain()
69
+ domain = domain[: (domain.find("://") + 3)]
70
+ r_session.mount(
71
+ domain,
72
+ HTTPAdapter(
73
+ max_retries=self.retries,
74
+ pool_connections=self.pool_connections,
75
+ pool_maxsize=self.pool_maxsize,
76
+ pool_block=True,
77
+ ),
78
+ )
79
+ self.session = r_session
80
+
81
+ @property
82
+ def config(self) -> dict:
83
+ """
84
+ Get the application config
85
+
86
+ :return: Application config
87
+ :rtype: dict
88
+ """
89
+ return self.app.config
90
+
91
+ def get(
92
+ self,
93
+ url: str,
94
+ headers: Optional[dict] = None,
95
+ params: Optional[Union[list, Tuple]] = None,
96
+ retry_login: bool = True,
97
+ merge_headers: bool = False,
98
+ ) -> Optional[requests.models.Response]:
99
+ """
100
+ Get Request for API
101
+
102
+ :param str url: URL for API call
103
+ :param dict headers: headers for the api get call, defaults to None
104
+ :param Optional[Union[list, Tuple]] params: Any parameters for the API call, defaults to None
105
+ :param bool retry_login: Whether to retry login on 401 Unauthorized responses, defaults to True
106
+ :param bool merge_headers: Whether to merge headers, defaults to False
107
+ :return: Requests response
108
+ :rtype: Optional[requests.models.Response]
109
+ """
110
+ url = normalize_url(url)
111
+ if self.auth:
112
+ self.session.auth = self.auth
113
+ headers = self._handle_headers(headers, merge_headers)
114
+ response = None
115
+ try:
116
+ self.logger.debug("GET: %s", url)
117
+ response = self.session.get(
118
+ url=url,
119
+ headers=headers,
120
+ params=params,
121
+ timeout=self.timeout,
122
+ )
123
+ if response.status_code == 401 and self._handle_401(retry_login):
124
+ self.logger.debug(self._retry_log)
125
+ response = self.get(url=url, headers=headers, params=params, retry_login=False)
126
+ return response
127
+ except Exception as e:
128
+ self._log_response_error(url, e, response)
129
+ return response
130
+ finally:
131
+ resp_text = getattr(response, "text", self._no_res_text)
132
+ self.logger.debug(f"{resp_text[:500]}..." if len(str(resp_text)) > 500 else resp_text)
133
+
134
+ def delete(
135
+ self, url: str, headers: Optional[dict] = None, retry_login: bool = True, merge_headers: bool = False
136
+ ) -> requests.models.Response:
137
+ """
138
+ Delete data using API
139
+
140
+ :param str url: URL for the API call
141
+ :param Optional[dict] headers: headers for the API call, defaults to None
142
+ :param bool retry_login: Whether to retry login on 401 Unauthorized responses, defaults to True
143
+ :param bool merge_headers: Whether to merge headers, defaults to False
144
+ :return: API response
145
+ :rtype: requests.models.Response
146
+ """
147
+ url = normalize_url(url)
148
+ if self.auth:
149
+ self.session.auth = self.auth
150
+ headers = self._handle_headers(headers, merge_headers)
151
+ response = None
152
+ try:
153
+ self.logger.debug("Delete: %s", url)
154
+ response = self.session.delete(
155
+ url=url,
156
+ headers=headers,
157
+ timeout=self.timeout,
158
+ )
159
+ if response.status_code == 401 and self._handle_401(retry_login):
160
+ self.logger.debug(self._retry_log)
161
+ response = self.delete(url=url, headers=headers, retry_login=False)
162
+ return response
163
+ except Exception as e:
164
+ self._log_response_error(url, e, response)
165
+ return response
166
+ finally:
167
+ self.logger.debug(getattr(response, "text", self._no_res_text))
168
+
169
+ def post(
170
+ self,
171
+ url: str,
172
+ headers: Optional[dict] = None,
173
+ json: Optional[Union[dict, str, list]] = None,
174
+ data: Optional[dict] = None,
175
+ files: Optional[list] = None,
176
+ params: Any = None,
177
+ retry_login: bool = True,
178
+ merge_headers: bool = False,
179
+ ) -> Optional[requests.models.Response]:
180
+ """
181
+ Post data to API
182
+
183
+ :param str url: URL for the API call
184
+ :param dict headers: Headers for the API call, defaults to None
185
+ :param Optional[Union[dict, str, list]] json: Dictionary of data for the API call, defaults to None
186
+ :param dict data: Dictionary of data for the API call, defaults to None
187
+ :param list files: Files to post during API call, defaults to None
188
+ :param Any params: Any parameters for the API call, defaults to None
189
+ :param bool retry_login: Whether to retry login on 401 Unauthorized responses, defaults to True
190
+ :param bool merge_headers: Whether to merge headers, defaults to False
191
+ :return: API response
192
+ :rtype: Optional[requests.models.Response]
193
+ """
194
+ url = normalize_url(url)
195
+ if self.auth:
196
+ self.session.auth = self.auth
197
+ headers = self._handle_headers(headers, merge_headers)
198
+ response = None
199
+ try:
200
+ self.logger.debug("POST: %s", url)
201
+ if not json and data:
202
+ response = self.session.post(
203
+ url=url,
204
+ headers=headers,
205
+ data=data,
206
+ files=files,
207
+ params=params,
208
+ timeout=self.timeout,
209
+ )
210
+ else:
211
+ response = self.session.post(
212
+ url=url,
213
+ headers=headers,
214
+ json=json,
215
+ files=files,
216
+ params=params,
217
+ timeout=self.timeout,
218
+ )
219
+ if getattr(response, "status_code", 0) == 401 and self._handle_401(retry_login):
220
+ self.logger.debug(self._retry_log)
221
+ response = self.post(
222
+ url=url,
223
+ headers=headers,
224
+ json=json,
225
+ data=data,
226
+ files=files,
227
+ params=params,
228
+ retry_login=False,
229
+ )
230
+ return response
231
+ except Exception as e:
232
+ self._log_response_error(url, e, response)
233
+ return response
234
+ finally:
235
+ self.logger.debug(getattr(response, "text", self._no_res_text))
236
+
237
+ def put(
238
+ self,
239
+ url: str,
240
+ headers: Optional[dict] = None,
241
+ json: Optional[Union[dict, List[dict]]] = None,
242
+ params: Optional[Union[list, Tuple]] = None,
243
+ retry_login: bool = True,
244
+ merge_headers: bool = False,
245
+ ) -> Optional[requests.models.Response]:
246
+ """
247
+ Update data via API call
248
+
249
+ :param str url: URL for the API call
250
+ :param Optional[dict] headers: Headers for the API call, defaults to None
251
+ :param Optional[Union[dict, List[dict]]] json: Dictionary of data for the API call, defaults to None
252
+ :param Optional[Union[list, Tuple]] params: Any parameters for the API call, defaults to None
253
+ :param bool retry_login: Whether to retry login on 401 Unauthorized responses, defaults to True
254
+ :param bool merge_headers: Whether to merge headers, defaults to False
255
+ :return: API response if
256
+ :rtype: Optional[requests.models.Response]
257
+ """
258
+ url = normalize_url(url)
259
+ if self.auth:
260
+ self.session.auth = self.auth
261
+ headers = self._handle_headers(headers, merge_headers)
262
+ response = None
263
+ try:
264
+ self.logger.debug("PUT: %s", url)
265
+ response = self.session.put(
266
+ url=url,
267
+ headers=headers,
268
+ json=json,
269
+ params=params,
270
+ timeout=self.timeout,
271
+ )
272
+
273
+ if getattr(response, "status_code", 0) == 401 and self._handle_401(retry_login):
274
+ self.logger.debug(self._retry_log)
275
+ response = self.put(
276
+ url=url,
277
+ headers=headers,
278
+ json=json,
279
+ params=params,
280
+ retry_login=False,
281
+ )
282
+ return response
283
+ except Exception as e:
284
+ self._log_response_error(url, e, response)
285
+ return response
286
+ finally:
287
+ self.logger.debug(getattr(response, "text", self._no_res_text))
288
+
289
+ def graph(
290
+ self,
291
+ query: str,
292
+ url: Optional[str] = None,
293
+ headers: Optional[dict] = None,
294
+ res_data: Optional[dict] = None,
295
+ merge_headers: bool = False,
296
+ ) -> dict:
297
+ """
298
+ Execute GraphQL query and handles pagination before returning the data to the API call
299
+
300
+ :param str query: the GraphQL query to execute
301
+ :param Optional[str] url: URL for the API call, defaults to None
302
+ :param Optional[dict] headers: Headers for the API call, defaults to None
303
+ :param Optional[dict] res_data: dictionary of data from GraphQL response, only used during pagination & recursion
304
+ :param bool merge_headers: Whether to merge headers, defaults to False
305
+ :return: Dictionary response from GraphQL API
306
+ :rtype: dict
307
+ """
308
+ self.logger.debug("STARTING NEW GRAPH CALL")
309
+ self.logger.debug("=" * 50)
310
+ response_data = {}
311
+ pagination_flag = False
312
+ # change the timeout to match the timeout of the GraphQL timeout in the application
313
+ self.timeout = 90
314
+ if self.auth:
315
+ self.session.auth = self.auth
316
+ headers = self._handle_headers(headers, merge_headers)
317
+ # check the query if skip was provided, if not add it for pagination
318
+ if "skip" not in query:
319
+ query = query.replace("(", "(skip: 0\n")
320
+ # set the url for the query
321
+ url = normalize_url(f'{self.config["domain"]}/graphql' if url is None else url)
322
+ self.logger.debug(f"{url=}")
323
+ self.logger.debug(f"{query=}")
324
+ # make the API call
325
+ response = self.session.post(
326
+ url=normalize_url(url),
327
+ headers=headers,
328
+ json={"query": query},
329
+ timeout=self.timeout,
330
+ )
331
+ self.logger.debug(f"{response.text=}")
332
+ try:
333
+ response_json = response.json()
334
+ if "errors" in response_json:
335
+ self.logger.error("GraphQL query returned errors:")
336
+ for error in response_json["errors"]:
337
+ self.logger.error(f"Message: {error.get('message')}")
338
+ self.logger.error(f"Locations: {error.get('locations')}")
339
+ self.logger.error(f"Path: {error.get('path')}")
340
+ self.logger.error(f"Query that caused the error: {query}", exc_info=True)
341
+ return {} # Return an empty dict instead of exiting
342
+ # convert response to JSON object
343
+ response_data = response_json["data"]
344
+ self.logger.debug(f"{response_data=}")
345
+ # iterate through and add it to the res_data if needed
346
+ for key, value in response_data.items():
347
+ # add the new API response data to the data from previous call
348
+ if res_data:
349
+ res_data[key]["items"].extend(response_data[key]["items"])
350
+ # check if pagination required
351
+ try:
352
+ if value.get("pageInfo").get("hasNextPage") is True and not pagination_flag:
353
+ # set pagination_flag to true
354
+ pagination_flag = True
355
+ # find the location of the old skip in the query and parse the int after it
356
+ old_skip_match = re.search(r"skip: (\d+)", query)
357
+ if old_skip_match:
358
+ old_skip = int(old_skip_match.group(1))
359
+
360
+ # set the new value of the skip using old + # of items returned
361
+ new_skip = old_skip + len(response_data[key]["items"])
362
+
363
+ # replace the old skip value with the new skip value that was calculated
364
+ query = query.replace(f"skip: {old_skip}", f"skip: {new_skip}")
365
+ else:
366
+ # If no skip found, add it to the beginning of the query
367
+ new_skip = len(response_data[key]["items"])
368
+ query = query.replace("(", f"(skip: {new_skip}\n", 1)
369
+
370
+ # if no previous pagination, break this loop
371
+ if not res_data:
372
+ break
373
+ except (KeyError, AttributeError):
374
+ continue
375
+ except requests.exceptions.JSONDecodeError as err:
376
+ self.logger.error("Received JSONDecodeError!\n%s", err)
377
+ self.logger.debug("%i: %s - %s", response.status_code, response.text, response.reason)
378
+ return {}
379
+ except KeyError as err:
380
+ self.logger.error("No items were returned from %s!\n%s", url, err)
381
+ self.logger.debug("%i: %s - %s", response.status_code, response.text, response.reason)
382
+ return {}
383
+ # check if already called for recursion
384
+ # res_data: set data to pagination data
385
+ # response_data: most recent API call
386
+ data = res_data or response_data
387
+ if pagination_flag:
388
+ # recall the function with the new query and extend the data with the results
389
+ response_data = self.graph(url=url, headers=headers, query=query, res_data=data)
390
+ # set the data to the pagination data
391
+ data = response_data
392
+ # return the data
393
+ return data
394
+
395
+ def update_server(
396
+ self,
397
+ url: str,
398
+ headers: Optional[dict] = None,
399
+ json_list: Optional[list] = None,
400
+ method: str = "post",
401
+ config: Optional[dict] = None,
402
+ message: str = "Working",
403
+ ) -> None:
404
+ """
405
+ Concurrent Post or Put of multiple objects
406
+
407
+ The 'update_server' method is deprecated, use 'RegScaleModel' create or update methods instead
408
+
409
+ :param str url: URL for the API call
410
+ :param Optional[dict] headers: Headers for the API call, defaults to None
411
+ :param Optional[list] json_list: Dictionary of data for the API call, defaults to None
412
+ :param str method: Method for API to use, defaults to "post"
413
+ :param Optional[dict] config: Config for the API, defaults to None
414
+ :param str message: Message to display in console, defaults to "Working"
415
+ :rtype: None
416
+ """
417
+ warnings.warn(
418
+ "The 'update_server' method is deprecated, use 'RegScaleModel' create or update methods instead",
419
+ DeprecationWarning,
420
+ )
421
+ if headers is None and config:
422
+ headers = {"Accept": self.accept, "Authorization": config["token"]}
423
+
424
+ if json_list and len(json_list) > 0:
425
+ with Progress(transient=False) as progress:
426
+ task = progress.add_task(message, total=len(json_list))
427
+ with concurrent.futures.ThreadPoolExecutor(max_workers=self.config["maxThreads"]) as executor:
428
+ if method.lower() == "post":
429
+ result_futures = list(
430
+ map(
431
+ lambda x: executor.submit(self.post, url, headers, x),
432
+ json_list,
433
+ )
434
+ )
435
+ if method.lower() == "put":
436
+ result_futures = list(
437
+ map(
438
+ lambda x: executor.submit(self.put, f"{url}/{x['id']}", headers, x),
439
+ json_list,
440
+ )
441
+ )
442
+ if method.lower() == "delete":
443
+ result_futures = list(
444
+ map(
445
+ lambda x: executor.submit(self.delete, f"{url}/{x['id']}", headers),
446
+ json_list,
447
+ )
448
+ )
449
+ for future in concurrent.futures.as_completed(result_futures):
450
+ try:
451
+ if future.result().status_code != 200:
452
+ self.logger.warning(
453
+ "Status code is %s: %s from %s.",
454
+ future.result().status_code,
455
+ future.result().text,
456
+ future.result().url,
457
+ )
458
+ progress.update(task, advance=1)
459
+ except Exception as ex:
460
+ self.logger.error("Error is %s, type: %s", ex, type(ex))
461
+
462
+ def _log_response_error(
463
+ self,
464
+ url: str,
465
+ error: Exception,
466
+ response: Optional[requests.Response],
467
+ ) -> None:
468
+ """
469
+ Log error messages from API responses
470
+
471
+ :param str url: URL for the API call
472
+ :param Exception error: Exception message
473
+ :param Optional[requests.Response] response: API response
474
+ :param str url: URL for the API call
475
+ """
476
+ if response is None:
477
+ self.logger.error("Received unexpected response from %s: %s", url, error)
478
+ else:
479
+ self.logger.error(
480
+ "Received unexpected response from %s %s\nStatus code %s: %s\nText: %s",
481
+ url,
482
+ error,
483
+ response.status_code,
484
+ response.reason,
485
+ response.text,
486
+ )
487
+
488
+ def _handle_login_on_401(
489
+ self,
490
+ retry_login: bool = True,
491
+ ) -> bool:
492
+ """
493
+ Handle login on 401 Unauthorized responses
494
+
495
+ :param bool retry_login: Whether to retry login or not, defaults to True
496
+ :return: Whether login was successful
497
+ :rtype: bool
498
+ """
499
+ token = self.config.get("token")
500
+ if token and "Bearer " in token:
501
+ token = token.split("Bearer ")[1]
502
+ self.logger.debug("verifying token")
503
+ is_token_valid = verify_token(app=self.app, token=token)
504
+ self.logger.debug(f"is token valid: {is_token_valid}")
505
+ if not is_token_valid:
506
+ self.logger.debug("getting new token")
507
+ new_token = login(
508
+ app=self.app,
509
+ str_user=os.getenv("REGSCALE_USERNAME"),
510
+ str_password=os.getenv("REGSCALE_PASSWORD"),
511
+ host=self.config["domain"],
512
+ )
513
+ self.logger.debug("Token: %s", new_token[:20])
514
+ return retry_login
515
+ return False
516
+
517
+ def _handle_401(self, retry_login: bool) -> bool:
518
+ """
519
+ Handle 401 Unauthorized responses.
520
+
521
+ :param bool retry_login: Whether to retry login
522
+ :return: True if login was retried, False otherwise
523
+ :rtype: bool
524
+ """
525
+ if self._handle_login_on_401(retry_login=retry_login):
526
+ self.logger.debug("Retrying request with new token.")
527
+ return True
528
+ return False
529
+
530
+ def _handle_headers(self, headers: Optional[dict], merge_headers=False) -> dict:
531
+ """
532
+ Handle headers for API calls
533
+
534
+ :param Optional[dict] headers: Headers for the API call
535
+ :param bool merge_headers: Whether to merge headers with defaults
536
+ :return: Dictionary of headers
537
+ :rtype: dict
538
+ """
539
+ default_headers = {
540
+ "accept": self.accept,
541
+ "Content-Type": self.content_type,
542
+ }
543
+ if token := self.config.get("token"):
544
+ default_headers["Authorization"] = token
545
+
546
+ if headers is None:
547
+ headers = default_headers
548
+
549
+ headers = headers or {}
550
+
551
+ if merge_headers:
552
+ return {**default_headers, **headers}
553
+
554
+ return headers
555
+
556
+
557
+ def normalize_url(url: str) -> str:
558
+ """
559
+ Function to remove extra slashes and trailing slash from a given URL
560
+
561
+ :param str url: URL string normalize
562
+ :return: A normalized URL
563
+ :rtype: str
564
+ """
565
+ segments = url.split("/")
566
+ correct_segments = [segment for segment in segments if segment != ""]
567
+ first_segment = str(correct_segments[0])
568
+ if "http" not in first_segment:
569
+ correct_segments = ["http:"] + correct_segments
570
+ correct_segments[0] = f"{correct_segments[0]}/"
571
+ return "/".join(correct_segments)