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.
- regscale/__init__.py +1 -0
- regscale/airflow/__init__.py +9 -0
- regscale/airflow/azure/__init__.py +9 -0
- regscale/airflow/azure/cli.py +89 -0
- regscale/airflow/azure/upload_dags.py +116 -0
- regscale/airflow/click_dags.py +127 -0
- regscale/airflow/click_mixins.py +82 -0
- regscale/airflow/config.py +25 -0
- regscale/airflow/factories/__init__.py +0 -0
- regscale/airflow/factories/connections.py +58 -0
- regscale/airflow/factories/workflows.py +78 -0
- regscale/airflow/hierarchy.py +88 -0
- regscale/airflow/operators/__init__.py +0 -0
- regscale/airflow/operators/click.py +36 -0
- regscale/airflow/sensors/__init__.py +0 -0
- regscale/airflow/sensors/sql.py +107 -0
- regscale/airflow/sessions/__init__.py +0 -0
- regscale/airflow/sessions/sql/__init__.py +3 -0
- regscale/airflow/sessions/sql/queries.py +64 -0
- regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
- regscale/airflow/tasks/__init__.py +0 -0
- regscale/airflow/tasks/branches.py +22 -0
- regscale/airflow/tasks/cli.py +116 -0
- regscale/airflow/tasks/click.py +73 -0
- regscale/airflow/tasks/debugging.py +9 -0
- regscale/airflow/tasks/groups.py +116 -0
- regscale/airflow/tasks/init.py +60 -0
- regscale/airflow/tasks/states.py +47 -0
- regscale/airflow/tasks/workflows.py +36 -0
- regscale/ansible/__init__.py +9 -0
- regscale/core/__init__.py +0 -0
- regscale/core/app/__init__.py +3 -0
- regscale/core/app/api.py +571 -0
- regscale/core/app/application.py +665 -0
- regscale/core/app/internal/__init__.py +136 -0
- regscale/core/app/internal/admin_actions.py +230 -0
- regscale/core/app/internal/assessments_editor.py +873 -0
- regscale/core/app/internal/catalog.py +316 -0
- regscale/core/app/internal/comparison.py +459 -0
- regscale/core/app/internal/control_editor.py +571 -0
- regscale/core/app/internal/encrypt.py +79 -0
- regscale/core/app/internal/evidence.py +1240 -0
- regscale/core/app/internal/file_uploads.py +151 -0
- regscale/core/app/internal/healthcheck.py +66 -0
- regscale/core/app/internal/login.py +305 -0
- regscale/core/app/internal/migrations.py +240 -0
- regscale/core/app/internal/model_editor.py +1701 -0
- regscale/core/app/internal/poam_editor.py +632 -0
- regscale/core/app/internal/workflow.py +105 -0
- regscale/core/app/logz.py +74 -0
- regscale/core/app/utils/XMLIR.py +258 -0
- regscale/core/app/utils/__init__.py +0 -0
- regscale/core/app/utils/api_handler.py +358 -0
- regscale/core/app/utils/app_utils.py +1110 -0
- regscale/core/app/utils/catalog_utils/__init__.py +0 -0
- regscale/core/app/utils/catalog_utils/common.py +91 -0
- regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
- regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
- regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
- regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
- regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
- regscale/core/app/utils/file_utils.py +238 -0
- regscale/core/app/utils/parser_utils.py +81 -0
- regscale/core/app/utils/pickle_file_handler.py +57 -0
- regscale/core/app/utils/regscale_utils.py +319 -0
- regscale/core/app/utils/report_utils.py +119 -0
- regscale/core/app/utils/variables.py +226 -0
- regscale/core/decorators.py +31 -0
- regscale/core/lazy_group.py +65 -0
- regscale/core/login.py +63 -0
- regscale/core/server/__init__.py +0 -0
- regscale/core/server/flask_api.py +473 -0
- regscale/core/server/helpers.py +373 -0
- regscale/core/server/rest.py +64 -0
- regscale/core/server/static/css/bootstrap.css +6030 -0
- regscale/core/server/static/css/bootstrap.min.css +6 -0
- regscale/core/server/static/css/main.css +176 -0
- regscale/core/server/static/images/regscale-cli.svg +49 -0
- regscale/core/server/static/images/regscale.svg +38 -0
- regscale/core/server/templates/base.html +74 -0
- regscale/core/server/templates/index.html +43 -0
- regscale/core/server/templates/login.html +28 -0
- regscale/core/server/templates/make_base64.html +22 -0
- regscale/core/server/templates/upload_STIG.html +109 -0
- regscale/core/server/templates/upload_STIG_result.html +26 -0
- regscale/core/server/templates/upload_ssp.html +144 -0
- regscale/core/server/templates/upload_ssp_result.html +128 -0
- regscale/core/static/__init__.py +0 -0
- regscale/core/static/regex.py +14 -0
- regscale/core/utils/__init__.py +117 -0
- regscale/core/utils/click_utils.py +13 -0
- regscale/core/utils/date.py +238 -0
- regscale/core/utils/graphql.py +254 -0
- regscale/core/utils/urls.py +23 -0
- regscale/dev/__init__.py +6 -0
- regscale/dev/analysis.py +454 -0
- regscale/dev/cli.py +235 -0
- regscale/dev/code_gen.py +492 -0
- regscale/dev/dirs.py +69 -0
- regscale/dev/docs.py +384 -0
- regscale/dev/monitoring.py +26 -0
- regscale/dev/profiling.py +216 -0
- regscale/exceptions/__init__.py +4 -0
- regscale/exceptions/license_exception.py +7 -0
- regscale/exceptions/validation_exception.py +9 -0
- regscale/integrations/__init__.py +1 -0
- regscale/integrations/commercial/__init__.py +486 -0
- regscale/integrations/commercial/ad.py +433 -0
- regscale/integrations/commercial/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/common.py +106 -0
- regscale/integrations/commercial/aqua/__init__.py +0 -0
- regscale/integrations/commercial/aqua/aqua.py +91 -0
- regscale/integrations/commercial/aws/__init__.py +6 -0
- regscale/integrations/commercial/aws/cli.py +322 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
- regscale/integrations/commercial/aws/inventory/base.py +64 -0
- regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
- regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
- regscale/integrations/commercial/aws/scanner.py +823 -0
- regscale/integrations/commercial/azure/__init__.py +0 -0
- regscale/integrations/commercial/azure/common.py +32 -0
- regscale/integrations/commercial/azure/intune.py +488 -0
- regscale/integrations/commercial/azure/scanner.py +49 -0
- regscale/integrations/commercial/burp.py +78 -0
- regscale/integrations/commercial/cpe.py +144 -0
- regscale/integrations/commercial/crowdstrike.py +1117 -0
- regscale/integrations/commercial/defender.py +1511 -0
- regscale/integrations/commercial/dependabot.py +210 -0
- regscale/integrations/commercial/durosuite/__init__.py +0 -0
- regscale/integrations/commercial/durosuite/api.py +1546 -0
- regscale/integrations/commercial/durosuite/process_devices.py +101 -0
- regscale/integrations/commercial/durosuite/scanner.py +637 -0
- regscale/integrations/commercial/durosuite/variables.py +21 -0
- regscale/integrations/commercial/ecr.py +90 -0
- regscale/integrations/commercial/gcp/__init__.py +237 -0
- regscale/integrations/commercial/gcp/auth.py +96 -0
- regscale/integrations/commercial/gcp/control_tests.py +238 -0
- regscale/integrations/commercial/gcp/variables.py +18 -0
- regscale/integrations/commercial/gitlab.py +332 -0
- regscale/integrations/commercial/grype.py +165 -0
- regscale/integrations/commercial/ibm.py +90 -0
- regscale/integrations/commercial/import_all/__init__.py +0 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
- regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
- regscale/integrations/commercial/jira.py +1046 -0
- regscale/integrations/commercial/mappings/__init__.py +0 -0
- regscale/integrations/commercial/mappings/csf_controls.json +713 -0
- regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
- regscale/integrations/commercial/nessus/__init__.py +0 -0
- regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
- regscale/integrations/commercial/nessus/scanner.py +416 -0
- regscale/integrations/commercial/nexpose.py +90 -0
- regscale/integrations/commercial/okta.py +798 -0
- regscale/integrations/commercial/opentext/__init__.py +0 -0
- regscale/integrations/commercial/opentext/click.py +99 -0
- regscale/integrations/commercial/opentext/scanner.py +143 -0
- regscale/integrations/commercial/prisma.py +91 -0
- regscale/integrations/commercial/qualys.py +1462 -0
- regscale/integrations/commercial/salesforce.py +980 -0
- regscale/integrations/commercial/sap/__init__.py +0 -0
- regscale/integrations/commercial/sap/click.py +31 -0
- regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
- regscale/integrations/commercial/sap/sysdig/click.py +57 -0
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
- regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
- regscale/integrations/commercial/sap/tenable/click.py +49 -0
- regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
- regscale/integrations/commercial/servicenow.py +1756 -0
- regscale/integrations/commercial/sicura/__init__.py +0 -0
- regscale/integrations/commercial/sicura/api.py +855 -0
- regscale/integrations/commercial/sicura/commands.py +73 -0
- regscale/integrations/commercial/sicura/scanner.py +481 -0
- regscale/integrations/commercial/sicura/variables.py +16 -0
- regscale/integrations/commercial/snyk.py +90 -0
- regscale/integrations/commercial/sonarcloud.py +260 -0
- regscale/integrations/commercial/sqlserver.py +369 -0
- regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
- regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
- regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
- regscale/integrations/commercial/stigv2/__init__.py +0 -0
- regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
- regscale/integrations/commercial/stigv2/click_commands.py +95 -0
- regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
- regscale/integrations/commercial/synqly/__init__.py +0 -0
- regscale/integrations/commercial/synqly/assets.py +46 -0
- regscale/integrations/commercial/synqly/ticketing.py +132 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
- regscale/integrations/commercial/synqly_jira.py +840 -0
- regscale/integrations/commercial/tenablev2/__init__.py +0 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
- regscale/integrations/commercial/tenablev2/click.py +1584 -0
- regscale/integrations/commercial/tenablev2/scanner.py +504 -0
- regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
- regscale/integrations/commercial/tenablev2/utils.py +78 -0
- regscale/integrations/commercial/tenablev2/variables.py +17 -0
- regscale/integrations/commercial/trivy.py +162 -0
- regscale/integrations/commercial/veracode.py +96 -0
- regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
- regscale/integrations/commercial/wizv2/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/click.py +429 -0
- regscale/integrations/commercial/wizv2/constants.py +1001 -0
- regscale/integrations/commercial/wizv2/issue.py +361 -0
- regscale/integrations/commercial/wizv2/models.py +112 -0
- regscale/integrations/commercial/wizv2/parsers.py +339 -0
- regscale/integrations/commercial/wizv2/sbom.py +115 -0
- regscale/integrations/commercial/wizv2/scanner.py +416 -0
- regscale/integrations/commercial/wizv2/utils.py +796 -0
- regscale/integrations/commercial/wizv2/variables.py +39 -0
- regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
- regscale/integrations/commercial/xray.py +91 -0
- regscale/integrations/integration/__init__.py +2 -0
- regscale/integrations/integration/integration.py +26 -0
- regscale/integrations/integration/inventory.py +17 -0
- regscale/integrations/integration/issue.py +100 -0
- regscale/integrations/integration_override.py +149 -0
- regscale/integrations/public/__init__.py +103 -0
- regscale/integrations/public/cisa.py +641 -0
- regscale/integrations/public/criticality_updater.py +70 -0
- regscale/integrations/public/emass.py +411 -0
- regscale/integrations/public/emass_slcm_import.py +697 -0
- regscale/integrations/public/fedramp/__init__.py +0 -0
- regscale/integrations/public/fedramp/appendix_parser.py +548 -0
- regscale/integrations/public/fedramp/click.py +479 -0
- regscale/integrations/public/fedramp/components.py +714 -0
- regscale/integrations/public/fedramp/docx_parser.py +259 -0
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
- regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
- regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
- regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
- regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
- regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
- regscale/integrations/public/fedramp/import_workbook.py +495 -0
- regscale/integrations/public/fedramp/inventory_items.py +244 -0
- regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
- regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
- regscale/integrations/public/fedramp/mappings/user.py +175 -0
- regscale/integrations/public/fedramp/mappings/values.py +141 -0
- regscale/integrations/public/fedramp/markdown_parser.py +150 -0
- regscale/integrations/public/fedramp/metadata.py +689 -0
- regscale/integrations/public/fedramp/models/__init__.py +59 -0
- regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
- regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
- regscale/integrations/public/fedramp/parts_mapper.py +107 -0
- regscale/integrations/public/fedramp/poam/__init__.py +0 -0
- regscale/integrations/public/fedramp/poam/scanner.py +851 -0
- regscale/integrations/public/fedramp/properties.py +201 -0
- regscale/integrations/public/fedramp/reporting.py +84 -0
- regscale/integrations/public/fedramp/resources.py +496 -0
- regscale/integrations/public/fedramp/rosetta.py +110 -0
- regscale/integrations/public/fedramp/ssp_logger.py +87 -0
- regscale/integrations/public/fedramp/system_characteristics.py +922 -0
- regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
- regscale/integrations/public/fedramp/system_implementation.py +190 -0
- regscale/integrations/public/fedramp/xml_utils.py +87 -0
- regscale/integrations/public/nist_catalog.py +275 -0
- regscale/integrations/public/oscal.py +1946 -0
- regscale/integrations/public/otx.py +169 -0
- regscale/integrations/scanner_integration.py +2692 -0
- regscale/integrations/variables.py +25 -0
- regscale/models/__init__.py +7 -0
- regscale/models/app_models/__init__.py +5 -0
- regscale/models/app_models/catalog_compare.py +213 -0
- regscale/models/app_models/click.py +252 -0
- regscale/models/app_models/datetime_encoder.py +21 -0
- regscale/models/app_models/import_validater.py +321 -0
- regscale/models/app_models/mapping.py +260 -0
- regscale/models/app_models/pipeline.py +37 -0
- regscale/models/click_models.py +413 -0
- regscale/models/config.py +154 -0
- regscale/models/email_style.css +67 -0
- regscale/models/hierarchy.py +8 -0
- regscale/models/inspect_models.py +79 -0
- regscale/models/integration_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/inspector.py +262 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
- regscale/models/integration_models/aqua.py +247 -0
- regscale/models/integration_models/azure_alerts.py +255 -0
- regscale/models/integration_models/base64.py +23 -0
- regscale/models/integration_models/burp.py +433 -0
- regscale/models/integration_models/burp_models.py +128 -0
- regscale/models/integration_models/cisa_kev_data.json +19333 -0
- regscale/models/integration_models/defender_data.py +93 -0
- regscale/models/integration_models/defenderimport.py +143 -0
- regscale/models/integration_models/drf.py +443 -0
- regscale/models/integration_models/ecr_models/__init__.py +0 -0
- regscale/models/integration_models/ecr_models/data.py +69 -0
- regscale/models/integration_models/ecr_models/ecr.py +239 -0
- regscale/models/integration_models/flat_file_importer.py +1079 -0
- regscale/models/integration_models/grype_import.py +247 -0
- regscale/models/integration_models/ibm.py +126 -0
- regscale/models/integration_models/implementation_results.py +85 -0
- regscale/models/integration_models/nexpose.py +140 -0
- regscale/models/integration_models/prisma.py +202 -0
- regscale/models/integration_models/qualys.py +720 -0
- regscale/models/integration_models/qualys_scanner.py +160 -0
- regscale/models/integration_models/sbom/__init__.py +0 -0
- regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
- regscale/models/integration_models/send_reminders.py +620 -0
- regscale/models/integration_models/snyk.py +155 -0
- regscale/models/integration_models/synqly_models/__init__.py +0 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -0
- regscale/models/integration_models/synqly_models/connector_types.py +22 -0
- regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
- regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
- regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
- regscale/models/integration_models/synqly_models/param.py +72 -0
- regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
- regscale/models/integration_models/synqly_models/tenants.py +39 -0
- regscale/models/integration_models/tenable_models/__init__.py +0 -0
- regscale/models/integration_models/tenable_models/integration.py +187 -0
- regscale/models/integration_models/tenable_models/models.py +513 -0
- regscale/models/integration_models/trivy_import.py +231 -0
- regscale/models/integration_models/veracode.py +217 -0
- regscale/models/integration_models/xray.py +135 -0
- regscale/models/locking.py +100 -0
- regscale/models/platform.py +110 -0
- regscale/models/regscale_models/__init__.py +67 -0
- regscale/models/regscale_models/assessment.py +570 -0
- regscale/models/regscale_models/assessment_plan.py +52 -0
- regscale/models/regscale_models/asset.py +567 -0
- regscale/models/regscale_models/asset_mapping.py +190 -0
- regscale/models/regscale_models/case.py +42 -0
- regscale/models/regscale_models/catalog.py +261 -0
- regscale/models/regscale_models/cci.py +46 -0
- regscale/models/regscale_models/change.py +167 -0
- regscale/models/regscale_models/checklist.py +372 -0
- regscale/models/regscale_models/comment.py +49 -0
- regscale/models/regscale_models/compliance_settings.py +112 -0
- regscale/models/regscale_models/component.py +412 -0
- regscale/models/regscale_models/component_mapping.py +65 -0
- regscale/models/regscale_models/control.py +38 -0
- regscale/models/regscale_models/control_implementation.py +1128 -0
- regscale/models/regscale_models/control_objective.py +261 -0
- regscale/models/regscale_models/control_parameter.py +100 -0
- regscale/models/regscale_models/control_test.py +34 -0
- regscale/models/regscale_models/control_test_plan.py +75 -0
- regscale/models/regscale_models/control_test_result.py +52 -0
- regscale/models/regscale_models/custom_field.py +245 -0
- regscale/models/regscale_models/data.py +109 -0
- regscale/models/regscale_models/data_center.py +40 -0
- regscale/models/regscale_models/deviation.py +203 -0
- regscale/models/regscale_models/email.py +97 -0
- regscale/models/regscale_models/evidence.py +47 -0
- regscale/models/regscale_models/evidence_mapping.py +40 -0
- regscale/models/regscale_models/facility.py +59 -0
- regscale/models/regscale_models/file.py +382 -0
- regscale/models/regscale_models/filetag.py +37 -0
- regscale/models/regscale_models/form_field_value.py +94 -0
- regscale/models/regscale_models/group.py +169 -0
- regscale/models/regscale_models/implementation_objective.py +335 -0
- regscale/models/regscale_models/implementation_option.py +275 -0
- regscale/models/regscale_models/implementation_role.py +33 -0
- regscale/models/regscale_models/incident.py +177 -0
- regscale/models/regscale_models/interconnection.py +43 -0
- regscale/models/regscale_models/issue.py +1176 -0
- regscale/models/regscale_models/leveraged_authorization.py +125 -0
- regscale/models/regscale_models/line_of_inquiry.py +52 -0
- regscale/models/regscale_models/link.py +205 -0
- regscale/models/regscale_models/meta_data.py +64 -0
- regscale/models/regscale_models/mixins/__init__.py +0 -0
- regscale/models/regscale_models/mixins/parent_cache.py +124 -0
- regscale/models/regscale_models/module.py +224 -0
- regscale/models/regscale_models/modules.py +191 -0
- regscale/models/regscale_models/objective.py +14 -0
- regscale/models/regscale_models/parameter.py +87 -0
- regscale/models/regscale_models/ports_protocol.py +81 -0
- regscale/models/regscale_models/privacy.py +89 -0
- regscale/models/regscale_models/profile.py +50 -0
- regscale/models/regscale_models/profile_link.py +68 -0
- regscale/models/regscale_models/profile_mapping.py +124 -0
- regscale/models/regscale_models/project.py +63 -0
- regscale/models/regscale_models/property.py +278 -0
- regscale/models/regscale_models/question.py +85 -0
- regscale/models/regscale_models/questionnaire.py +87 -0
- regscale/models/regscale_models/questionnaire_instance.py +177 -0
- regscale/models/regscale_models/rbac.py +132 -0
- regscale/models/regscale_models/reference.py +86 -0
- regscale/models/regscale_models/regscale_model.py +1643 -0
- regscale/models/regscale_models/requirement.py +29 -0
- regscale/models/regscale_models/risk.py +274 -0
- regscale/models/regscale_models/sbom.py +54 -0
- regscale/models/regscale_models/scan_history.py +436 -0
- regscale/models/regscale_models/search.py +53 -0
- regscale/models/regscale_models/security_control.py +132 -0
- regscale/models/regscale_models/security_plan.py +204 -0
- regscale/models/regscale_models/software_inventory.py +159 -0
- regscale/models/regscale_models/stake_holder.py +64 -0
- regscale/models/regscale_models/stig.py +647 -0
- regscale/models/regscale_models/supply_chain.py +152 -0
- regscale/models/regscale_models/system_role.py +188 -0
- regscale/models/regscale_models/system_role_external_assignment.py +40 -0
- regscale/models/regscale_models/tag.py +37 -0
- regscale/models/regscale_models/tag_mapping.py +19 -0
- regscale/models/regscale_models/task.py +133 -0
- regscale/models/regscale_models/threat.py +196 -0
- regscale/models/regscale_models/user.py +175 -0
- regscale/models/regscale_models/user_group.py +55 -0
- regscale/models/regscale_models/vulnerability.py +242 -0
- regscale/models/regscale_models/vulnerability_mapping.py +162 -0
- regscale/models/regscale_models/workflow.py +55 -0
- regscale/models/regscale_models/workflow_action.py +34 -0
- regscale/models/regscale_models/workflow_instance.py +269 -0
- regscale/models/regscale_models/workflow_instance_step.py +114 -0
- regscale/models/regscale_models/workflow_template.py +58 -0
- regscale/models/regscale_models/workflow_template_step.py +45 -0
- regscale/regscale.py +815 -0
- regscale/utils/__init__.py +7 -0
- regscale/utils/b64conversion.py +14 -0
- regscale/utils/click_utils.py +118 -0
- regscale/utils/decorators.py +48 -0
- regscale/utils/dict_utils.py +59 -0
- regscale/utils/files.py +79 -0
- regscale/utils/fxns.py +30 -0
- regscale/utils/graphql_client.py +113 -0
- regscale/utils/lists.py +16 -0
- regscale/utils/numbers.py +12 -0
- regscale/utils/shell.py +148 -0
- regscale/utils/string.py +121 -0
- regscale/utils/synqly_utils.py +165 -0
- regscale/utils/threading/__init__.py +8 -0
- regscale/utils/threading/threadhandler.py +131 -0
- regscale/utils/threading/threadsafe_counter.py +47 -0
- regscale/utils/threading/threadsafe_dict.py +242 -0
- regscale/utils/threading/threadsafe_list.py +83 -0
- regscale/utils/version.py +104 -0
- regscale/validation/__init__.py +0 -0
- regscale/validation/address.py +37 -0
- regscale/validation/record.py +48 -0
- regscale/visualization/__init__.py +5 -0
- regscale/visualization/click.py +34 -0
- regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
- regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
- regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
- regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
- regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
- regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
- tests/fixtures/__init__.py +2 -0
- tests/fixtures/api.py +87 -0
- tests/fixtures/models.py +91 -0
- tests/fixtures/test_fixture.py +144 -0
- tests/mocks/__init__.py +0 -0
- tests/mocks/objects.py +3 -0
- tests/mocks/response.py +32 -0
- tests/mocks/xml.py +13 -0
- tests/regscale/__init__.py +0 -0
- tests/regscale/core/__init__.py +0 -0
- tests/regscale/core/test_api.py +232 -0
- tests/regscale/core/test_app.py +406 -0
- tests/regscale/core/test_login.py +37 -0
- tests/regscale/core/test_logz.py +66 -0
- tests/regscale/core/test_sbom_generator.py +87 -0
- tests/regscale/core/test_validation_utils.py +163 -0
- tests/regscale/core/test_version.py +78 -0
- tests/regscale/models/__init__.py +0 -0
- tests/regscale/models/test_asset.py +71 -0
- tests/regscale/models/test_config.py +26 -0
- tests/regscale/models/test_control_implementation.py +27 -0
- tests/regscale/models/test_import.py +97 -0
- tests/regscale/models/test_issue.py +36 -0
- tests/regscale/models/test_mapping.py +52 -0
- tests/regscale/models/test_platform.py +31 -0
- tests/regscale/models/test_regscale_model.py +346 -0
- tests/regscale/models/test_report.py +32 -0
- tests/regscale/models/test_tenable_integrations.py +118 -0
- tests/regscale/models/test_user_model.py +121 -0
- tests/regscale/test_about.py +19 -0
- tests/regscale/test_authorization.py +65 -0
regscale/core/app/api.py
ADDED
|
@@ -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)
|