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,1176 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for a RegScale Issue"""
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from regscale.models import File
|
|
11
|
+
|
|
12
|
+
from urllib.parse import urljoin
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from pydantic import Field, field_validator
|
|
16
|
+
from requests import JSONDecodeError
|
|
17
|
+
from rich.progress import Progress
|
|
18
|
+
|
|
19
|
+
from regscale.core.app.api import Api
|
|
20
|
+
from regscale.core.app.application import Application
|
|
21
|
+
from regscale.core.app.logz import create_logger
|
|
22
|
+
from regscale.core.app.utils.app_utils import check_file_path, get_current_datetime, reformat_str_date, save_data_to
|
|
23
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
24
|
+
from regscale.utils.version import RegscaleVersion
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OpenIssueDict(TypedDict):
|
|
28
|
+
"""TypedDict for open issues"""
|
|
29
|
+
|
|
30
|
+
id: int
|
|
31
|
+
otherIdentifier: str
|
|
32
|
+
integrationFindingId: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IssueSeverity(str, Enum):
|
|
36
|
+
"""Issue Severity"""
|
|
37
|
+
|
|
38
|
+
NotAssigned = "IV - Not Assigned"
|
|
39
|
+
Low = "III - Low - Other Weakness"
|
|
40
|
+
Moderate = "II - Moderate - Reportable Condition"
|
|
41
|
+
High = "I - High - Significant Deficiency"
|
|
42
|
+
Critical = "0 - Critical - Critical Deficiency"
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Return the value of the Enum as a string
|
|
47
|
+
|
|
48
|
+
:return: The value of the Enum as a string
|
|
49
|
+
:rtype: str
|
|
50
|
+
"""
|
|
51
|
+
return self.value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class IssueStatus(str, Enum):
|
|
55
|
+
"""Issue Status"""
|
|
56
|
+
|
|
57
|
+
Draft = "Draft"
|
|
58
|
+
PendingScreening = "Pending Screening"
|
|
59
|
+
Open = "Open"
|
|
60
|
+
PendingVerification = "Pending Verification"
|
|
61
|
+
Closed = "Closed"
|
|
62
|
+
Cancelled = "Cancelled"
|
|
63
|
+
PendingDecommission = "Pending Decommission"
|
|
64
|
+
SupplyChainProcurementDependency = "Supply Chain/Procurement Dependency"
|
|
65
|
+
VendorDependency = "Vendor Dependency for Fix"
|
|
66
|
+
Delayed = "Delayed"
|
|
67
|
+
ExceptionWaiver = "Exception/Waiver"
|
|
68
|
+
PendingApproval = "Pending Approval"
|
|
69
|
+
|
|
70
|
+
def __str__(self) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Return the value of the Enum as a string
|
|
73
|
+
|
|
74
|
+
:return: The value of the Enum as a string
|
|
75
|
+
:rtype: str
|
|
76
|
+
"""
|
|
77
|
+
return self.value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Issue(RegScaleModel):
|
|
81
|
+
"""Issue Model"""
|
|
82
|
+
|
|
83
|
+
_module_slug = "issues"
|
|
84
|
+
_x_api_version = "2"
|
|
85
|
+
_unique_fields = [
|
|
86
|
+
["integrationFindingId", "vulnerabilityId", "status"],
|
|
87
|
+
["otherIdentifier", "parentModule", "parentId", "status"],
|
|
88
|
+
]
|
|
89
|
+
_exclude_graphql_fields = [
|
|
90
|
+
"facility",
|
|
91
|
+
"org",
|
|
92
|
+
"createdBy",
|
|
93
|
+
"lastUpdatedBy",
|
|
94
|
+
"extra_data",
|
|
95
|
+
"tenantsId",
|
|
96
|
+
"issueOwner",
|
|
97
|
+
"controlImplementationIds",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
title: Optional[str] = ""
|
|
101
|
+
severityLevel: Union[IssueSeverity, str] = IssueSeverity.NotAssigned
|
|
102
|
+
issueOwnerId: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
103
|
+
dueDate: Optional[str] = ""
|
|
104
|
+
id: int = 0
|
|
105
|
+
tenantsId: int = 1
|
|
106
|
+
uuid: Optional[str] = None
|
|
107
|
+
dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
108
|
+
description: Optional[str] = None
|
|
109
|
+
issueOwner: Optional[str] = None
|
|
110
|
+
costEstimate: Optional[int] = None
|
|
111
|
+
levelOfEffort: Optional[int] = None
|
|
112
|
+
identification: Optional[str] = "" # Has to be an empty string or else it will fail to create
|
|
113
|
+
capStatus: Optional[str] = None
|
|
114
|
+
sourceReport: Optional[str] = None
|
|
115
|
+
status: Optional[Union[IssueStatus, str]] = None
|
|
116
|
+
dateCompleted: Optional[str] = None
|
|
117
|
+
activitiesObserved: Optional[str] = None
|
|
118
|
+
failuresObserved: Optional[str] = None
|
|
119
|
+
requirementsViolated: Optional[str] = None
|
|
120
|
+
safetyImpact: Optional[str] = None
|
|
121
|
+
securityImpact: Optional[str] = None
|
|
122
|
+
qualityImpact: Optional[str] = None
|
|
123
|
+
facility: Optional[str] = None
|
|
124
|
+
facilityId: Optional[int] = None
|
|
125
|
+
org: Optional[str] = None
|
|
126
|
+
orgId: Optional[int] = None
|
|
127
|
+
controlId: Optional[int] = None
|
|
128
|
+
assessmentId: Optional[int] = None
|
|
129
|
+
requirementId: Optional[int] = None
|
|
130
|
+
securityPlanId: Optional[int] = None
|
|
131
|
+
projectId: Optional[int] = None
|
|
132
|
+
supplyChainId: Optional[int] = None
|
|
133
|
+
policyId: Optional[int] = None
|
|
134
|
+
componentId: Optional[int] = None
|
|
135
|
+
incidentId: Optional[int] = None
|
|
136
|
+
jiraId: Optional[str] = None
|
|
137
|
+
serviceNowId: Optional[str] = None
|
|
138
|
+
wizId: Optional[str] = None
|
|
139
|
+
burpId: Optional[str] = None
|
|
140
|
+
defenderId: Optional[str] = None
|
|
141
|
+
defenderAlertId: Optional[str] = None
|
|
142
|
+
defenderCloudId: Optional[str] = None
|
|
143
|
+
salesforceId: Optional[str] = None
|
|
144
|
+
prismaId: Optional[str] = None
|
|
145
|
+
tenableId: Optional[str] = None
|
|
146
|
+
tenableNessusId: Optional[str] = None
|
|
147
|
+
qualysId: Optional[str] = None
|
|
148
|
+
pluginId: Optional[str] = None
|
|
149
|
+
cve: Optional[str] = None
|
|
150
|
+
assetIdentifier: Optional[str] = None
|
|
151
|
+
falsePositive: Optional[str] = None
|
|
152
|
+
operationalRequirement: Optional[str] = None
|
|
153
|
+
autoApproved: Optional[str] = None
|
|
154
|
+
kevList: Optional[str] = None
|
|
155
|
+
dateFirstDetected: Optional[str] = None
|
|
156
|
+
changes: Optional[str] = None
|
|
157
|
+
vendorDependency: Optional[str] = None
|
|
158
|
+
vendorName: Optional[str] = None
|
|
159
|
+
vendorLastUpdate: Optional[str] = None
|
|
160
|
+
vendorActions: Optional[str] = None
|
|
161
|
+
deviationRationale: Optional[str] = None
|
|
162
|
+
parentId: Optional[int] = None
|
|
163
|
+
parentModule: Optional[str] = None
|
|
164
|
+
createdBy: Optional[str] = None
|
|
165
|
+
createdById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
166
|
+
lastUpdatedBy: Optional[str] = None
|
|
167
|
+
lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
168
|
+
dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
169
|
+
securityChecks: Optional[str] = None
|
|
170
|
+
recommendedActions: Optional[str] = None
|
|
171
|
+
isPublic: Optional[bool] = True
|
|
172
|
+
dependabotId: Optional[str] = None
|
|
173
|
+
isPoam: Optional[bool] = False
|
|
174
|
+
originalRiskRating: Optional[str] = None
|
|
175
|
+
adjustedRiskRating: Optional[str] = None
|
|
176
|
+
bRiskAdjustment: Optional[bool] = None
|
|
177
|
+
basisForAdjustment: Optional[str] = None
|
|
178
|
+
poamComments: Optional[str] = None
|
|
179
|
+
otherIdentifier: Optional[str] = None
|
|
180
|
+
integrationFindingId: Optional[str] = None
|
|
181
|
+
wizCicdScanId: Optional[str] = None
|
|
182
|
+
wizCicdScanVuln: Optional[str] = None
|
|
183
|
+
sonarQubeIssueId: Optional[str] = None
|
|
184
|
+
qualityAssurerId: Optional[str] = None
|
|
185
|
+
remediationDescription: Optional[str] = None
|
|
186
|
+
manualDetectionSource: Optional[str] = None
|
|
187
|
+
manualDetectionId: Optional[str] = None
|
|
188
|
+
vulnerabilityId: Optional[int] = None
|
|
189
|
+
riskAdjustment: Optional[str] = None
|
|
190
|
+
controlImplementationIds: List[int] = Field(default_factory=list)
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def is_multiple_controls_supported() -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Check if multiple control is supported
|
|
196
|
+
|
|
197
|
+
:return: True if multiple controls is supported
|
|
198
|
+
:rtype: bool
|
|
199
|
+
"""
|
|
200
|
+
return RegscaleVersion.meets_minimum_version("7.0.0", dev_is_latest=False)
|
|
201
|
+
|
|
202
|
+
def __init__(self, **data: Any):
|
|
203
|
+
# Handle aliases internally
|
|
204
|
+
if "parent_id" in data:
|
|
205
|
+
data["parentId"] = data.pop("parent_id")
|
|
206
|
+
if "parent_module" in data:
|
|
207
|
+
data["parentModule"] = data.pop("parent_module")
|
|
208
|
+
super().__init__(**data)
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def _get_additional_endpoints() -> dict:
|
|
212
|
+
"""
|
|
213
|
+
Get additional endpoints for the Issues model.
|
|
214
|
+
|
|
215
|
+
:return: A dictionary of additional endpoints
|
|
216
|
+
:rtype: dict
|
|
217
|
+
"""
|
|
218
|
+
return {
|
|
219
|
+
"user_open_items_days": "/api/{model_slug}/userOpenItemsDays/{strUserId}/{intDays}",
|
|
220
|
+
"set_quality_assurer": "/api/{model_slug}/setQualityAssurer/{intIssueId}/{strQaUserId}",
|
|
221
|
+
"remove_quality_assurer": "/api/{model_slug}/removeQualityAssurer/{intIssueId}",
|
|
222
|
+
"process_lineage": "/api/{model_slug}/processLineage/{intIssueId}",
|
|
223
|
+
"get_count": "/api/{model_slug}/getCount",
|
|
224
|
+
"get_by_date_range": "/api/{model_slug}/getByDateRange/{dtStart}/{dtEnd}",
|
|
225
|
+
"get_by_date_range_and_date_field": "/api/{model_slug}/getByDateRangeAndDateField/{dateField}/{dtStart}/{dtEnd}",
|
|
226
|
+
"graph_by_owner_then_status": "/api/{model_slug}/graphByOwnerThenStatus/{dateField}/{dtStart}/{dtEnd}",
|
|
227
|
+
"group_by_owner_and_plan_then_status_forever": "/api/{model_slug}/groupByOwnerAndPlanThenStatusForever",
|
|
228
|
+
"group_by_owner_and_plan_then_status": "/api/{model_slug}/groupByOwnerAndPlanThenStatus/{dateField}/{dtStart}/{dtEnd}",
|
|
229
|
+
"group_by_owner_and_component_then_status": "/api/{model_slug}/groupByOwnerAndComponentThenStatus/{dateField}/{dtStart}/{dtEnd}",
|
|
230
|
+
"group_by_owner_and_component_then_status_forever": "/api/{model_slug}/groupByOwnerAndComponentThenStatusForever",
|
|
231
|
+
"group_by_owner_and_component_then_status_drilldown": "/api/{model_slug}/groupByOwnerAndComponentThenStatusDrilldown/{intId}/{ownerId}/{dateField}/{dtStart}/{dtEnd}",
|
|
232
|
+
"group_by_owner_and_plan_then_status_drilldown": "/api/{model_slug}/groupByOwnerAndPlanThenStatusDrilldown/{intId}/{ownerId}/{dateField}/{dtStart}/{dtEnd}",
|
|
233
|
+
"get_by_date_closed": "/api/{model_slug}/getByDateClosed/{dtStart}/{dtEnd}",
|
|
234
|
+
"get_all_by_integration_field": "/api/{model_slug}/getAllByIntegrationField/{strFieldName}",
|
|
235
|
+
"get_active_by_integration_field": "/api/{model_slug}/getActiveByIntegrationField/{strFieldName}",
|
|
236
|
+
"get_filtered_list": "/api/{model_slug}/getFilteredList/{strFind}",
|
|
237
|
+
"get_all_by_grand_parent": "/api/{model_slug}/getAllByGrandParent/{intParentId}/{strModule}",
|
|
238
|
+
"query_by_custom_field": "/api/{model_slug}/queryByCustomField/{strFieldName}/{strValue}",
|
|
239
|
+
"issue_timeline": "/api/{model_slug}/issueTimeline/{intId}/{strModule}/{strType}",
|
|
240
|
+
"calendar_issues": "/api/{model_slug}/calendarIssues/{dtDate}/{fId}/{orgId}/{userId}",
|
|
241
|
+
"graph": "/api/{model_slug}/graph",
|
|
242
|
+
"graph_by_date": "/api/{model_slug}/graphByDate/{strGroupBy}/{year}",
|
|
243
|
+
"filter_issues": "/api/{model_slug}/filterIssues",
|
|
244
|
+
"update_issue_screening": "/api/{model_slug}/screening/{id}",
|
|
245
|
+
"retrieve_issue": "/api/{model_slug}/{intId}",
|
|
246
|
+
"emass_component_export": "/api/{model_slug}/emassComponentExport/{intId}",
|
|
247
|
+
"emass_ssp_export": "/api/{model_slug}/emassSSPExport/{intId}",
|
|
248
|
+
"find_by_other_identifier": "/api/{model_slug}/findByOtherIdentifier/{id}",
|
|
249
|
+
"find_by_service_now_id": "/api/{model_slug}/findByServiceNowId/{id}",
|
|
250
|
+
"find_by_salesforce_case": "/api/{model_slug}/findBySalesforceCase/{id}",
|
|
251
|
+
"find_by_jira_id": "/api/{model_slug}/findByJiraId/{id}",
|
|
252
|
+
"find_by_dependabot_id": "/api/{model_slug}/findByDependabotId/{id}",
|
|
253
|
+
"find_by_prisma_id": "/api/{model_slug}/findByPrismaId/{id}",
|
|
254
|
+
"find_by_wiz_id": "/api/{model_slug}/findByWizId/{id}",
|
|
255
|
+
"find_by_wiz_cicd_scan_id": "/api/{model_slug}/findByWizCicdScanId/{wizCicdScanId}",
|
|
256
|
+
"get_all_by_wiz_cicd_scan_vuln": "/api/{model_slug}/getAllByWizCicdScanVuln/{wizCicdScanVuln}",
|
|
257
|
+
"get_active_by_wiz_cicd_scan_vuln": "/api/{model_slug}/getActiveByWizCicdScanVuln/{wizCicdScanVuln}",
|
|
258
|
+
"find_by_sonar_qube_issue_id": "/api/{model_slug}/findBySonarQubeIssueId/{projectId}/{issueId}",
|
|
259
|
+
"find_by_defender_365_id": "/api/{model_slug}/findByDefender365Id/{id}",
|
|
260
|
+
"find_by_defender_365_alert_id": "/api/{model_slug}/findByDefender365AlertId/{id}",
|
|
261
|
+
"find_by_defender_cloud_id": "/api/{model_slug}/findByDefenderCloudId/{id}",
|
|
262
|
+
"report": "/api/{model_slug}/report/{strReport}",
|
|
263
|
+
"schedule": "/api/{model_slug}/schedule/{dtStart}/{dtEnd}/{dvar}",
|
|
264
|
+
"graph_due_date": "/api/{model_slug}/graphDueDate/{year}",
|
|
265
|
+
"graph_date_identified": "/api/{model_slug}/graphDateIdentified/{year}/{status}",
|
|
266
|
+
"graph_severity_level_by_date_identified": "/api/{model_slug}/graphSeverityLevelByDateIdentified/{year}",
|
|
267
|
+
"graph_cost_by_date_identified": "/api/{model_slug}/graphCostByDateIdentified/{year}",
|
|
268
|
+
"graph_facility_by_date_identified": "/api/{model_slug}/graphFacilityByDateIdentified/{year}",
|
|
269
|
+
"get_severity_level_by_status": "/api/{model_slug}/getSeverityLevelByStatus/{dtStart}/{dtEnd}",
|
|
270
|
+
"graph_due_date_by_status": "/api/{model_slug}/graphDueDateByStatus/{year}",
|
|
271
|
+
"dashboard": "/api/{model_slug}/dashboard/{strGroupBy}",
|
|
272
|
+
"drilldown": "/api/{model_slug}/drilldown/{strMonth}/{temporal}/{strCategory}/{chartType}",
|
|
273
|
+
"main_dashboard": "/api/{model_slug}/mainDashboard/{intYear}",
|
|
274
|
+
"main_dashboard_chart": "/api/{model_slug}/mainDashboardChart/{year}",
|
|
275
|
+
"dashboard_by_parent": "/api/{model_slug}/dashboardByParent/{strGroupBy}/{intId}/{strModule}",
|
|
276
|
+
"batch_create": "/api/{model_slug}/batchCreate",
|
|
277
|
+
"batch_update": "/api/{model_slug}/batchUpdate",
|
|
278
|
+
"find_by_integration_finding_id": "/api/{model_slug}/findByIntegrationFindingId/{id}",
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def find_by_other_identifier(cls, other_identifier: str) -> List["Issue"]:
|
|
283
|
+
"""
|
|
284
|
+
Find an issue by its other identifier.
|
|
285
|
+
|
|
286
|
+
:param str other_identifier: The other identifier to search for
|
|
287
|
+
:return: The found Issues
|
|
288
|
+
:rtype: List[Issue]
|
|
289
|
+
"""
|
|
290
|
+
api_handler = cls._get_api_handler()
|
|
291
|
+
endpoint = cls.get_endpoint("find_by_other_identifier").format(id=other_identifier)
|
|
292
|
+
|
|
293
|
+
response = api_handler.get(endpoint)
|
|
294
|
+
issues: List["Issue"] = cls._handle_list_response(response)
|
|
295
|
+
return issues
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def find_by_integration_finding_id(cls, integration_finding_id: str) -> List["Issue"]:
|
|
299
|
+
"""
|
|
300
|
+
Find an issue by its integration finding id.
|
|
301
|
+
|
|
302
|
+
:param str integration_finding_id: The integration finding id to search for
|
|
303
|
+
:return: The found Issues
|
|
304
|
+
:rtype: List[Issue]
|
|
305
|
+
"""
|
|
306
|
+
endpoint = cls.get_endpoint("find_by_integration_finding_id").format(id=integration_finding_id)
|
|
307
|
+
response = cls._get_api_handler().get(endpoint)
|
|
308
|
+
issues: List["Issue"] = cls._handle_list_response(response)
|
|
309
|
+
# Ensure we are returning the cached object if it exists to ensure bulk updates work
|
|
310
|
+
issues = [cls.get_cached_object(cls._get_cache_key(issue, {})) or issue for issue in issues]
|
|
311
|
+
return issues
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def get_all_by_integration_field(cls, field: str) -> List["Issue"]:
|
|
315
|
+
"""
|
|
316
|
+
Get all issues by integration field.
|
|
317
|
+
|
|
318
|
+
:param str field: The integration field to search for
|
|
319
|
+
:return: The found Issues
|
|
320
|
+
:rtype: List[Issue]
|
|
321
|
+
"""
|
|
322
|
+
endpoint = cls.get_endpoint("get_all_by_integration_field").format(strFieldName=field)
|
|
323
|
+
response = cls._get_api_handler().get(endpoint)
|
|
324
|
+
return cls._handle_list_response(response)
|
|
325
|
+
|
|
326
|
+
@classmethod
|
|
327
|
+
def get_all_by_manual_detection_source(cls, value: str) -> List["Issue"]:
|
|
328
|
+
"""
|
|
329
|
+
Get all issues with the manual detection source
|
|
330
|
+
|
|
331
|
+
:param str value: The manual detection source to search for
|
|
332
|
+
:return: The found Issues
|
|
333
|
+
:rtype: List[Issue]
|
|
334
|
+
"""
|
|
335
|
+
query = f"""
|
|
336
|
+
query {{
|
|
337
|
+
issues(take: 50, skip: 0, where: {{ manualDetectionSource: {{eq: "{value}"}}}})
|
|
338
|
+
{{
|
|
339
|
+
items {{
|
|
340
|
+
{Issue.build_graphql_fields()}
|
|
341
|
+
}}
|
|
342
|
+
pageInfo {{
|
|
343
|
+
hasNextPage
|
|
344
|
+
}}
|
|
345
|
+
,totalCount}}
|
|
346
|
+
}}
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
existing_issues = cls._get_api_handler().graph(query=query)["issues"]["items"]
|
|
350
|
+
except (JSONDecodeError, TypeError, KeyError):
|
|
351
|
+
existing_issues = []
|
|
352
|
+
return [Issue(**issue) for issue in existing_issues]
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def get_issues_by_asset_map(plan_id: int) -> Dict[str, List["Issue"]]:
|
|
356
|
+
"""
|
|
357
|
+
Get a dictionary of issues grouped by asset identifier for a given security plan.
|
|
358
|
+
|
|
359
|
+
:param int plan_id: The ID of the security plan
|
|
360
|
+
:return: A dictionary where keys are asset identifiers and values are lists of associated issues
|
|
361
|
+
:rtype: Dict[str, List[Issue]]
|
|
362
|
+
"""
|
|
363
|
+
issues = Issue.fetch_issues_by_ssp(None, ssp_id=plan_id, status=IssueStatus.Open.value)
|
|
364
|
+
issues_by_asset = defaultdict(list)
|
|
365
|
+
for issue in issues:
|
|
366
|
+
if issue.assetIdentifier:
|
|
367
|
+
for asset_identifier in issue.assetIdentifier.split("\n"):
|
|
368
|
+
issues_by_asset[asset_identifier].append(issue)
|
|
369
|
+
return issues_by_asset
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def assign_risk_rating(cls, value: Any) -> str:
|
|
373
|
+
"""
|
|
374
|
+
Function to assign risk rating for an issue in RegScale using the provided value
|
|
375
|
+
|
|
376
|
+
:param Any value: The value to analyze to determine the issue's risk rating
|
|
377
|
+
:return: String of risk rating for RegScale issue, or "" if not found
|
|
378
|
+
:rtype: str
|
|
379
|
+
"""
|
|
380
|
+
if isinstance(value, str):
|
|
381
|
+
if "low" in value.lower():
|
|
382
|
+
return "Low"
|
|
383
|
+
if "medium" in value.lower() or "moderate" in value.lower():
|
|
384
|
+
return "Moderate"
|
|
385
|
+
if "high" in value.lower() or "critical" in value.lower():
|
|
386
|
+
return "High"
|
|
387
|
+
return ""
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def assign_severity(value: Optional[Any] = None) -> str:
|
|
391
|
+
"""
|
|
392
|
+
Function to assign severity for an issue in RegScale using the provided value
|
|
393
|
+
|
|
394
|
+
:param Optional[Any] value: The value to analyze to determine the issue's severity, defaults to None
|
|
395
|
+
:return: String of severity level for RegScale issue
|
|
396
|
+
:rtype: str
|
|
397
|
+
"""
|
|
398
|
+
severity_levels = {
|
|
399
|
+
"low": IssueSeverity.Low.value,
|
|
400
|
+
"moderate": IssueSeverity.Moderate.value,
|
|
401
|
+
"high": IssueSeverity.High.value,
|
|
402
|
+
}
|
|
403
|
+
severity = IssueSeverity.NotAssigned.value
|
|
404
|
+
# see if the value is an int or float
|
|
405
|
+
if isinstance(value, (int, float)):
|
|
406
|
+
# check severity score and assign it to the appropriate RegScale severity
|
|
407
|
+
if value >= 7:
|
|
408
|
+
severity = severity_levels["high"]
|
|
409
|
+
elif 4 <= value < 7:
|
|
410
|
+
severity = severity_levels["moderate"]
|
|
411
|
+
else:
|
|
412
|
+
severity = severity_levels["low"]
|
|
413
|
+
elif isinstance(value, str):
|
|
414
|
+
if value.lower() in ["low", "lowest"]:
|
|
415
|
+
severity = severity_levels["low"]
|
|
416
|
+
elif value.lower() in ["medium", "moderate"]:
|
|
417
|
+
severity = severity_levels["moderate"]
|
|
418
|
+
elif value.lower() in ["high", "critical", "highest"]:
|
|
419
|
+
severity = severity_levels["high"]
|
|
420
|
+
elif value in list(severity_levels.values()):
|
|
421
|
+
severity = value
|
|
422
|
+
return severity
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def update_issue(app: Application, issue: "Issue") -> Optional["Issue"]:
|
|
426
|
+
"""
|
|
427
|
+
Update an issue in RegScale
|
|
428
|
+
|
|
429
|
+
:param Application app: Application Instance
|
|
430
|
+
:param Issue issue: Issue to update in RegScale
|
|
431
|
+
:return: Updated issue in RegScale
|
|
432
|
+
:rtype: Optional[Issue]
|
|
433
|
+
"""
|
|
434
|
+
if isinstance(issue, dict):
|
|
435
|
+
issue = Issue(**issue)
|
|
436
|
+
api = Api()
|
|
437
|
+
issue_id = issue.id
|
|
438
|
+
|
|
439
|
+
response = api.put(app.config["domain"] + f"/api/issues/{issue_id}", json=issue.dict())
|
|
440
|
+
if response.status_code == 200:
|
|
441
|
+
try:
|
|
442
|
+
issue = Issue(**response.json())
|
|
443
|
+
except JSONDecodeError:
|
|
444
|
+
return None
|
|
445
|
+
return issue
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def insert_issue(app: Application, issue: "Issue") -> Optional["Issue"]:
|
|
449
|
+
"""
|
|
450
|
+
Insert an issue in RegScale
|
|
451
|
+
|
|
452
|
+
:param Application app: Application Instance
|
|
453
|
+
:param Issue issue: Issue to insert to RegScale
|
|
454
|
+
:return: Newly created issue in RegScale
|
|
455
|
+
:rtype: Optional[Issue]
|
|
456
|
+
"""
|
|
457
|
+
if isinstance(issue, dict):
|
|
458
|
+
issue = Issue(**issue)
|
|
459
|
+
api = Api()
|
|
460
|
+
logger = create_logger()
|
|
461
|
+
response = api.post(app.config["domain"] + "/api/issues", json=issue.dict())
|
|
462
|
+
if response.status_code == 200:
|
|
463
|
+
try:
|
|
464
|
+
issue = Issue(**response.json())
|
|
465
|
+
except JSONDecodeError as jex:
|
|
466
|
+
logger.error("Unable to read issue:\n%s", jex)
|
|
467
|
+
return None
|
|
468
|
+
else:
|
|
469
|
+
logger.warning("Unable to insert issue: %s", issue.title)
|
|
470
|
+
return issue
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def bulk_insert(
|
|
474
|
+
app: Application,
|
|
475
|
+
issues: List["Issue"],
|
|
476
|
+
max_workers: Optional[int] = 10,
|
|
477
|
+
batch_size: int = 100,
|
|
478
|
+
batch: bool = False,
|
|
479
|
+
) -> List["Issue"]:
|
|
480
|
+
"""
|
|
481
|
+
Bulk insert issues using the RegScale API and ThreadPoolExecutor
|
|
482
|
+
|
|
483
|
+
:param Application app: Application Instance
|
|
484
|
+
:param List["Issue"] issues: List of issues to insert
|
|
485
|
+
:param Optional[int] max_workers: Max Workers, defaults to 10
|
|
486
|
+
:param int batch_size: Number of issues to insert per batch, defaults to 100
|
|
487
|
+
:param bool batch: Insert issues in batches, defaults to False
|
|
488
|
+
:return: List of Issues from RegScale
|
|
489
|
+
:rtype: List[Issue]
|
|
490
|
+
"""
|
|
491
|
+
api = Api()
|
|
492
|
+
url = urljoin(app.config["domain"], "/api/{model_slug}/batchcreate")
|
|
493
|
+
results = []
|
|
494
|
+
api.logger.info("Creating %i new issue(s) in RegScale...", len(issues))
|
|
495
|
+
with Progress(transient=False) as progress:
|
|
496
|
+
task = progress.add_task(f"Creating {len(issues)} new issue(s) in RegScale...", total=len(issues))
|
|
497
|
+
if batch:
|
|
498
|
+
# Chunk list into batches
|
|
499
|
+
batches = [issues[i : i + batch_size] for i in range(0, len(issues), batch_size)]
|
|
500
|
+
for my_batch in batches:
|
|
501
|
+
res = api.post(url=url.format(model_slug="issues"), json=[iss.dict() for iss in my_batch])
|
|
502
|
+
if not res.ok:
|
|
503
|
+
app.logger.error(
|
|
504
|
+
"%i: %s\nError creating batch of issues: %s",
|
|
505
|
+
res.status_code,
|
|
506
|
+
res.text,
|
|
507
|
+
res.reason,
|
|
508
|
+
)
|
|
509
|
+
results.append(res)
|
|
510
|
+
progress.update(task, advance=len(my_batch))
|
|
511
|
+
else:
|
|
512
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
513
|
+
futures = [
|
|
514
|
+
executor.submit(
|
|
515
|
+
issue.create,
|
|
516
|
+
)
|
|
517
|
+
for issue in issues
|
|
518
|
+
]
|
|
519
|
+
for future in as_completed(futures):
|
|
520
|
+
issue = future.result()
|
|
521
|
+
results.append(issue)
|
|
522
|
+
progress.update(task, advance=1)
|
|
523
|
+
return results
|
|
524
|
+
|
|
525
|
+
@staticmethod
|
|
526
|
+
def bulk_update(
|
|
527
|
+
app: Application,
|
|
528
|
+
issues: List["Issue"],
|
|
529
|
+
max_workers: int = 10,
|
|
530
|
+
batch_size: int = 100,
|
|
531
|
+
batch: bool = False,
|
|
532
|
+
) -> List["Issue"]:
|
|
533
|
+
"""
|
|
534
|
+
Bulk update issues using the RegScale API and ThreadPoolExecutor
|
|
535
|
+
|
|
536
|
+
:param Application app: Application Instance
|
|
537
|
+
:param List["Issue"] issues: List of issues to update
|
|
538
|
+
:param int max_workers: Max Workers, defaults to 10
|
|
539
|
+
:param int batch_size: Number of issues to update per batch, defaults to 100
|
|
540
|
+
:param bool batch: Update issues in batches, defaults to False
|
|
541
|
+
:return: List of Issues from RegScale
|
|
542
|
+
:rtype: List[Issue]
|
|
543
|
+
"""
|
|
544
|
+
api = Api()
|
|
545
|
+
url = urljoin(app.config["domain"], "/api/{model_slug}/batchupdate")
|
|
546
|
+
results = []
|
|
547
|
+
api.logger.info("Updating %i issue(s) in RegScale...", len(issues))
|
|
548
|
+
with Progress(transient=False) as progress:
|
|
549
|
+
task = progress.add_task(f"Updating {len(issues)} issue(s) in RegScale...", total=len(issues))
|
|
550
|
+
if batch:
|
|
551
|
+
# Chunk list into batches
|
|
552
|
+
batches = [issues[i : i + batch_size] for i in range(0, len(issues), batch_size)]
|
|
553
|
+
for my_batch in batches:
|
|
554
|
+
res = api.put(url=url.format(model_slug="issues"), json=[iss.dict() for iss in my_batch])
|
|
555
|
+
if not res.ok:
|
|
556
|
+
app.logger.error(
|
|
557
|
+
"%i: %s\nError creating batch of issues: %s",
|
|
558
|
+
res.status_code,
|
|
559
|
+
res.text,
|
|
560
|
+
res.reason,
|
|
561
|
+
)
|
|
562
|
+
results.append(res)
|
|
563
|
+
progress.update(task, advance=len(my_batch))
|
|
564
|
+
else:
|
|
565
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
566
|
+
futures = [executor.submit(issue.save) for issue in issues]
|
|
567
|
+
for future in as_completed(futures):
|
|
568
|
+
issue = future.result()
|
|
569
|
+
results.append(issue)
|
|
570
|
+
progress.update(task, advance=1)
|
|
571
|
+
|
|
572
|
+
return results
|
|
573
|
+
|
|
574
|
+
@staticmethod
|
|
575
|
+
def fetch_issues_by_parent(
|
|
576
|
+
app: Application,
|
|
577
|
+
regscale_id: int,
|
|
578
|
+
regscale_module: str,
|
|
579
|
+
) -> List["Issue"]:
|
|
580
|
+
"""
|
|
581
|
+
Find all issues by parent id and parent module
|
|
582
|
+
|
|
583
|
+
:param Application app: Application Instance
|
|
584
|
+
:param int regscale_id: Parent ID
|
|
585
|
+
:param str regscale_module: Parent Module
|
|
586
|
+
:return: List of issues from RegScale
|
|
587
|
+
:rtype: List[Issue]
|
|
588
|
+
"""
|
|
589
|
+
api = Api()
|
|
590
|
+
body = f"""
|
|
591
|
+
query {{
|
|
592
|
+
issues(take: 50, skip: 0, where: {{ parentModule: {{eq: "{regscale_module}"}} parentId: {{
|
|
593
|
+
eq: {regscale_id}
|
|
594
|
+
}}}}) {{
|
|
595
|
+
items {{
|
|
596
|
+
{Issue.build_graphql_fields()}
|
|
597
|
+
}}
|
|
598
|
+
pageInfo {{
|
|
599
|
+
hasNextPage
|
|
600
|
+
}}
|
|
601
|
+
,totalCount}}
|
|
602
|
+
}}
|
|
603
|
+
"""
|
|
604
|
+
try:
|
|
605
|
+
existing_issues = api.graph(query=body)["issues"]["items"]
|
|
606
|
+
except (JSONDecodeError, TypeError, KeyError):
|
|
607
|
+
existing_issues = []
|
|
608
|
+
return [Issue(**issue) for issue in existing_issues]
|
|
609
|
+
|
|
610
|
+
@staticmethod
|
|
611
|
+
def fetch_issues_by_ssp(
|
|
612
|
+
app: Optional[Application],
|
|
613
|
+
ssp_id: int,
|
|
614
|
+
status: Optional[str] = None,
|
|
615
|
+
) -> List["Issue"]:
|
|
616
|
+
"""
|
|
617
|
+
Find all issues by parent id and parent module
|
|
618
|
+
|
|
619
|
+
:param Application app: Application Instance
|
|
620
|
+
:param int ssp_id: RegScale SSP Id
|
|
621
|
+
:param Optional[str] status: Issue Status, defaults to None
|
|
622
|
+
:return: List of Issues from RegScale SSP
|
|
623
|
+
:rtype: List[Issue]
|
|
624
|
+
"""
|
|
625
|
+
api = Api()
|
|
626
|
+
where_conditions = [f"securityPlanId: {{eq: {ssp_id}}}"]
|
|
627
|
+
if status:
|
|
628
|
+
where_conditions.append(f'status: {{eq: "{status}"}}')
|
|
629
|
+
where_str = ", ".join(where_conditions)
|
|
630
|
+
body = f"""
|
|
631
|
+
query {{
|
|
632
|
+
issues(take: 50, skip: 0, where: {{ {where_str} }}) {{
|
|
633
|
+
items {{
|
|
634
|
+
{Issue.build_graphql_fields()}
|
|
635
|
+
}}
|
|
636
|
+
pageInfo {{
|
|
637
|
+
hasNextPage
|
|
638
|
+
}}
|
|
639
|
+
totalCount
|
|
640
|
+
}}
|
|
641
|
+
}}
|
|
642
|
+
"""
|
|
643
|
+
try:
|
|
644
|
+
existing_issues = api.graph(query=body)["issues"]["items"]
|
|
645
|
+
except (JSONDecodeError, TypeError, KeyError):
|
|
646
|
+
existing_issues = []
|
|
647
|
+
return [Issue(**issue) for issue in existing_issues]
|
|
648
|
+
|
|
649
|
+
@staticmethod
|
|
650
|
+
def fetch_all_issues(
|
|
651
|
+
app: Application,
|
|
652
|
+
) -> List["Issue"]:
|
|
653
|
+
"""
|
|
654
|
+
Find all issues in RegScale
|
|
655
|
+
|
|
656
|
+
:param Application app: Application Instance
|
|
657
|
+
:return: List of Issues from RegScale
|
|
658
|
+
:rtype: List[Issue]
|
|
659
|
+
"""
|
|
660
|
+
api = Api()
|
|
661
|
+
body = f"""
|
|
662
|
+
query {{
|
|
663
|
+
issues(take: 50, skip: 0) {{
|
|
664
|
+
items {{
|
|
665
|
+
{Issue.build_graphql_fields()}
|
|
666
|
+
}}
|
|
667
|
+
pageInfo {{
|
|
668
|
+
hasNextPage
|
|
669
|
+
}}
|
|
670
|
+
,totalCount}}
|
|
671
|
+
}}
|
|
672
|
+
"""
|
|
673
|
+
try:
|
|
674
|
+
api.logger.info("Retrieving all issues from RegScale...")
|
|
675
|
+
existing_issues = api.graph(query=body)["issues"]["items"]
|
|
676
|
+
api.logger.info("Retrieved %i issue(s) from RegScale.", len(existing_issues))
|
|
677
|
+
except (JSONDecodeError, KeyError):
|
|
678
|
+
existing_issues = []
|
|
679
|
+
return [Issue(**issue) for issue in existing_issues]
|
|
680
|
+
|
|
681
|
+
@staticmethod
|
|
682
|
+
def fetch_issue_by_id(
|
|
683
|
+
app: Application,
|
|
684
|
+
issue_id: int,
|
|
685
|
+
) -> Optional["Issue"]:
|
|
686
|
+
"""
|
|
687
|
+
Find a RegScale issue by its id
|
|
688
|
+
|
|
689
|
+
:param Application app: Application Instance
|
|
690
|
+
:param int issue_id: RegScale Issue Id
|
|
691
|
+
:return: Issue from RegScale or None if it doesn't exist
|
|
692
|
+
:rtype: Optional[Issue]
|
|
693
|
+
"""
|
|
694
|
+
api = Api()
|
|
695
|
+
issue_response = api.get(url=f"{app.config['domain']}/api/issues/{issue_id}")
|
|
696
|
+
issue = None
|
|
697
|
+
try:
|
|
698
|
+
issue = Issue(**issue_response.json())
|
|
699
|
+
except JSONDecodeError:
|
|
700
|
+
logger = create_logger()
|
|
701
|
+
logger.warning("Unable to find issue with id %i", issue_id)
|
|
702
|
+
return issue
|
|
703
|
+
|
|
704
|
+
@staticmethod
|
|
705
|
+
def fetch_issues_and_attachments_by_parent(
|
|
706
|
+
parent_id: int,
|
|
707
|
+
parent_module: str,
|
|
708
|
+
fetch_attachments: Optional[bool] = True,
|
|
709
|
+
save_issues: Optional[bool] = True,
|
|
710
|
+
) -> Tuple[List["Issue"], Optional[Dict[int, List["File"]]]]:
|
|
711
|
+
"""
|
|
712
|
+
Fetch all issues from RegScale for the provided parent record
|
|
713
|
+
|
|
714
|
+
:param int parent_id: Parent record ID in RegScale
|
|
715
|
+
:param str parent_module: Parent record module in RegScale
|
|
716
|
+
:param Optional[Application] app: Application object, deprecated 3.26.2024, defaults to None
|
|
717
|
+
:param Optional[bool] fetch_attachments: Whether to fetch attachments from RegScale, defaults to True
|
|
718
|
+
:param Optional[bool] save_issues: Save RegScale issues to a .json in artifacts, defaults to True
|
|
719
|
+
:return: List of RegScale issues, dictionary of issue's attachments as File objects
|
|
720
|
+
:rtype: Tuple[List[Issue], Optional[Dict[int, List[File]]]]
|
|
721
|
+
"""
|
|
722
|
+
from regscale.models import File
|
|
723
|
+
|
|
724
|
+
attachments: Optional[Dict[int, List[File]]] = None
|
|
725
|
+
logger = create_logger()
|
|
726
|
+
# get the existing issues for the parent record that are already in RegScale
|
|
727
|
+
logger.info("Fetching full issue list from RegScale %s #%i.", parent_module, parent_id)
|
|
728
|
+
issues_data = Issue().get_all_by_parent(
|
|
729
|
+
parent_id=parent_id,
|
|
730
|
+
parent_module=parent_module,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# check for null/not found response
|
|
734
|
+
if len(issues_data) == 0:
|
|
735
|
+
logger.warning(
|
|
736
|
+
"No existing issues for this RegScale record #%i in %s.",
|
|
737
|
+
parent_id,
|
|
738
|
+
parent_module,
|
|
739
|
+
)
|
|
740
|
+
else:
|
|
741
|
+
if fetch_attachments:
|
|
742
|
+
# get the attachments for the issue
|
|
743
|
+
api = Api()
|
|
744
|
+
attachments = {
|
|
745
|
+
issue.id: files
|
|
746
|
+
for issue in issues_data
|
|
747
|
+
if (
|
|
748
|
+
files := File.get_files_for_parent_from_regscale(
|
|
749
|
+
parent_id=issue.id,
|
|
750
|
+
parent_module="issues",
|
|
751
|
+
api=api,
|
|
752
|
+
)
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
logger.info(
|
|
756
|
+
"Found %i issue(s) from RegScale %s #%i for processing.",
|
|
757
|
+
len(issues_data),
|
|
758
|
+
parent_module,
|
|
759
|
+
parent_id,
|
|
760
|
+
)
|
|
761
|
+
if save_issues:
|
|
762
|
+
# write issue data to a json file
|
|
763
|
+
check_file_path("artifacts")
|
|
764
|
+
file_name = "existingRegScaleIssues.json"
|
|
765
|
+
file_path = Path("./artifacts") / file_name
|
|
766
|
+
save_data_to(
|
|
767
|
+
file=file_path,
|
|
768
|
+
data=[issue.dict() for issue in issues_data],
|
|
769
|
+
output_log=False,
|
|
770
|
+
)
|
|
771
|
+
logger.info(
|
|
772
|
+
"Saved RegScale issue(s) for %s #%i, see %s", parent_module, parent_id, str(file_path.absolute())
|
|
773
|
+
)
|
|
774
|
+
return issues_data, attachments
|
|
775
|
+
|
|
776
|
+
@classmethod
|
|
777
|
+
def get_open_issues_ids_by_implementation_id(cls, plan_id: int) -> Dict[int, List[OpenIssueDict]]:
|
|
778
|
+
"""
|
|
779
|
+
Get all open issues by implementation id for a given security plan
|
|
780
|
+
|
|
781
|
+
:param int plan_id: The ID of the parent
|
|
782
|
+
:return: A dictionary of control ids and their associated issue ids
|
|
783
|
+
:rtype: Dict[int, List[OpenIssueDict]]
|
|
784
|
+
"""
|
|
785
|
+
import logging
|
|
786
|
+
|
|
787
|
+
logger = logging.getLogger("regscale")
|
|
788
|
+
take = 50
|
|
789
|
+
skip = 0
|
|
790
|
+
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
791
|
+
logger.info("Fetching open issues for controls and for security plan %i...", plan_id)
|
|
792
|
+
|
|
793
|
+
# Define fields based on version
|
|
794
|
+
fields = """
|
|
795
|
+
id,
|
|
796
|
+
controlId
|
|
797
|
+
otherIdentifier
|
|
798
|
+
{extra_fields}
|
|
799
|
+
""".format(
|
|
800
|
+
extra_fields="controlImplementations { id }" if cls.is_multiple_controls_supported() else ""
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
while True:
|
|
804
|
+
query = f"""
|
|
805
|
+
query MyQuery() {{
|
|
806
|
+
{cls.get_module_string()}(
|
|
807
|
+
skip: {skip},
|
|
808
|
+
take: {take},
|
|
809
|
+
where: {{
|
|
810
|
+
securityPlanId: {{eq: {plan_id}}},
|
|
811
|
+
status: {{eq: "Open"}}
|
|
812
|
+
}}
|
|
813
|
+
) {{
|
|
814
|
+
items {{ {fields} }}
|
|
815
|
+
pageInfo {{ hasNextPage }}
|
|
816
|
+
totalCount
|
|
817
|
+
}}
|
|
818
|
+
}}
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
response = cls._get_api_handler().graph(query)
|
|
822
|
+
items = response.get(cls.get_module_string(), {}).get("items", [])
|
|
823
|
+
|
|
824
|
+
for item in items:
|
|
825
|
+
if cls.is_multiple_controls_supported() and item.get("controlImplementations"):
|
|
826
|
+
for control in item.get("controlImplementations", []):
|
|
827
|
+
control_issues[control["id"]].append(
|
|
828
|
+
OpenIssueDict(id=item["id"], otherIdentifier=item["otherIdentifier"])
|
|
829
|
+
)
|
|
830
|
+
else:
|
|
831
|
+
control_issues[item["controlId"]].append(
|
|
832
|
+
OpenIssueDict(id=item["id"], otherIdentifier=item["otherIdentifier"])
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
if not response.get(cls.get_module_string(), {}).get("pageInfo", {}).get("hasNextPage", False):
|
|
836
|
+
break
|
|
837
|
+
skip += take
|
|
838
|
+
logger.info("Finished fetching %i open control issue(s) for security plan %i", len(control_issues), plan_id)
|
|
839
|
+
return control_issues
|
|
840
|
+
|
|
841
|
+
@classmethod
|
|
842
|
+
def get_sort_position_dict(cls) -> Dict[str, int]:
|
|
843
|
+
"""
|
|
844
|
+
Overrides the base method.
|
|
845
|
+
|
|
846
|
+
:return: The sort position in the list of properties
|
|
847
|
+
:rtype: Dict[str, int]
|
|
848
|
+
"""
|
|
849
|
+
return {
|
|
850
|
+
"id": 1,
|
|
851
|
+
"title": 2,
|
|
852
|
+
"severityLevel": 3,
|
|
853
|
+
"issueOwnerId": 4,
|
|
854
|
+
"dueDate": 5,
|
|
855
|
+
"uuid": -1,
|
|
856
|
+
"dateCreated": 6,
|
|
857
|
+
"description": 7,
|
|
858
|
+
"issueOwner": -1,
|
|
859
|
+
"costEstimate": 9,
|
|
860
|
+
"levelOfEffort": 10,
|
|
861
|
+
"identification": 11,
|
|
862
|
+
"capStatus": 12,
|
|
863
|
+
"sourceReport": 13,
|
|
864
|
+
"status": 14,
|
|
865
|
+
"dateCompleted": 15,
|
|
866
|
+
"activitiesObserved": 16,
|
|
867
|
+
"failuresObserved": 17,
|
|
868
|
+
"requirementsViolated": 18,
|
|
869
|
+
"safetyImpact": 19,
|
|
870
|
+
"securityImpact": 20,
|
|
871
|
+
"qualityImpact": 21,
|
|
872
|
+
"facility": -1,
|
|
873
|
+
"facilityId": -1,
|
|
874
|
+
"org": -1,
|
|
875
|
+
"orgId": -1,
|
|
876
|
+
"controlId": 26,
|
|
877
|
+
"assessmentId": 27,
|
|
878
|
+
"requirementId": 28,
|
|
879
|
+
"securityPlanId": 29,
|
|
880
|
+
"projectId": 30,
|
|
881
|
+
"supplyChainId": 31,
|
|
882
|
+
"policyId": 32,
|
|
883
|
+
"componentId": 33,
|
|
884
|
+
"incidentId": 34,
|
|
885
|
+
"jiraId": 35,
|
|
886
|
+
"serviceNowId": 36,
|
|
887
|
+
"wizId": 37,
|
|
888
|
+
"burpId": 38,
|
|
889
|
+
"defenderId": 39,
|
|
890
|
+
"defenderAlertId": 40,
|
|
891
|
+
"defenderCloudId": 41,
|
|
892
|
+
"salesforceId": 42,
|
|
893
|
+
"prismaId": 43,
|
|
894
|
+
"tenableId": 44,
|
|
895
|
+
"tenableNessusId": 45,
|
|
896
|
+
"qualysId": 46,
|
|
897
|
+
"pluginId": 47,
|
|
898
|
+
"cve": 48,
|
|
899
|
+
"assetIdentifier": 49,
|
|
900
|
+
"falsePositive": 50,
|
|
901
|
+
"operationalRequirement": 51,
|
|
902
|
+
"autoApproved": 52,
|
|
903
|
+
"kevList": 53,
|
|
904
|
+
"dateFirstDetected": 54,
|
|
905
|
+
"changes": 55,
|
|
906
|
+
"vendorDependency": 56,
|
|
907
|
+
"vendorName": 57,
|
|
908
|
+
"vendorLastUpdate": 58,
|
|
909
|
+
"vendorActions": 59,
|
|
910
|
+
"deviationRationale": 60,
|
|
911
|
+
"parentId": 61,
|
|
912
|
+
"parentModule": 62,
|
|
913
|
+
"createdBy": -1,
|
|
914
|
+
"createdById": -1,
|
|
915
|
+
"lastUpdatedBy": -1,
|
|
916
|
+
"lastUpdatedById": -1,
|
|
917
|
+
"dateLastUpdated": -1,
|
|
918
|
+
"securityChecks": 63,
|
|
919
|
+
"recommendedActions": 64,
|
|
920
|
+
"isPublic": 65,
|
|
921
|
+
"dependabotId": 66,
|
|
922
|
+
"isPoam": 67,
|
|
923
|
+
"originalRiskRating": 68,
|
|
924
|
+
"adjustedRiskRating": 69,
|
|
925
|
+
"bRiskAdjustment": 70,
|
|
926
|
+
"basisForAdjustment": 71,
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
@classmethod
|
|
930
|
+
def get_enum_values(cls, field_name: str) -> List[Union[IssueSeverity, IssueStatus, str]]:
|
|
931
|
+
"""
|
|
932
|
+
Overrides the base method.
|
|
933
|
+
|
|
934
|
+
:param str field_name: The property name to provide enum values for
|
|
935
|
+
:return: List of enum values or strings
|
|
936
|
+
:rtype: List[Union[IssueSeverity, IssueStatus, str]]
|
|
937
|
+
"""
|
|
938
|
+
if field_name == "severityLevel":
|
|
939
|
+
return [severity.__str__() for severity in IssueSeverity]
|
|
940
|
+
if field_name == "status":
|
|
941
|
+
return [status.__str__() for status in IssueStatus]
|
|
942
|
+
if field_name == "identification":
|
|
943
|
+
return [
|
|
944
|
+
"A-123 Review",
|
|
945
|
+
"Assessment/Audit (External)",
|
|
946
|
+
"Assessment/Audit (Internal)",
|
|
947
|
+
"Critical Control Review",
|
|
948
|
+
"FDCC/USGCB",
|
|
949
|
+
"GAO Audit",
|
|
950
|
+
"IG Audit",
|
|
951
|
+
"Incidnet Response Lessons Learned",
|
|
952
|
+
"ITAR",
|
|
953
|
+
"Other",
|
|
954
|
+
"Penetration Test",
|
|
955
|
+
"Risk Assessment",
|
|
956
|
+
"Security Authorization",
|
|
957
|
+
"Security Control Assessment",
|
|
958
|
+
"Vulnerability Assessment",
|
|
959
|
+
]
|
|
960
|
+
return cls.get_bool_enums(field_name)
|
|
961
|
+
|
|
962
|
+
@classmethod
|
|
963
|
+
def get_lookup_field(cls, field_name: str) -> str:
|
|
964
|
+
"""
|
|
965
|
+
Overrides the base method.
|
|
966
|
+
|
|
967
|
+
:param str field_name: The property name to provide enum values for
|
|
968
|
+
:return: The field name to look up
|
|
969
|
+
:rtype: str
|
|
970
|
+
"""
|
|
971
|
+
lookup_fields = {"issueOwnerId": "user", "facilityId": "facilities", "orgId": "organizations"}
|
|
972
|
+
if field_name in lookup_fields.keys():
|
|
973
|
+
return lookup_fields[field_name]
|
|
974
|
+
return ""
|
|
975
|
+
|
|
976
|
+
@classmethod
|
|
977
|
+
def is_date_field(cls, field_name: str) -> bool:
|
|
978
|
+
"""
|
|
979
|
+
Overrides the base method.
|
|
980
|
+
|
|
981
|
+
:param str field_name: The property name to provide enum values for
|
|
982
|
+
:return: If the field should be formatted as a date
|
|
983
|
+
:rtype: bool
|
|
984
|
+
"""
|
|
985
|
+
return field_name in ["dueDate", "dateCreated", "dateCompleted", "dateFirstDetected"]
|
|
986
|
+
|
|
987
|
+
# pylint: disable=C0301
|
|
988
|
+
@classmethod
|
|
989
|
+
def get_export_query(cls, app: Application, parent_id: int, parent_module: str) -> List[Dict[str, Any]]:
|
|
990
|
+
"""
|
|
991
|
+
Overrides the base method.
|
|
992
|
+
|
|
993
|
+
:param Application app: RegScale Application object
|
|
994
|
+
:param int parent_id: RegScale ID of parent
|
|
995
|
+
:param str parent_module: Module of parent
|
|
996
|
+
:return: GraphQL response from RegScale
|
|
997
|
+
:rtype: List[Dict[str, Any]]
|
|
998
|
+
"""
|
|
999
|
+
body = """
|
|
1000
|
+
query {
|
|
1001
|
+
issues (skip: 0, take: 50, where: {parentId: {eq: parent_id} parentModule: {eq: "parent_module"}}) {
|
|
1002
|
+
items {
|
|
1003
|
+
id
|
|
1004
|
+
issueOwnerId
|
|
1005
|
+
issueOwner {
|
|
1006
|
+
firstName
|
|
1007
|
+
lastName
|
|
1008
|
+
userName
|
|
1009
|
+
}
|
|
1010
|
+
title
|
|
1011
|
+
dateCreated
|
|
1012
|
+
description
|
|
1013
|
+
severityLevel
|
|
1014
|
+
costEstimate
|
|
1015
|
+
levelOfEffort
|
|
1016
|
+
dueDate
|
|
1017
|
+
identification
|
|
1018
|
+
status
|
|
1019
|
+
dateCompleted
|
|
1020
|
+
activitiesObserved
|
|
1021
|
+
failuresObserved
|
|
1022
|
+
requirementsViolated
|
|
1023
|
+
safetyImpact
|
|
1024
|
+
securityImpact
|
|
1025
|
+
qualityImpact
|
|
1026
|
+
securityChecks
|
|
1027
|
+
recommendedActions
|
|
1028
|
+
isPoam
|
|
1029
|
+
parentId
|
|
1030
|
+
parentModule
|
|
1031
|
+
}
|
|
1032
|
+
totalCount
|
|
1033
|
+
pageInfo {
|
|
1034
|
+
hasNextPage
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
""".replace(
|
|
1039
|
+
"parent_module", parent_module
|
|
1040
|
+
).replace(
|
|
1041
|
+
"parent_id", str(parent_id)
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
api = Api()
|
|
1045
|
+
existing_issue_data = api.graph(query=body)
|
|
1046
|
+
|
|
1047
|
+
if existing_issue_data["issues"]["totalCount"] > 0:
|
|
1048
|
+
raw_data = existing_issue_data["issues"]["items"]
|
|
1049
|
+
moded_data = []
|
|
1050
|
+
for a in raw_data:
|
|
1051
|
+
moded_data.append(build_issue_dict_from_query(a))
|
|
1052
|
+
return moded_data
|
|
1053
|
+
return []
|
|
1054
|
+
|
|
1055
|
+
# pylint: emable=C0301
|
|
1056
|
+
|
|
1057
|
+
@classmethod
|
|
1058
|
+
def find_by_service_now_id(cls, snow_id: str) -> List["Issue"]:
|
|
1059
|
+
"""
|
|
1060
|
+
Find issues by its serviceNowId
|
|
1061
|
+
|
|
1062
|
+
:param str snow_id: The serviceNowId to search for
|
|
1063
|
+
:return: The found Issues
|
|
1064
|
+
:rtype: List[Issue]
|
|
1065
|
+
"""
|
|
1066
|
+
api_handler = cls._get_api_handler()
|
|
1067
|
+
endpoint = cls.get_endpoint("find_by_service_now_id").format(id=snow_id)
|
|
1068
|
+
|
|
1069
|
+
response = api_handler.get(endpoint)
|
|
1070
|
+
issues: List["Issue"] = cls._handle_list_response(response)
|
|
1071
|
+
return issues
|
|
1072
|
+
|
|
1073
|
+
@classmethod
|
|
1074
|
+
def use_query(cls) -> bool:
|
|
1075
|
+
"""
|
|
1076
|
+
Overrides the base method.
|
|
1077
|
+
|
|
1078
|
+
:return: Whether to use query
|
|
1079
|
+
:rtype: bool
|
|
1080
|
+
"""
|
|
1081
|
+
return True
|
|
1082
|
+
|
|
1083
|
+
@classmethod
|
|
1084
|
+
def get_extra_fields(cls) -> List[str]:
|
|
1085
|
+
"""
|
|
1086
|
+
Overrides the base method.
|
|
1087
|
+
|
|
1088
|
+
:return: List of extra field names
|
|
1089
|
+
:rtype: List[str]
|
|
1090
|
+
"""
|
|
1091
|
+
return []
|
|
1092
|
+
|
|
1093
|
+
@classmethod
|
|
1094
|
+
def get_include_fields(cls) -> List[str]:
|
|
1095
|
+
"""
|
|
1096
|
+
Overrides the base method.
|
|
1097
|
+
|
|
1098
|
+
:return: List of field names to include
|
|
1099
|
+
:rtype: List[str]
|
|
1100
|
+
"""
|
|
1101
|
+
return []
|
|
1102
|
+
|
|
1103
|
+
@field_validator("riskAdjustment")
|
|
1104
|
+
def validate_risk_adjustment(cls, v: str) -> str:
|
|
1105
|
+
"""
|
|
1106
|
+
Validates the riskAdjustment field.
|
|
1107
|
+
|
|
1108
|
+
:param str v: The value to validate
|
|
1109
|
+
:raise ValueError: If the value is not valid
|
|
1110
|
+
|
|
1111
|
+
:return: The validated values
|
|
1112
|
+
:rtype: str
|
|
1113
|
+
|
|
1114
|
+
"""
|
|
1115
|
+
allowed_values = ["No", "Yes", "Pending", None]
|
|
1116
|
+
if v not in allowed_values:
|
|
1117
|
+
raise ValueError(f"riskAdjustment must be one of {allowed_values}")
|
|
1118
|
+
return v
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def build_issue_dict_from_query(a: Dict[str, Any]) -> Dict[str, Any]:
|
|
1122
|
+
"""
|
|
1123
|
+
This method takes in a single record from the graphQL query and reformat
|
|
1124
|
+
it into an issue dict.
|
|
1125
|
+
|
|
1126
|
+
:param Dict[str, Any] a: A single record returned from the query
|
|
1127
|
+
:return: Reformatted dict for the data needs
|
|
1128
|
+
:rtype: Dict[str, Any]
|
|
1129
|
+
"""
|
|
1130
|
+
modeled_item = {}
|
|
1131
|
+
modeled_item["id"] = a["id"]
|
|
1132
|
+
modeled_item["issueOwnerId"] = (
|
|
1133
|
+
(
|
|
1134
|
+
str(a["issueOwner"]["lastName"]).strip()
|
|
1135
|
+
+ ", "
|
|
1136
|
+
+ str(a["issueOwner"]["firstName"]).strip()
|
|
1137
|
+
+ " ("
|
|
1138
|
+
+ str(a["issueOwner"]["userName"]).strip()
|
|
1139
|
+
+ ")"
|
|
1140
|
+
)
|
|
1141
|
+
if a["issueOwner"]
|
|
1142
|
+
else "None"
|
|
1143
|
+
)
|
|
1144
|
+
modeled_item["title"] = a["title"]
|
|
1145
|
+
modeled_item["dateCreated"] = reformat_str_date(a["dateCreated"])
|
|
1146
|
+
modeled_item["isPoam"] = a["isPoam"]
|
|
1147
|
+
modeled_item["description"] = a["description"] if a["description"] else "None"
|
|
1148
|
+
modeled_item["severityLevel"] = a["severityLevel"]
|
|
1149
|
+
modeled_item["costEstimate"] = a["costEstimate"] if a["costEstimate"] and a["costEstimate"] != "None" else 0.00
|
|
1150
|
+
modeled_item["levelOfEffort"] = a["levelOfEffort"] if a["levelOfEffort"] and a["levelOfEffort"] != "None" else 0
|
|
1151
|
+
modeled_item["dueDate"] = reformat_str_date(a["dueDate"])
|
|
1152
|
+
modeled_item["identification"] = a["identification"] if a["identification"] else "None"
|
|
1153
|
+
modeled_item["status"] = a["status"] if a["status"] else "None"
|
|
1154
|
+
modeled_item["dateCompleted"] = reformat_str_date(a["dateCompleted"]) if a["dateCompleted"] else ""
|
|
1155
|
+
modeled_item["activitiesObserved"] = blank_if_empty(a, "activitiesObserved")
|
|
1156
|
+
modeled_item["failuresObserved"] = blank_if_empty(a, "failuresObserved")
|
|
1157
|
+
modeled_item["requirementsViolated"] = blank_if_empty(a, "requirementsViolated")
|
|
1158
|
+
modeled_item["safetyImpact"] = blank_if_empty(a, "safetyImpact")
|
|
1159
|
+
modeled_item["securityImpact"] = blank_if_empty(a, "securityImpact")
|
|
1160
|
+
modeled_item["qualityImpact"] = blank_if_empty(a, "qualityImpact")
|
|
1161
|
+
modeled_item["securityChecks"] = blank_if_empty(a, "securityChecks")
|
|
1162
|
+
modeled_item["recommendedActions"] = blank_if_empty(a, "recommendedActions")
|
|
1163
|
+
return modeled_item
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def blank_if_empty(data: dict, field: str) -> str:
|
|
1167
|
+
"""
|
|
1168
|
+
This method will return the value of the specified field from the passed dict if it exists. If
|
|
1169
|
+
it doesn't, an empty string will be returned.
|
|
1170
|
+
|
|
1171
|
+
:param dict data: the data to be queried for the field
|
|
1172
|
+
:param str field: the field to return if it exists in the dict
|
|
1173
|
+
:return: str the field value or an empty string
|
|
1174
|
+
:rtype: str
|
|
1175
|
+
"""
|
|
1176
|
+
return data.get(field, "")
|