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
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Functions used throughout the application"""
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
# standard python imports
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from regscale.core.app import create_logger
|
|
10
|
+
from regscale.core.utils.date import datetime_str
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import pandas as pd # Type Checking
|
|
14
|
+
from regscale.core.app.application import Application
|
|
15
|
+
from regscale.core.app.api import Api
|
|
16
|
+
|
|
17
|
+
import csv
|
|
18
|
+
import glob
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import ntpath
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import random
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from collections import abc
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from io import BytesIO
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from shutil import copyfileobj, copytree, rmtree
|
|
33
|
+
from site import getusersitepackages
|
|
34
|
+
from tempfile import gettempdir
|
|
35
|
+
from typing import Any, BinaryIO, Dict, NoReturn, Optional, Union
|
|
36
|
+
from urllib.parse import urlparse
|
|
37
|
+
|
|
38
|
+
import psutil
|
|
39
|
+
import pytz
|
|
40
|
+
import requests
|
|
41
|
+
import xmltodict
|
|
42
|
+
from dateutil import relativedelta
|
|
43
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
|
|
44
|
+
|
|
45
|
+
from regscale.core.app.internal.login import is_licensed
|
|
46
|
+
from regscale.exceptions.license_exception import LicenseException
|
|
47
|
+
|
|
48
|
+
logger = create_logger()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_cross_platform_user_data_dir() -> Path:
|
|
52
|
+
"""
|
|
53
|
+
Return the user data directory for the current platform
|
|
54
|
+
|
|
55
|
+
:return: user data directory
|
|
56
|
+
:rtype: Path
|
|
57
|
+
"""
|
|
58
|
+
if sys.platform == "win32":
|
|
59
|
+
return Path(os.getenv("APPDATA")) / "regscale"
|
|
60
|
+
else:
|
|
61
|
+
return Path.home() / ".config" / "regscale"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_license(config: Optional[dict] = None) -> "Application":
|
|
65
|
+
"""
|
|
66
|
+
Check RegScale license
|
|
67
|
+
|
|
68
|
+
:param Optional[dict] config: Config dictionary, defaults to None
|
|
69
|
+
:raises: LicenseException if Application license isn't at the requested level of the feature
|
|
70
|
+
:return: Application object
|
|
71
|
+
:rtype: Application
|
|
72
|
+
"""
|
|
73
|
+
from regscale.core.app.application import Application
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
app = Application(config)
|
|
77
|
+
if not is_licensed(app):
|
|
78
|
+
raise LicenseException("This feature is limited to RegScale Enterprise, please check RegScale license.")
|
|
79
|
+
except LicenseException as e:
|
|
80
|
+
error_and_exit(str(e.with_traceback(None)))
|
|
81
|
+
return app
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_site_package_location() -> Path:
|
|
85
|
+
"""
|
|
86
|
+
Return site package location as string
|
|
87
|
+
|
|
88
|
+
:return: site package location
|
|
89
|
+
:rtype: Path
|
|
90
|
+
"""
|
|
91
|
+
return Path(getusersitepackages())
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def creation_date(path_to_file: Union[str, Path]) -> float:
|
|
95
|
+
"""
|
|
96
|
+
Try to get the date that a file was created, falling back to when it was
|
|
97
|
+
last modified if that isn't possible.
|
|
98
|
+
See http://stackoverflow.com/a/39501288/1709587 for explanation.
|
|
99
|
+
|
|
100
|
+
:param Union[str, Path] path_to_file: Path to the file
|
|
101
|
+
:return: Date of creation
|
|
102
|
+
:rtype: float
|
|
103
|
+
"""
|
|
104
|
+
if platform.system() == "Windows":
|
|
105
|
+
return os.path.getctime(path_to_file)
|
|
106
|
+
else:
|
|
107
|
+
stat = os.stat(path_to_file)
|
|
108
|
+
try:
|
|
109
|
+
return stat.st_birthtime
|
|
110
|
+
except AttributeError:
|
|
111
|
+
# We're probably on Linux. No easy way to get creation dates here,
|
|
112
|
+
# so we'll settle for when its content was last modified.
|
|
113
|
+
return stat.st_mtime
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def is_valid_fqdn(fqdn: str) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Function to check if the provided fqdn is valid
|
|
119
|
+
|
|
120
|
+
:param str fqdn: FQDN to check
|
|
121
|
+
:return: True if valid, False if not
|
|
122
|
+
:rtype: bool
|
|
123
|
+
"""
|
|
124
|
+
if isinstance(fqdn, str):
|
|
125
|
+
fqdn_regex = r"^(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)*"
|
|
126
|
+
fqdn_regex += r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost)$"
|
|
127
|
+
if re.match(fqdn_regex, fqdn, re.IGNORECASE):
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def convert_datetime_to_regscale_string(reg_dt: datetime, dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S") -> str:
|
|
133
|
+
"""
|
|
134
|
+
Convert a datetime object to a RegScale API friendly string
|
|
135
|
+
|
|
136
|
+
:param datetime reg_dt: Datetime object
|
|
137
|
+
:param Optional[str] dt_format: Defaults to "%Y-%m-%d %H:%M:%S".
|
|
138
|
+
:return: RegScale datetime string
|
|
139
|
+
:rtype: str
|
|
140
|
+
"""
|
|
141
|
+
return datetime_str(reg_dt, dt_format)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def reformat_str_date(date_str: str, dt_format: str = "%m/%d/%Y") -> str:
|
|
145
|
+
"""
|
|
146
|
+
Function to convert a string into a datetime object and reformat it to dt_format, default \
|
|
147
|
+
format is MM/DD/YYYY
|
|
148
|
+
|
|
149
|
+
:param str date_str: date as a string
|
|
150
|
+
:param str dt_format: datetime string format, defaults to "%m/%d/%Y"
|
|
151
|
+
:return: string with the provided date format
|
|
152
|
+
:rtype: str
|
|
153
|
+
"""
|
|
154
|
+
# replace the T with a space and create list of result
|
|
155
|
+
date_str = date_str.replace("T", " ").split(" ")
|
|
156
|
+
|
|
157
|
+
return datetime.strptime(date_str[0], "%Y-%m-%d").strftime(dt_format)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def pretty_short_str(long_str: str, start_length: int, end_length: int) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Function to convert long string to shortened string
|
|
163
|
+
|
|
164
|
+
:param str long_str: long string to shorten
|
|
165
|
+
:param int start_length: number of characters to use from string start
|
|
166
|
+
:param int end_length: number of characters to use from string end
|
|
167
|
+
:return: attractive short string of form 'start..end'
|
|
168
|
+
:rtype: str
|
|
169
|
+
"""
|
|
170
|
+
return long_str[:start_length] + ".." + long_str[-end_length:]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def camel_case(text: str) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Function to convert known module to camel case... (GraphQL)
|
|
176
|
+
|
|
177
|
+
:param str text: string to convert to camelCase
|
|
178
|
+
:return: Provided string in camelCase format
|
|
179
|
+
:rtype: str
|
|
180
|
+
"""
|
|
181
|
+
# Split the input string into words using a regular expression
|
|
182
|
+
words = [word for word in re.split(r"[\s_\-]+|(?<=[a-z])(?=[A-Z])", text) if word]
|
|
183
|
+
# Make the first word lowercase, and capitalize the first letter of each subsequent word
|
|
184
|
+
words[0] = words[0].lower()
|
|
185
|
+
for i in range(1, len(words)):
|
|
186
|
+
words[i] = words[i].capitalize()
|
|
187
|
+
# Concatenate the words without spaces
|
|
188
|
+
return "".join(words)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def snake_case(text: str) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Function to convert a string to snake_case
|
|
194
|
+
|
|
195
|
+
:param str text: string to convert
|
|
196
|
+
:return: string in snake_case
|
|
197
|
+
:rtype: str
|
|
198
|
+
"""
|
|
199
|
+
# Split the input string into words using a regular expression
|
|
200
|
+
words = [word for word in re.split(r"[\s_\-]+|(?<=[a-z])(?=[A-Z])", text) if word]
|
|
201
|
+
# Make the first word lowercase, and capitalize the first letter of each subsequent word
|
|
202
|
+
words[0] = words[0].lower()
|
|
203
|
+
for i in range(1, len(words)):
|
|
204
|
+
words[i] = words[i].capitalize()
|
|
205
|
+
# Concatenate the words without spaces
|
|
206
|
+
return "_".join(words)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def uncamel_case(camel_str: str) -> str:
|
|
210
|
+
"""
|
|
211
|
+
Function to convert camelCase strings to Title Case
|
|
212
|
+
|
|
213
|
+
:param str camel_str: string to convert
|
|
214
|
+
:return: Title Case string from provided camelCase
|
|
215
|
+
:rtype: str
|
|
216
|
+
"""
|
|
217
|
+
# check to see if a string with data was passed
|
|
218
|
+
if camel_str != "":
|
|
219
|
+
# split at any uppercase letters
|
|
220
|
+
result = re.sub("([A-Z])", r" \1", camel_str)
|
|
221
|
+
|
|
222
|
+
# use title to Title Case the string and strip to remove leading
|
|
223
|
+
# and trailing white spaces
|
|
224
|
+
result = result.title().strip()
|
|
225
|
+
return result
|
|
226
|
+
return ""
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_css(file_path: str) -> str:
|
|
230
|
+
"""
|
|
231
|
+
Function to load the CSS properties from the given file_path
|
|
232
|
+
|
|
233
|
+
:param str file_path: file path to the desired CSS file
|
|
234
|
+
:return: CSS as a string
|
|
235
|
+
:rtype: str
|
|
236
|
+
"""
|
|
237
|
+
# create variable to store the string and return
|
|
238
|
+
css = ""
|
|
239
|
+
import importlib.resources as pkg_resources
|
|
240
|
+
|
|
241
|
+
# check if the filepath exists before trying to open it
|
|
242
|
+
with pkg_resources.open_text("regscale.models", file_path) as file:
|
|
243
|
+
# read the file and store it as a string
|
|
244
|
+
css = file.read()
|
|
245
|
+
# return the css variable
|
|
246
|
+
return css
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def epoch_to_datetime(
|
|
250
|
+
epoch: str,
|
|
251
|
+
epoch_type: str = "seconds",
|
|
252
|
+
dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S",
|
|
253
|
+
) -> str:
|
|
254
|
+
"""
|
|
255
|
+
Return datetime from unix epoch
|
|
256
|
+
|
|
257
|
+
:param str epoch: unix epoch
|
|
258
|
+
:param str epoch_type: type of epoch, defaults to 'seconds'
|
|
259
|
+
:param Optional[str] dt_format: datetime string format, defaults to "%Y-%m-%d %H:%M:%S"
|
|
260
|
+
:return: datetime string
|
|
261
|
+
:rtype: str
|
|
262
|
+
"""
|
|
263
|
+
if epoch_type == "milliseconds":
|
|
264
|
+
return datetime.fromtimestamp(int(epoch) / 1000).strftime(dt_format)
|
|
265
|
+
return datetime.fromtimestamp(int(epoch)).strftime(dt_format)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def get_current_datetime(dt_format: Optional[str] = "%Y-%m-%d %H:%M:%S") -> str:
|
|
269
|
+
"""
|
|
270
|
+
Return current datetime
|
|
271
|
+
|
|
272
|
+
:param Optional[str] dt_format: desired format for datetime string, defaults to "%Y-%m-%d %H:%M:%S"
|
|
273
|
+
:return: Current datetime as a string
|
|
274
|
+
:rtype: str
|
|
275
|
+
"""
|
|
276
|
+
return datetime.now().strftime(dt_format)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def regscale_string_to_datetime(reg_dt: str) -> datetime:
|
|
280
|
+
"""
|
|
281
|
+
Convert a RegScale API friendly string to a datetime object
|
|
282
|
+
|
|
283
|
+
:param str reg_dt: RegScale datetime string
|
|
284
|
+
:return: Datetime object
|
|
285
|
+
:rtype: datetime
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
dt = datetime.fromisoformat(reg_dt)
|
|
289
|
+
# Make sure timezone is UTC aware
|
|
290
|
+
return pytz.utc.localize(dt)
|
|
291
|
+
except ValueError:
|
|
292
|
+
try:
|
|
293
|
+
# remove the milliseconds from the string if they exist (prevents ValueError)
|
|
294
|
+
date_parts = reg_dt.split(".")
|
|
295
|
+
if len(date_parts) > 1:
|
|
296
|
+
reg_dt = date_parts[0]
|
|
297
|
+
dt = datetime.fromisoformat(reg_dt)
|
|
298
|
+
return pytz.utc.localize(dt)
|
|
299
|
+
except ValueError:
|
|
300
|
+
error_and_exit(f"Invalid datetime string provided: {reg_dt}")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def regscale_string_to_epoch(reg_dt: str) -> int:
|
|
304
|
+
"""
|
|
305
|
+
Convert a RegScale API friendly string to an epoch seconds integer
|
|
306
|
+
|
|
307
|
+
:param str reg_dt: RegScale datetime string
|
|
308
|
+
:return: Datetime object
|
|
309
|
+
:rtype: int
|
|
310
|
+
"""
|
|
311
|
+
dt = regscale_string_to_datetime(reg_dt)
|
|
312
|
+
# convert to epoch
|
|
313
|
+
return int(dt.timestamp())
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def cci_control_mapping(file_path: Path) -> list:
|
|
317
|
+
"""
|
|
318
|
+
Simple function to read csv artifact to help with STIG mapping
|
|
319
|
+
|
|
320
|
+
:param Path file_path: file path to the csv artifact
|
|
321
|
+
:return: List of the csv contents
|
|
322
|
+
:rtype: list
|
|
323
|
+
"""
|
|
324
|
+
with open(file_path, "r", newline="", encoding="utf-8") as file:
|
|
325
|
+
reader = csv.reader(file)
|
|
326
|
+
return list(reader)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def copy_and_overwrite(from_path: Path, to_path: Path) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Copy and overwrite files recursively in a given path
|
|
332
|
+
|
|
333
|
+
:param Path from_path: Path to copy from
|
|
334
|
+
:param Path to_path: Path to copy to
|
|
335
|
+
:rtype: None
|
|
336
|
+
"""
|
|
337
|
+
if os.path.exists(to_path):
|
|
338
|
+
rmtree(to_path)
|
|
339
|
+
copytree(from_path, to_path)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def create_progress_object(indeterminate: bool = False) -> Progress:
|
|
343
|
+
"""
|
|
344
|
+
Function to create and return a progress object
|
|
345
|
+
|
|
346
|
+
:param bool indeterminate: If the progress bar should be indeterminate, defaults to False
|
|
347
|
+
:return: Progress object for live progress in console
|
|
348
|
+
:rtype: Progress
|
|
349
|
+
"""
|
|
350
|
+
if indeterminate:
|
|
351
|
+
return Progress(
|
|
352
|
+
"{task.description}",
|
|
353
|
+
SpinnerColumn(),
|
|
354
|
+
)
|
|
355
|
+
return Progress(
|
|
356
|
+
"{task.description}",
|
|
357
|
+
SpinnerColumn(),
|
|
358
|
+
BarColumn(),
|
|
359
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
360
|
+
TimeElapsedColumn(),
|
|
361
|
+
TextColumn("Remaining:"),
|
|
362
|
+
TimeRemainingColumn(),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_file_type(file_name: Union[Path, str]) -> str:
|
|
367
|
+
"""
|
|
368
|
+
Function to get the file type of the provided file_path and returns it as a string
|
|
369
|
+
|
|
370
|
+
:param Union[Path, str] file_name: Path to the file
|
|
371
|
+
:return: Returns string of file type
|
|
372
|
+
:rtype: str
|
|
373
|
+
"""
|
|
374
|
+
if isinstance(file_name, str):
|
|
375
|
+
file_name = Path(file_name)
|
|
376
|
+
file_type = Path(file_name).suffix
|
|
377
|
+
return file_type.lower()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def xml_file_to_dict(file_path: Path) -> dict:
|
|
381
|
+
"""
|
|
382
|
+
Function to convert an XML file to a dictionary
|
|
383
|
+
|
|
384
|
+
:param Path file_path: Path to the XML file
|
|
385
|
+
:return: Dictionary of the XML file
|
|
386
|
+
:rtype: dict
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
# Use try/except for performance reasons, faster than check before.
|
|
390
|
+
try:
|
|
391
|
+
return xmltodict.parse(file_path.read_text(encoding="utf-8"))
|
|
392
|
+
except FileNotFoundError:
|
|
393
|
+
error_and_exit(f"The provided file path doesn't exist! Provided: {file_path}")
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def get_file_name(file_path: str) -> str:
|
|
397
|
+
"""
|
|
398
|
+
Function to parse the provided file path and returns the file's name as a string
|
|
399
|
+
|
|
400
|
+
:param str file_path: path to the file
|
|
401
|
+
:return: File name
|
|
402
|
+
:rtype: str
|
|
403
|
+
"""
|
|
404
|
+
# split the provided file_path with ntpath
|
|
405
|
+
directory, file_name = ntpath.split(file_path)
|
|
406
|
+
# return the file_path or directory
|
|
407
|
+
return file_name or ntpath.basename(directory)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def get_recent_files(file_path: Path, file_count: int, file_type: str = None) -> list:
|
|
411
|
+
"""
|
|
412
|
+
Function to go to the provided file_path and get the x number of recent items
|
|
413
|
+
optional argument of file_type to filter the directory
|
|
414
|
+
|
|
415
|
+
:param Path file_path: Directory to get recent files in
|
|
416
|
+
:param int file_count: # of files to return
|
|
417
|
+
:param str file_type: file type to sort directory for, defaults to none
|
|
418
|
+
:return: list of recent files in the provided directory
|
|
419
|
+
:rtype: list
|
|
420
|
+
"""
|
|
421
|
+
# verify the provided file_path exists
|
|
422
|
+
if os.path.exists(file_path):
|
|
423
|
+
# get the list of files from the provided path, get the desired
|
|
424
|
+
# file_type if provided
|
|
425
|
+
file_list = glob.glob(f"{file_path}/*{file_type}") if file_type else glob.glob(f"{file_path}/*")
|
|
426
|
+
|
|
427
|
+
# sort the file_list by modified date in descending order
|
|
428
|
+
file_list.sort(key=os.path.getmtime, reverse=True)
|
|
429
|
+
|
|
430
|
+
# check if file_list has more items than the provided number, remove the rest
|
|
431
|
+
if len(file_list) > file_count:
|
|
432
|
+
file_list = file_list[:file_count]
|
|
433
|
+
else:
|
|
434
|
+
error_and_exit(f"The provided file path doesn't exist! Provided: {file_path}")
|
|
435
|
+
# return the list of files
|
|
436
|
+
return file_list
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def check_config_for_issues(config: dict, issue: str, key: str) -> Optional[Any]:
|
|
440
|
+
"""
|
|
441
|
+
Function to check config keys
|
|
442
|
+
|
|
443
|
+
:param dict config: Application config
|
|
444
|
+
:param str issue: Issue to check
|
|
445
|
+
:param str key: Key to check
|
|
446
|
+
:return: Value from config or None
|
|
447
|
+
:rtype: Optional[Any]
|
|
448
|
+
"""
|
|
449
|
+
return (
|
|
450
|
+
config["issues"][issue][key]
|
|
451
|
+
if "issues" in config.keys()
|
|
452
|
+
and issue in config["issues"].keys()
|
|
453
|
+
and config["issues"][issue].get(key) is not None
|
|
454
|
+
else None
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def find_uuid_in_str(str_to_search: str) -> str:
|
|
459
|
+
"""
|
|
460
|
+
Find a UUID in a long string
|
|
461
|
+
|
|
462
|
+
:param str str_to_search: Long string
|
|
463
|
+
:return: Matching string
|
|
464
|
+
:rtype: str
|
|
465
|
+
"""
|
|
466
|
+
if dat := re.findall(
|
|
467
|
+
r"[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
|
|
468
|
+
str_to_search,
|
|
469
|
+
):
|
|
470
|
+
return dat[0]
|
|
471
|
+
return str_to_search
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def recursive_items(nested: Union[abc.Mapping, dict]):
|
|
475
|
+
"""
|
|
476
|
+
Function to recursively move through a dictionary and pull out key value pairs
|
|
477
|
+
|
|
478
|
+
:param Union[abc.Mapping, dict] nested: Nested dict to recurse through
|
|
479
|
+
:yield: generated iterable key value pairs
|
|
480
|
+
"""
|
|
481
|
+
for key, value in nested.items():
|
|
482
|
+
if isinstance(value, abc.Mapping):
|
|
483
|
+
yield from recursive_items(value)
|
|
484
|
+
if isinstance(value, list):
|
|
485
|
+
for dictionary in value:
|
|
486
|
+
if isinstance(dictionary, dict):
|
|
487
|
+
yield from recursive_items(dictionary)
|
|
488
|
+
else:
|
|
489
|
+
yield key, value
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def check_file_path(file_path: Union[str, Path], output: bool = True) -> None:
|
|
493
|
+
"""
|
|
494
|
+
Function to check the provided file path, if it doesn't exist it will be created
|
|
495
|
+
|
|
496
|
+
:param Union[str, Path] file_path: Path to the directory
|
|
497
|
+
:param bool output: If the function should output to the console, defaults to True
|
|
498
|
+
:rtype: None
|
|
499
|
+
"""
|
|
500
|
+
# see if the provided directory exists, if not create it
|
|
501
|
+
if not os.path.exists(file_path):
|
|
502
|
+
os.makedirs(file_path)
|
|
503
|
+
# notify user directory has been created
|
|
504
|
+
if output:
|
|
505
|
+
logger.info("%s didn't exist, but has been created.", file_path)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def capitalize_words(word: str) -> str:
|
|
509
|
+
"""
|
|
510
|
+
Function to convert string to title case
|
|
511
|
+
|
|
512
|
+
:param str word: Desired string to process
|
|
513
|
+
:return: String with words titlecased
|
|
514
|
+
:rtype: str
|
|
515
|
+
"""
|
|
516
|
+
return re.sub(r"\w+", lambda m: m.group(0).capitalize(), word)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def error_and_exit(error_desc: str, show_exec: bool = True) -> NoReturn:
|
|
520
|
+
"""
|
|
521
|
+
Function to display and log the provided error_desc and exits the application
|
|
522
|
+
|
|
523
|
+
:param str error_desc: Description of the error encountered
|
|
524
|
+
:param bool show_exec: If the function should show the exception, defaults to True
|
|
525
|
+
:rtype: NoReturn
|
|
526
|
+
"""
|
|
527
|
+
exc_info = sys.exc_info()[0] is not None if sys.exc_info() and show_exec else None
|
|
528
|
+
if exc_info:
|
|
529
|
+
logger.error(error_desc, exc_info=True)
|
|
530
|
+
else:
|
|
531
|
+
logger.error(error_desc)
|
|
532
|
+
from regscale.core.app.application import Application
|
|
533
|
+
|
|
534
|
+
app = Application()
|
|
535
|
+
if app.running_in_airflow:
|
|
536
|
+
raise RuntimeError(error_desc)
|
|
537
|
+
sys.exit(1)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def check_url(url: str) -> bool:
|
|
541
|
+
"""
|
|
542
|
+
Function to check if the provided url is valid
|
|
543
|
+
|
|
544
|
+
:param str url: URL to check
|
|
545
|
+
:return: True if URL is valid, False if not
|
|
546
|
+
:rtype: bool
|
|
547
|
+
"""
|
|
548
|
+
try:
|
|
549
|
+
result = urlparse(url)
|
|
550
|
+
return all([result.scheme, result.netloc])
|
|
551
|
+
except ValueError:
|
|
552
|
+
return False
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def download_file(url: str, download_path: str = gettempdir(), verify: bool = True) -> Path:
|
|
556
|
+
"""
|
|
557
|
+
Download file from the provided url and save it to the provided download_path
|
|
558
|
+
|
|
559
|
+
:param str url: URL location of the file to download
|
|
560
|
+
:param str download_path: Path to download the file to, defaults to gettempdir()
|
|
561
|
+
:param bool verify: SSL verification for requests, defaults to True
|
|
562
|
+
:return: Path to the downloaded file
|
|
563
|
+
:rtype: Path
|
|
564
|
+
"""
|
|
565
|
+
path = Path(download_path)
|
|
566
|
+
local_filename = ntpath.basename(url)
|
|
567
|
+
# NOTE the stream=True parameter below
|
|
568
|
+
with requests.get(url, stream=True, timeout=10, verify=verify) as response:
|
|
569
|
+
response.raise_for_status()
|
|
570
|
+
with open(path / local_filename, "wb") as file:
|
|
571
|
+
copyfileobj(response.raw, file)
|
|
572
|
+
return path / local_filename
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def check_supported_file_type(file: Path) -> None:
|
|
576
|
+
"""
|
|
577
|
+
Check if the file type is supported.
|
|
578
|
+
|
|
579
|
+
:param Path file: Path to the file.
|
|
580
|
+
:raises: RuntimeError if the file type is not supported.
|
|
581
|
+
:rtype: None
|
|
582
|
+
"""
|
|
583
|
+
if file.suffix.lower() not in [".csv", ".json", ".xlsx"]:
|
|
584
|
+
raise RuntimeError(f"Unsupported file type: {file.suffix}")
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def save_to_csv(file: Path, data: Any, output_log: bool, transpose: bool = True) -> None:
|
|
588
|
+
"""
|
|
589
|
+
Save data to a CSV file.
|
|
590
|
+
|
|
591
|
+
:param Path file: Path to the file.
|
|
592
|
+
:param Any data: The data to save.
|
|
593
|
+
:param bool output_log: Whether to output logs.
|
|
594
|
+
:param bool transpose: Whether to transpose the data, defaults to True
|
|
595
|
+
:rtype: None
|
|
596
|
+
"""
|
|
597
|
+
import pandas as pd # Optimize import performance
|
|
598
|
+
|
|
599
|
+
if transpose:
|
|
600
|
+
data = pd.DataFrame(data).transpose()
|
|
601
|
+
else:
|
|
602
|
+
data = pd.DataFrame(data)
|
|
603
|
+
data.to_csv(file)
|
|
604
|
+
if output_log:
|
|
605
|
+
logger.info("Data successfully saved to: %s", file.name)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def save_to_excel(file: Path, data: Any, output_log: bool, transpose: bool = True) -> None:
|
|
609
|
+
"""
|
|
610
|
+
Save data to an Excel file.
|
|
611
|
+
|
|
612
|
+
:param Path file: Path to the file.
|
|
613
|
+
:param Any data: The data to save.
|
|
614
|
+
:param bool output_log: Whether to output logs.
|
|
615
|
+
:param bool transpose: Whether to transpose the data, defaults to True
|
|
616
|
+
:rtype: None
|
|
617
|
+
"""
|
|
618
|
+
import pandas as pd # Optimize import performance
|
|
619
|
+
|
|
620
|
+
d_frame = pd.DataFrame(data)
|
|
621
|
+
if transpose:
|
|
622
|
+
d_frame = d_frame.transpose()
|
|
623
|
+
|
|
624
|
+
d_frame.to_excel(file)
|
|
625
|
+
if output_log:
|
|
626
|
+
logger.info("Data successfully saved to: %s", file.name)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def save_to_json(file: Path, data: Any, output_log: bool) -> None:
|
|
630
|
+
"""
|
|
631
|
+
Save data to a JSON file. Attempts to use json.dump and falls back to write if needed.
|
|
632
|
+
|
|
633
|
+
:param Path file: Path to the file.
|
|
634
|
+
:param Any data: The data to save.
|
|
635
|
+
:param bool output_log: Whether to output logs.
|
|
636
|
+
:rtype: None
|
|
637
|
+
"""
|
|
638
|
+
try:
|
|
639
|
+
with open(file, "w", encoding="utf-8") as outfile:
|
|
640
|
+
json.dump(data, outfile, indent=4)
|
|
641
|
+
except TypeError:
|
|
642
|
+
with open(file, "w", encoding="utf-8") as outfile:
|
|
643
|
+
outfile.write(str(data))
|
|
644
|
+
if output_log:
|
|
645
|
+
logger.info("Data successfully saved to %s", file.name)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def save_data_to(file: Path, data: Any, output_log: bool = True, transpose_data: bool = True) -> None:
|
|
649
|
+
"""
|
|
650
|
+
Save the provided data to the specified file.
|
|
651
|
+
|
|
652
|
+
:param Path file: Path to the file.
|
|
653
|
+
:param Any data: The data to save.
|
|
654
|
+
:param bool output_log: Output logs during execution, defaults to True.
|
|
655
|
+
:param bool transpose_data: Transpose the data for csv or xlsx files, defaults to True.
|
|
656
|
+
:rtype: None
|
|
657
|
+
"""
|
|
658
|
+
check_supported_file_type(file)
|
|
659
|
+
check_file_path(file.parent)
|
|
660
|
+
|
|
661
|
+
if output_log:
|
|
662
|
+
logger.info("Prepping data to be saved to %s", file.name)
|
|
663
|
+
|
|
664
|
+
try:
|
|
665
|
+
if file.suffix.lower() == ".csv":
|
|
666
|
+
save_to_csv(file, data, output_log, transpose_data)
|
|
667
|
+
elif file.suffix.lower() == ".xlsx":
|
|
668
|
+
save_to_excel(file, data, output_log, transpose_data)
|
|
669
|
+
elif file.suffix.lower() == ".json":
|
|
670
|
+
save_to_json(file, data, output_log)
|
|
671
|
+
except PermissionError:
|
|
672
|
+
error_and_exit(f"Unable to save {file.name}. Please verify it is closed and try again.")
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def remove_nested_dict(data: dict, skip_keys: list = None) -> dict:
|
|
676
|
+
"""
|
|
677
|
+
Function to remove nested dictionaries in the provided dictionary,
|
|
678
|
+
also allows the option to remove a key from the provided dictionary
|
|
679
|
+
|
|
680
|
+
:param dict data: The raw data that needs to have nested dictionaries removed
|
|
681
|
+
:param list skip_keys: List of Keys to skip during iteration of the provided dict
|
|
682
|
+
:return: Clean dictionary without nested dictionaries
|
|
683
|
+
:rtype: dict
|
|
684
|
+
"""
|
|
685
|
+
# create blank dictionary to store the clean dictionary
|
|
686
|
+
result = {}
|
|
687
|
+
# iterate through the keys and values in the provided dictionary
|
|
688
|
+
for key, value in data.items():
|
|
689
|
+
# see if skip_key was provided and if the current key == skip_key
|
|
690
|
+
if skip_keys and key in skip_keys:
|
|
691
|
+
# continue to the next key
|
|
692
|
+
continue
|
|
693
|
+
# check if the item is a nested dictionary
|
|
694
|
+
if isinstance(value, dict):
|
|
695
|
+
# evaluate the nested dictionary passing the nested dictionary and skip_key
|
|
696
|
+
new_keys = remove_nested_dict(value, skip_keys=skip_keys)
|
|
697
|
+
# update the value to a non-nested dictionary
|
|
698
|
+
# result[key] = value FIXME remove for now, is causing issues
|
|
699
|
+
# iterate through the keys of the nested dictionary
|
|
700
|
+
for inner_key in new_keys:
|
|
701
|
+
# make sure key doesn't already exist in result
|
|
702
|
+
if f"{key}_{inner_key}" in result:
|
|
703
|
+
last_char = inner_key[-1]
|
|
704
|
+
# see if the inner_key ends with a number
|
|
705
|
+
if isinstance(last_char, int):
|
|
706
|
+
# increment the number by 1 and replace the old one with the new one
|
|
707
|
+
last_char += 1
|
|
708
|
+
inner_key[-1] = last_char
|
|
709
|
+
else:
|
|
710
|
+
inner_key += "2"
|
|
711
|
+
# combine the key + nested key and store it into the clean dictionary
|
|
712
|
+
result[f"{key}_{inner_key}"] = result[key][inner_key]
|
|
713
|
+
else:
|
|
714
|
+
# item isn't a nested dictionary, save the data
|
|
715
|
+
result[key] = value
|
|
716
|
+
# return the un-nested dictionary
|
|
717
|
+
return result
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def flatten_dict(data: abc.MutableMapping) -> abc.MutableMapping:
|
|
721
|
+
"""
|
|
722
|
+
Flattens a dictionary
|
|
723
|
+
|
|
724
|
+
:param abc.MutableMapping data: data that needs to be flattened
|
|
725
|
+
:return: A flattened dictionary that has camelcase keys
|
|
726
|
+
:rtype: abc.MutableMapping
|
|
727
|
+
"""
|
|
728
|
+
import pandas as pd # Optimize import performance
|
|
729
|
+
|
|
730
|
+
# create variable to store the clean and flattened dictionary
|
|
731
|
+
flat_dict_clean = {}
|
|
732
|
+
|
|
733
|
+
# flatten the dictionary using panda's json_normalize function
|
|
734
|
+
[flat_dict] = pd.json_normalize(data, sep="@").to_dict(orient="records")
|
|
735
|
+
|
|
736
|
+
# iterate through the keys to camelcase them and
|
|
737
|
+
for key, value in flat_dict.items():
|
|
738
|
+
# find the location of all the @, which are the separator for nested keys
|
|
739
|
+
sep_locations = key.find("@")
|
|
740
|
+
|
|
741
|
+
# check if there are more than one period
|
|
742
|
+
if isinstance(sep_locations, list):
|
|
743
|
+
# iterate through the period locations
|
|
744
|
+
for period in sep_locations:
|
|
745
|
+
# capitalize the character following the period
|
|
746
|
+
key = key[:period] + key[period + 1].upper() + key[period + 2 :]
|
|
747
|
+
|
|
748
|
+
# remove the @
|
|
749
|
+
key = key.replace("@", "")
|
|
750
|
+
elif sep_locations != -1:
|
|
751
|
+
# capitalize the character following the @
|
|
752
|
+
key = key[:sep_locations] + key[sep_locations + 1].upper() + key[sep_locations + 2 :]
|
|
753
|
+
|
|
754
|
+
# remove the @
|
|
755
|
+
key = key.replace("@", "")
|
|
756
|
+
|
|
757
|
+
# add the cleaned key with the original value
|
|
758
|
+
flat_dict_clean[key] = value
|
|
759
|
+
return flat_dict_clean
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def days_between(vuln_time: str, fmt: Optional[str] = "%Y-%m-%dT%H:%M:%SZ") -> int:
|
|
763
|
+
"""
|
|
764
|
+
Find the difference in days between 2 datetimes
|
|
765
|
+
|
|
766
|
+
:param str vuln_time: date published
|
|
767
|
+
:param Optional[str] fmt: datetime string format, defaults to "%Y-%m-%d %H:%M:%S"
|
|
768
|
+
:return: days between 2 dates
|
|
769
|
+
:rtype: int
|
|
770
|
+
"""
|
|
771
|
+
start = datetime.strptime(vuln_time, fmt)
|
|
772
|
+
today = datetime.strftime(datetime.now(), fmt)
|
|
773
|
+
end = datetime.strptime(today, fmt)
|
|
774
|
+
difference = relativedelta.relativedelta(end, start)
|
|
775
|
+
return difference.days
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def parse_url_for_pagination(raw_string: str) -> str:
|
|
779
|
+
"""
|
|
780
|
+
Function to parse the provided string and get the URL for pagination
|
|
781
|
+
|
|
782
|
+
:param str raw_string: string that needs to be parsed for the pagination URL
|
|
783
|
+
:return: URL for pagination in Okta API
|
|
784
|
+
:rtype: str
|
|
785
|
+
"""
|
|
786
|
+
# split the string at the < signs
|
|
787
|
+
split_urls = raw_string.split("<")
|
|
788
|
+
|
|
789
|
+
# get the last entry
|
|
790
|
+
split_url = split_urls[-1]
|
|
791
|
+
|
|
792
|
+
# remove the remaining text from the last entry and return it
|
|
793
|
+
return split_url[: split_url.find(">")]
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def random_hex_color() -> str:
|
|
797
|
+
"""Return a random hex color
|
|
798
|
+
|
|
799
|
+
:return: hex color
|
|
800
|
+
:rtype: str
|
|
801
|
+
"""
|
|
802
|
+
return "#%02X%02X%02X" % (
|
|
803
|
+
random.randint(0, 255),
|
|
804
|
+
random.randint(0, 255),
|
|
805
|
+
random.randint(0, 255),
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def format_dict_to_html(data: dict, indent: int = 1) -> str:
|
|
810
|
+
"""Format a dictionary to HTML
|
|
811
|
+
|
|
812
|
+
:param dict data: Dictionary of data
|
|
813
|
+
:param int indent: Indentation. Defaults to 1.
|
|
814
|
+
:return: String representing HTML
|
|
815
|
+
:rtype: str
|
|
816
|
+
"""
|
|
817
|
+
htmls = []
|
|
818
|
+
for key, val in data.items():
|
|
819
|
+
htmls.append(
|
|
820
|
+
f"<span style='font-style: italic; color: #888'>{key}</span>: {format_data_to_html(val, indent + 1)}"
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
return f'<div style="margin-left: {indent}em">{",<br>".join(htmls)}</div>'
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def format_data_to_html(obj: Union[list, dict], indent: int = 1) -> str:
|
|
827
|
+
"""Format a list or a dict object to HTML
|
|
828
|
+
|
|
829
|
+
:param Union[list, dict] obj: list or dict of data
|
|
830
|
+
:param int indent: Indentation. Defaults to 1.
|
|
831
|
+
:return: String representing HTML
|
|
832
|
+
:rtype: str
|
|
833
|
+
"""
|
|
834
|
+
htmls = []
|
|
835
|
+
|
|
836
|
+
if isinstance(obj, list):
|
|
837
|
+
htmls.extend(format_data_to_html(key, indent + 1) for key in obj)
|
|
838
|
+
elif isinstance(obj, dict):
|
|
839
|
+
htmls.extend(
|
|
840
|
+
f"<span style='font-style: italic; color: #888'>{key}</span>: {format_data_to_html(val, indent + 1)}"
|
|
841
|
+
for key, val in obj.items()
|
|
842
|
+
)
|
|
843
|
+
if htmls:
|
|
844
|
+
return f'<div style="margin-left: {indent}em">{",<br>".join(htmls)}</div>'
|
|
845
|
+
return str(obj)
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def get_env_variable(key: str) -> Optional[Any]:
|
|
849
|
+
"""Return environment variable value regardless of case.
|
|
850
|
+
|
|
851
|
+
:param str key: Environment variable key
|
|
852
|
+
:return: Environment variable value
|
|
853
|
+
:rtype: Optional[Any]
|
|
854
|
+
"""
|
|
855
|
+
return next((v for k, v in os.environ.items() if k.lower() == key.lower()), None)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def find_keys(node: Union[list, dict], kv: Any):
|
|
859
|
+
"""
|
|
860
|
+
Python generator function to traverse deeply nested lists or dictionaries to
|
|
861
|
+
extract values of every key found in a given node
|
|
862
|
+
|
|
863
|
+
:param Union[list, dict] node: A string, dict or list to parse.
|
|
864
|
+
:param Any kv: Key, Value pair
|
|
865
|
+
:yield: Value of the key
|
|
866
|
+
"""
|
|
867
|
+
if isinstance(node, list):
|
|
868
|
+
for i in node:
|
|
869
|
+
yield from find_keys(i, kv)
|
|
870
|
+
elif isinstance(node, dict):
|
|
871
|
+
if kv in node:
|
|
872
|
+
yield node[kv]
|
|
873
|
+
for j in node.values():
|
|
874
|
+
yield from find_keys(j, kv)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def get_user_names() -> "pd.DataFrame":
|
|
878
|
+
"""This function uses API Endpoint to retrieve all user names in database
|
|
879
|
+
|
|
880
|
+
:return: pandas dataframe with usernames
|
|
881
|
+
:rtype: pd.DataFrame
|
|
882
|
+
"""
|
|
883
|
+
from regscale.core.app.api import Api
|
|
884
|
+
from regscale.core.app.application import Application
|
|
885
|
+
|
|
886
|
+
app = Application()
|
|
887
|
+
config = app.config
|
|
888
|
+
api = Api()
|
|
889
|
+
accounts = api.get(url=config["domain"] + "/api/accounts").json()
|
|
890
|
+
|
|
891
|
+
user_names = [[" ".join(item["name"].split()), item["id"]] for item in accounts]
|
|
892
|
+
import pandas as pd # Optimize import performance
|
|
893
|
+
|
|
894
|
+
return pd.DataFrame(
|
|
895
|
+
user_names,
|
|
896
|
+
index=None,
|
|
897
|
+
columns=["User", "UserId"],
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def check_empty_nan(value: Any, default_return: Any = None) -> Union[str, float, bool, None]:
|
|
902
|
+
"""
|
|
903
|
+
This function takes a given value and checks if value is empty, NaN, or a bool value based on value type
|
|
904
|
+
|
|
905
|
+
:param Any value: Value for checking
|
|
906
|
+
:param Any default_return: The default return value, defaults to None
|
|
907
|
+
:return: A string value, float value, bool value, or None
|
|
908
|
+
:rtype: Union[str, float, bool, None]
|
|
909
|
+
"""
|
|
910
|
+
if isinstance(value, str) and value.lower().strip() in ["true", "false"]:
|
|
911
|
+
return value.lower().strip() == "true"
|
|
912
|
+
if isinstance(value, str) and value.strip() == "":
|
|
913
|
+
return default_return
|
|
914
|
+
if isinstance(value, float) and math.isnan(value):
|
|
915
|
+
return default_return
|
|
916
|
+
return value
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def compute_hash(file: Union[BinaryIO, BytesIO], chunk_size: int = 8192) -> str:
|
|
920
|
+
"""
|
|
921
|
+
Computes the SHA-256 hash of a file-like object using chunks to avoid using too much memory
|
|
922
|
+
|
|
923
|
+
:param Union[BinaryIO, BytesIO] file: File-like object that supports .read() and .seek()
|
|
924
|
+
:param int chunk_size: Size of the chunks to read from the file, defaults to 8192
|
|
925
|
+
:return: SHA-256 hash of the file
|
|
926
|
+
:rtype: str
|
|
927
|
+
"""
|
|
928
|
+
hasher = hashlib.sha256()
|
|
929
|
+
|
|
930
|
+
# Read the file in small chunks to avoid using too much memory
|
|
931
|
+
while True:
|
|
932
|
+
if chunk := file.read(chunk_size):
|
|
933
|
+
hasher.update(chunk)
|
|
934
|
+
else:
|
|
935
|
+
break
|
|
936
|
+
# Reset the file's position, so it can be read again later
|
|
937
|
+
file.seek(0)
|
|
938
|
+
return hasher.hexdigest()
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def compute_hashes_in_directory(directory: Union[str, Path]) -> dict:
|
|
942
|
+
"""
|
|
943
|
+
Computes the SHA-256 hash of all files in a directory
|
|
944
|
+
|
|
945
|
+
:param Union[str, Path] directory: Directory to compute hashes for
|
|
946
|
+
:return: Dictionary of hashes keyed by file path
|
|
947
|
+
:rtype: dict
|
|
948
|
+
"""
|
|
949
|
+
file_hashes = {}
|
|
950
|
+
for file in os.listdir(directory):
|
|
951
|
+
with open(os.path.join(directory, file), "rb") as in_file:
|
|
952
|
+
file_hash = compute_hash(in_file)
|
|
953
|
+
file_hashes[file_hash] = os.path.join(directory, file)
|
|
954
|
+
return file_hashes
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def walk_directory_for_files(directory: str, extension: str = ".ckl") -> tuple[list, int]:
|
|
958
|
+
"""
|
|
959
|
+
Recursively search a directory for files with a given extension
|
|
960
|
+
|
|
961
|
+
:param str directory: Directory to search for files
|
|
962
|
+
:param str extension: File extension to search for, defaults to ".ckl"
|
|
963
|
+
:return: Tuple of list of files and total file count analyzed
|
|
964
|
+
:rtype: tuple[list, int]
|
|
965
|
+
"""
|
|
966
|
+
desired_files = []
|
|
967
|
+
total_file_count = 0
|
|
968
|
+
for root, dirs, files in os.walk(directory):
|
|
969
|
+
for file in files:
|
|
970
|
+
total_file_count += 1
|
|
971
|
+
if file.endswith(extension):
|
|
972
|
+
file_path = os.path.join(root, file)
|
|
973
|
+
desired_files.append(file_path)
|
|
974
|
+
return desired_files, total_file_count
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def detect_shell() -> str:
|
|
978
|
+
"""
|
|
979
|
+
Function to detect the current shell and returns it as a string
|
|
980
|
+
|
|
981
|
+
:return: String of the current shell
|
|
982
|
+
:rtype: str
|
|
983
|
+
"""
|
|
984
|
+
if os.name == "posix":
|
|
985
|
+
shell_path = os.getenv("SHELL")
|
|
986
|
+
if shell_path:
|
|
987
|
+
return os.path.basename(shell_path)
|
|
988
|
+
elif os.name == "nt":
|
|
989
|
+
try:
|
|
990
|
+
process = psutil.Process(os.getpid())
|
|
991
|
+
while process.name().lower() not in [
|
|
992
|
+
"cmd.exe",
|
|
993
|
+
"powershell.exe",
|
|
994
|
+
]:
|
|
995
|
+
process = process.parent()
|
|
996
|
+
if process is None:
|
|
997
|
+
return "Unknown"
|
|
998
|
+
terminal_process = process.name()
|
|
999
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
1000
|
+
return "Unknown"
|
|
1001
|
+
if "cmd.exe" in terminal_process.lower():
|
|
1002
|
+
return "CMD"
|
|
1003
|
+
elif "powershell.exe" in terminal_process.lower():
|
|
1004
|
+
return "PowerShell"
|
|
1005
|
+
else:
|
|
1006
|
+
return "Unknown"
|
|
1007
|
+
return "Unknown"
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def convert_to_string(data: Union[str, list, datetime, bool, dict]) -> str:
|
|
1011
|
+
"""
|
|
1012
|
+
Convert any data type to a string
|
|
1013
|
+
|
|
1014
|
+
:param Union[str, list, datetime, bool, dict] data: Data to convert to a string
|
|
1015
|
+
:return: Representation of the data as a string
|
|
1016
|
+
:rtype: str
|
|
1017
|
+
"""
|
|
1018
|
+
if isinstance(data, datetime):
|
|
1019
|
+
return data.strftime("%b %d, %Y")
|
|
1020
|
+
# see if the value is a boolean
|
|
1021
|
+
elif isinstance(data, bool):
|
|
1022
|
+
return "True" if data else "False"
|
|
1023
|
+
elif isinstance(data, dict):
|
|
1024
|
+
# Convert each key and value in the dictionary to a string
|
|
1025
|
+
str_dict = ""
|
|
1026
|
+
for key, value in data.items():
|
|
1027
|
+
str_dict += f"{convert_to_string(key)}: {convert_to_string(value)}\n"
|
|
1028
|
+
return str_dict
|
|
1029
|
+
elif isinstance(data, list):
|
|
1030
|
+
# Convert each item in the list to a string
|
|
1031
|
+
str_list = []
|
|
1032
|
+
for item in data:
|
|
1033
|
+
str_list.append(convert_to_string(item))
|
|
1034
|
+
return ", ".join(str_list)
|
|
1035
|
+
else:
|
|
1036
|
+
return str(data)
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def remove_timezone_from_date_str(date: str) -> str:
|
|
1040
|
+
"""
|
|
1041
|
+
Function to remove the timezone from a date string
|
|
1042
|
+
|
|
1043
|
+
:param str date: Date as a string to process
|
|
1044
|
+
:return: Clean date string
|
|
1045
|
+
:rtype: str
|
|
1046
|
+
"""
|
|
1047
|
+
tz_pattern = r"\s[A-Za-z]{3,4}\s*\+*\d*|[+-]\d{4}"
|
|
1048
|
+
|
|
1049
|
+
# Use re.sub to remove the time zone if it exists
|
|
1050
|
+
clean_date = re.sub(tz_pattern, "", date).rstrip(" ")
|
|
1051
|
+
return clean_date
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def update_keys_to_lowercase_first_letter(original_dict: Dict) -> Dict:
|
|
1055
|
+
"""
|
|
1056
|
+
Function to update dictionary keys to have the first letter lowercase
|
|
1057
|
+
|
|
1058
|
+
:param Dict original_dict: Dictionary to update
|
|
1059
|
+
:return: Dictionary with updated keys
|
|
1060
|
+
:rtype: Dict
|
|
1061
|
+
"""
|
|
1062
|
+
updated_dict = {}
|
|
1063
|
+
for key, value in original_dict.items():
|
|
1064
|
+
# Lowercase the first letter of the key and combine it with the rest of the string
|
|
1065
|
+
updated_key = key[0].lower() + key[1:] if key else key
|
|
1066
|
+
updated_dict[updated_key] = value
|
|
1067
|
+
return updated_dict
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def remove_keys(dictionary: dict, keys_to_remove: list) -> None:
|
|
1071
|
+
"""
|
|
1072
|
+
Removes specified keys from a dictionary
|
|
1073
|
+
|
|
1074
|
+
:param dict dictionary: The dictionary to remove keys from
|
|
1075
|
+
:param list keys_to_remove: List of keys to remove
|
|
1076
|
+
:rtype: None
|
|
1077
|
+
"""
|
|
1078
|
+
for key in keys_to_remove:
|
|
1079
|
+
dictionary.pop(key, None)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def log_memory() -> None:
|
|
1083
|
+
"""
|
|
1084
|
+
Function to log the current memory usage in a cross-platform fashion
|
|
1085
|
+
|
|
1086
|
+
:rtype: None
|
|
1087
|
+
"""
|
|
1088
|
+
logger.debug("RAM Percent Used: %i", psutil.virtual_memory()[2])
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
def extract_vuln_id_from_strings(text: str) -> Union[list, str]:
|
|
1092
|
+
"""
|
|
1093
|
+
Extract security notices and vuln id strings from a given text.
|
|
1094
|
+
|
|
1095
|
+
:param str text: The input text containing USN strings
|
|
1096
|
+
:return: A list of USN strings or a string
|
|
1097
|
+
:rtype: Union[list, str]
|
|
1098
|
+
"""
|
|
1099
|
+
usn_pattern = r"USN-\d{4}-\d{1,4}"
|
|
1100
|
+
ghsa_pattern = r"GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}"
|
|
1101
|
+
cve_pattern = r"CVE-\d{4}-\d{4,7}"
|
|
1102
|
+
svd_pattern = r"SVD-\d{4}-\d{4,7}"
|
|
1103
|
+
usn = re.findall(usn_pattern, text)
|
|
1104
|
+
ghsa = re.findall(ghsa_pattern, text)
|
|
1105
|
+
cve = re.findall(cve_pattern, text)
|
|
1106
|
+
splunk = re.findall(svd_pattern, text)
|
|
1107
|
+
res = usn + ghsa + cve + splunk
|
|
1108
|
+
if res:
|
|
1109
|
+
return res # no need to save spaces
|
|
1110
|
+
return text
|