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,1128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for a RegScale Security Control Implementation"""
|
|
4
|
+
# standard python imports
|
|
5
|
+
import logging
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from lxml.etree import Element
|
|
12
|
+
from pydantic import ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from regscale.core.app.api import Api
|
|
15
|
+
from regscale.core.app.application import Application
|
|
16
|
+
from regscale.core.app.utils.app_utils import get_current_datetime, remove_keys
|
|
17
|
+
from regscale.core.app.utils.catalog_utils.common import parentheses_to_dot
|
|
18
|
+
from regscale.models.regscale_models.implementation_role import ImplementationRole
|
|
19
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
20
|
+
from regscale.models.regscale_models.security_control import SecurityControl
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("regscale")
|
|
23
|
+
PATCH_CONTENT_TYPE = "application/json-patch+json"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ControlImplementationStatus(str, Enum):
|
|
27
|
+
"""Control Implementation Status"""
|
|
28
|
+
|
|
29
|
+
FullyImplemented = "Fully Implemented"
|
|
30
|
+
Implemented = "Implemented"
|
|
31
|
+
NotImplemented = "Not Implemented"
|
|
32
|
+
PartiallyImplemented = "Partially Implemented"
|
|
33
|
+
InRemediation = "In Remediation"
|
|
34
|
+
Inherited = "Inherited"
|
|
35
|
+
NA = "Not Applicable"
|
|
36
|
+
Planned = "Planned"
|
|
37
|
+
Archived = "Archived"
|
|
38
|
+
RiskAccepted = "Risk Accepted"
|
|
39
|
+
Alternative = "Alternate Implementation"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ControlImplementationOrigin(str, Enum):
|
|
43
|
+
"""Control Origination"""
|
|
44
|
+
|
|
45
|
+
Provider = "Provider"
|
|
46
|
+
ProviderSS = "Provider (System Specific)"
|
|
47
|
+
CustomerConfigured = "Customer Configured"
|
|
48
|
+
CustomerProvided = "Customer"
|
|
49
|
+
Inherited = "Inherited"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ControlImplementation(RegScaleModel):
|
|
53
|
+
"""Control Implementation"""
|
|
54
|
+
|
|
55
|
+
_module_slug = "controlImplementation"
|
|
56
|
+
_module_string = "controls"
|
|
57
|
+
_get_objects_for_list = True
|
|
58
|
+
|
|
59
|
+
controlOwnerId: str = Field(default_factory=RegScaleModel.get_user_id)
|
|
60
|
+
status: str # Required
|
|
61
|
+
controlID: int # Required foreign key to Security Control
|
|
62
|
+
status_lst: List[ControlImplementationStatus] = []
|
|
63
|
+
id: int = 0
|
|
64
|
+
parentId: Optional[int] = None
|
|
65
|
+
parentModule: Optional[str] = None
|
|
66
|
+
control: Union[SecurityControl, dict, None] = None # Security Control object
|
|
67
|
+
createdById: Optional[str] = Field(default_factory=get_current_datetime)
|
|
68
|
+
uuid: Optional[str] = None
|
|
69
|
+
policy: Optional[str] = None
|
|
70
|
+
implementation: Optional[str] = None
|
|
71
|
+
dateLastAssessed: Optional[str] = None
|
|
72
|
+
lastAssessmentResult: Optional[str] = None
|
|
73
|
+
practiceLevel: Optional[str] = None
|
|
74
|
+
processLevel: Optional[str] = None
|
|
75
|
+
cyberFunction: Optional[str] = None
|
|
76
|
+
implementationType: Optional[str] = None
|
|
77
|
+
implementationMethod: Optional[str] = None
|
|
78
|
+
qdWellDesigned: Optional[str] = None
|
|
79
|
+
qdProcedures: Optional[str] = None
|
|
80
|
+
qdSegregation: Optional[str] = None
|
|
81
|
+
qdFlowdown: Optional[str] = None
|
|
82
|
+
qdAutomated: Optional[str] = None
|
|
83
|
+
qdOverall: Optional[str] = None
|
|
84
|
+
qiResources: Optional[str] = None
|
|
85
|
+
qiMaturity: Optional[str] = None
|
|
86
|
+
qiReporting: Optional[str] = None
|
|
87
|
+
qiVendorCompliance: Optional[str] = None
|
|
88
|
+
qiIssues: Optional[str] = None
|
|
89
|
+
qiOverall: Optional[str] = None
|
|
90
|
+
responsibility: Optional[str] = None
|
|
91
|
+
inheritedControlId: Optional[int] = None
|
|
92
|
+
inheritedRequirementId: Optional[int] = None
|
|
93
|
+
inheritedSecurityPlanId: Optional[int] = None
|
|
94
|
+
inheritedPolicyId: Optional[int] = None
|
|
95
|
+
dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
96
|
+
lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
97
|
+
dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
98
|
+
weight: Optional[int] = None
|
|
99
|
+
isPublic: Optional[bool] = True
|
|
100
|
+
inheritable: Optional[bool] = False
|
|
101
|
+
systemRoleId: Optional[int] = None
|
|
102
|
+
plannedImplementationDate: Optional[str] = None
|
|
103
|
+
stepsToImplement: Optional[str] = None
|
|
104
|
+
exclusionJustification: Optional[str] = None
|
|
105
|
+
bBaseline: Optional[bool] = False
|
|
106
|
+
bInherited: Optional[bool] = False
|
|
107
|
+
bOverlay: Optional[bool] = False
|
|
108
|
+
bTailored: Optional[bool] = False
|
|
109
|
+
bStatusImplemented: Optional[bool] = False
|
|
110
|
+
bStatusPartiallyImplemented: Optional[bool] = False
|
|
111
|
+
bStatusPlanned: Optional[bool] = False
|
|
112
|
+
bStatusAlternative: Optional[bool] = False
|
|
113
|
+
bStatusNotApplicable: Optional[bool] = False
|
|
114
|
+
bServiceProviderCorporate: Optional[bool] = False
|
|
115
|
+
bServiceProviderSystemSpecific: Optional[bool] = False
|
|
116
|
+
bServiceProviderHybrid: Optional[bool] = False
|
|
117
|
+
bConfiguredByCustomer: Optional[bool] = False
|
|
118
|
+
bProvidedByCustomer: Optional[bool] = False
|
|
119
|
+
bShared: Optional[bool] = False
|
|
120
|
+
bInheritedFedrampAuthorization: Optional[bool] = False
|
|
121
|
+
cloudImplementation: Optional[str] = None
|
|
122
|
+
customerImplementation: Optional[str] = None
|
|
123
|
+
controlSource: Optional[str] = "Baseline"
|
|
124
|
+
maturityLevel: Optional[str] = None
|
|
125
|
+
assessmentFrequency: int = 0
|
|
126
|
+
|
|
127
|
+
def __str__(self):
|
|
128
|
+
return f"Control Implementation {self.id}: {self.controlID}"
|
|
129
|
+
|
|
130
|
+
def model_post_init(self, __context: Any) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Model post init method
|
|
133
|
+
|
|
134
|
+
:param Any __context: The context
|
|
135
|
+
:return: None
|
|
136
|
+
"""
|
|
137
|
+
self.status_lst = self._get_status_enum()
|
|
138
|
+
|
|
139
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Override __setattr__ to update status_lst when status changes.
|
|
142
|
+
|
|
143
|
+
:param str name: The attribute name
|
|
144
|
+
:param Any value: The attribute value
|
|
145
|
+
:return: None
|
|
146
|
+
"""
|
|
147
|
+
super().__setattr__(name, value)
|
|
148
|
+
if name == "status":
|
|
149
|
+
self.status_lst = self._get_status_enum()
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def _get_additional_endpoints(cls) -> ConfigDict:
|
|
153
|
+
"""
|
|
154
|
+
Get additional endpoints for the API.
|
|
155
|
+
|
|
156
|
+
:return: A dictionary of additional endpoints
|
|
157
|
+
:rtype: ConfigDict
|
|
158
|
+
"""
|
|
159
|
+
return ConfigDict( # type: ignore
|
|
160
|
+
get_all_count="/api/{model_slug}/getAllCount",
|
|
161
|
+
get_filtered_list="/api/{model_slug}/getFilteredList/{str_find}",
|
|
162
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}/{strModule}",
|
|
163
|
+
get_all_by_plan="/api/{model_slug}/getAllByPlan/{int_security_plan}",
|
|
164
|
+
get_all_by_plan_with_controls="/api/{model_slug}/getAllByPlanWithControls/{int_security_plan}",
|
|
165
|
+
get_compliance_history_by_plan="/api/{model_slug}/GetComplianceHistoryByPlan/{int_parent}/{str_module}",
|
|
166
|
+
save_compliance_history_by_plan="/api/{model_slug}/SaveComplianceHistoryByPlan",
|
|
167
|
+
get_all_by_plan_with_objectives="/api/{model_slug}/getAllByPlanWithObjectives/{int_security_plan}",
|
|
168
|
+
get_all_by_component_list="/api/{model_slug}/getAllByComponentList",
|
|
169
|
+
get_mappings_by_security_plan="/api/{model_slug}/getMappingsBySecurityPlan/{int_security_plan}",
|
|
170
|
+
get_list_by_plan="/api/{model_slug}/getListByPlan/{int_security_plan}",
|
|
171
|
+
get_list_by_parent="/api/{model_slug}/getListByParent/{int_id}/{str_module}",
|
|
172
|
+
get_master_assessment_list="/api/{model_slug}/getMasterAssessmentList/{int_parent}/{str_module}",
|
|
173
|
+
get_list_by_parent_control="/api/{model_slug}/getListByParentControl/{parent_control_id}",
|
|
174
|
+
get_sc_list_by_plan="/api/{model_slug}/getSCListByPlan/{int_security_plan}",
|
|
175
|
+
get_inheritance_list_by_plan="/api/{model_slug}/getInheritanceListByPlan/{int_security_plan}",
|
|
176
|
+
get_sc_list_by_component="/api/{model_slug}/getSCListByComponent/{int_component}",
|
|
177
|
+
graph_main_dashboard="/api/{model_slug}/graphMainDashboard/{str_group_by}/{str_mod}",
|
|
178
|
+
export="/api/{model_slug}/export/{int_id}",
|
|
179
|
+
wizard="/api/{model_slug}/wizard/{int_id}/{str_module}",
|
|
180
|
+
get_date_last_assessed_by_parent="/api/{model_slug}/getDateLastAssessedByParent/{int_record}",
|
|
181
|
+
get_date_last_assessed_by_parent_and_module="/api/{model_slug}/getDateLastAssessedByParentAndModule/{str_module}/{int_record}",
|
|
182
|
+
get_date_last_assessed_for_all_assets="/api/{model_slug}/getDateLastAssessedForAllAssets/{int_record}",
|
|
183
|
+
graph_controls_by_date="/api/{model_slug}/graphControlsByDate/{year}",
|
|
184
|
+
get_date_last_assessed_by_control="/api/{model_slug}/getDateLastAssessedByControl/{int_control}",
|
|
185
|
+
get_by_status_and_parent="/api/{model_slug}/getByStatusAndParent/{int_id}",
|
|
186
|
+
get_by_status_and_parent_control="/api/{model_slug}/getByStatusAndParentControl/{int_id}",
|
|
187
|
+
get_by_owner_and_parent="/api/{model_slug}/getByOwnerAndParent/{int_id}",
|
|
188
|
+
get_by_owner_and_parent_control="/api/{model_slug}/getByOwnerAndParentControl/{int_id}",
|
|
189
|
+
get_by_result_and_parent="/api/{model_slug}/getByResultAndParent/{int_id}",
|
|
190
|
+
get_by_result_and_parent_control="/api/{model_slug}/getByResultAndParentControl/{int_id}",
|
|
191
|
+
get_by_process_and_parent="/api/{model_slug}/getByProcessAndParent/{int_id}",
|
|
192
|
+
get_by_practice_and_parent="/api/{model_slug}/getByPracticeAndParent/{int_id}",
|
|
193
|
+
get_by_practice_and_control="/api/{model_slug}/getByPracticeAndControl/{int_id}",
|
|
194
|
+
get_by_process_and_control="/api/{model_slug}/getByProcessAndControl/{int_id}",
|
|
195
|
+
graph="/api/{model_slug}/graph",
|
|
196
|
+
filter_control_implementations="/api/{model_slug}/filterControlImplementations",
|
|
197
|
+
filter_scorecard="/api/{model_slug}/filterScorecard",
|
|
198
|
+
scorecard_count="/api/{model_slug}/ScorecardCount",
|
|
199
|
+
query_by_custom_field="/api/{model_slug}/queryByCustomField/{str_field_name}/{str_value}",
|
|
200
|
+
insert="/api/controlImplementation",
|
|
201
|
+
batch_create="/api/{model_slug}/batchCreate",
|
|
202
|
+
batch_update="/api/{model_slug}/batchUpdate",
|
|
203
|
+
quick_update="/api/{model_slug}/quickUpdate/{id}/{str_status}/{int_weight}/{str_user}",
|
|
204
|
+
dashboard_by_parent="/api/{model_slug}/dashboardByParent/{str_group_by}/{int_id}/{str_module}",
|
|
205
|
+
security_control_dashboard="/api/{model_slug}/securityControlDashboard/{str_group_by}/{int_id}",
|
|
206
|
+
dashboard_by_parent_and_catalogue="/api/{model_slug}/dashboardByParentAndCatalogue/{str_group_by}/{int_id}/{int_cat_id}",
|
|
207
|
+
group_by_family="/api/{model_slug}/groupByFamily/{int_security_plan}",
|
|
208
|
+
dashboard_by_sp="/api/{model_slug}/dashboardBySP/{str_group_by}/{int_security_plan}",
|
|
209
|
+
report="/api/{model_slug}/report/{str_report}",
|
|
210
|
+
get_by_parent="/api/{model_slug}/getByParent/{int_id}/{str_module}",
|
|
211
|
+
get_count_by_parent="/api/{model_slug}/getCountByParent/{int_id}/{str_module}",
|
|
212
|
+
get_all_asset_controls_by_component="/api/{model_slug}/getAllAssetControlsByComponent/{int_id}",
|
|
213
|
+
drilldown_asset_controls_by_component="/api/{model_slug}/drilldownAssetControlsByComponent/{component_id}/{str_field}/{str_value}",
|
|
214
|
+
get_control_context="/api/{model_slug}/getControlContext/{int_control_id}/{int_parent_id}/{str_module}",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def find_by_unique(self, **kwargs: dict) -> Optional["ControlImplementation"]:
|
|
218
|
+
"""
|
|
219
|
+
Find an object by unique query.
|
|
220
|
+
|
|
221
|
+
:param dict **kwargs: The unique query parameters
|
|
222
|
+
:return: The object or None if not found
|
|
223
|
+
:rtype: Optional[ControlImplementation]
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
for instance in self.get_by_security_control_id(security_control_id=self.controlID):
|
|
227
|
+
return instance
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
def _get_status_enum(self) -> List["ControlImplementationStatus"]:
|
|
231
|
+
"""
|
|
232
|
+
A method to pull the RegScale multiselect status as a list of ControlImplementationStatus.
|
|
233
|
+
|
|
234
|
+
:return: A list of control implementation status
|
|
235
|
+
:rtype: List["ControlImplementationStatus"]
|
|
236
|
+
"""
|
|
237
|
+
if not self.status:
|
|
238
|
+
return []
|
|
239
|
+
try:
|
|
240
|
+
return [ControlImplementationStatus(status.strip()) for status in self.status.split(",")]
|
|
241
|
+
except ValueError:
|
|
242
|
+
return []
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def get_by_security_control_id(cls, security_control_id: int) -> List["ControlImplementation"]:
|
|
246
|
+
"""
|
|
247
|
+
Get a list of control implementations by security control ID.
|
|
248
|
+
|
|
249
|
+
:param int security_control_id: The ID of the security control
|
|
250
|
+
:return: A list of control implementations
|
|
251
|
+
:rtype: List[ControlImplementation]
|
|
252
|
+
"""
|
|
253
|
+
response = cls._get_api_handler().get(
|
|
254
|
+
endpoint=cls.get_endpoint("get_by_security_control_id").format(int_security_plan=security_control_id)
|
|
255
|
+
)
|
|
256
|
+
security_controls = []
|
|
257
|
+
if response and response.ok:
|
|
258
|
+
for ci in response.json():
|
|
259
|
+
if ci := cls.get_object(object_id=ci["id"]):
|
|
260
|
+
security_controls.append(ci)
|
|
261
|
+
return security_controls
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def get_list_by_plan(cls, plan_id: int) -> List["ControlImplementation"]:
|
|
265
|
+
"""
|
|
266
|
+
Get a list of control implementations by plan ID.
|
|
267
|
+
|
|
268
|
+
:param int plan_id: The ID of the plan
|
|
269
|
+
:return: A list of control implementations
|
|
270
|
+
:rtype: List[ControlImplementation]
|
|
271
|
+
"""
|
|
272
|
+
response = cls._get_api_handler().get(
|
|
273
|
+
endpoint=cls.get_endpoint("get_list_by_plan").format(int_security_plan=plan_id)
|
|
274
|
+
)
|
|
275
|
+
security_controls = []
|
|
276
|
+
if response and response.ok:
|
|
277
|
+
for ci in response.json():
|
|
278
|
+
if ci := cls.get_object(object_id=ci["id"]):
|
|
279
|
+
security_controls.append(ci)
|
|
280
|
+
return security_controls
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def get_control_label_map_by_plan(cls, plan_id: int) -> Dict[str, int]:
|
|
284
|
+
"""
|
|
285
|
+
Get a map of control labels to control implementations by plan ID.
|
|
286
|
+
|
|
287
|
+
:param int plan_id: The ID of the plan
|
|
288
|
+
:return: A dictionary mapping control IDs to implementation IDs
|
|
289
|
+
:rtype: Dict[str, int]
|
|
290
|
+
"""
|
|
291
|
+
logger.info("Getting control label map by plan...")
|
|
292
|
+
response = cls._get_api_handler().get(
|
|
293
|
+
endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
|
|
294
|
+
)
|
|
295
|
+
if response and response.ok:
|
|
296
|
+
logger.info("Fetched control label map by plan successfully.")
|
|
297
|
+
return {parentheses_to_dot(ci["control"]["controlId"]): ci["id"] for ci in response.json()}
|
|
298
|
+
logger.info("Unable to get control label map by plan.")
|
|
299
|
+
return {}
|
|
300
|
+
|
|
301
|
+
@classmethod
|
|
302
|
+
def fetch_implementation_ids_by_cci(cls, parent_id: int, cci_name: str, skip: int = 0, take: int = 50) -> list[int]:
|
|
303
|
+
"""
|
|
304
|
+
Fetch control implementation ids by CCI.
|
|
305
|
+
|
|
306
|
+
:param int parent_id: The ID of the parent
|
|
307
|
+
:param str cci_name: The name of the CCI
|
|
308
|
+
:param int skip: The number of items to skip
|
|
309
|
+
:param int take: The number of items to take
|
|
310
|
+
:return: A list of control implementation IDs
|
|
311
|
+
:rtype: list[int]
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
query = f"""
|
|
315
|
+
query GetControlImplementations() {{
|
|
316
|
+
controlImplementations(
|
|
317
|
+
skip: {skip}, take: {take}, where: {{
|
|
318
|
+
parentId: {{eq: {parent_id}}},
|
|
319
|
+
control: {{
|
|
320
|
+
controlObjectives: {{
|
|
321
|
+
some: {{
|
|
322
|
+
otherId: {{
|
|
323
|
+
contains: "{cci_name}"
|
|
324
|
+
}}
|
|
325
|
+
}}
|
|
326
|
+
}}
|
|
327
|
+
}}
|
|
328
|
+
}}
|
|
329
|
+
) {{
|
|
330
|
+
items {{
|
|
331
|
+
id
|
|
332
|
+
}}
|
|
333
|
+
pageInfo {{
|
|
334
|
+
hasNextPage
|
|
335
|
+
}}
|
|
336
|
+
totalCount
|
|
337
|
+
}}
|
|
338
|
+
}}
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
response = cls._get_api_handler().graph(query)
|
|
342
|
+
if "controlImplementations" in response:
|
|
343
|
+
return [item["id"] for item in response["controlImplementations"]["items"]]
|
|
344
|
+
return []
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def get_control_id_map_by_plan(cls, plan_id: int) -> Dict[int, int]:
|
|
348
|
+
"""
|
|
349
|
+
Get a map of control ids to control implementations by plan ID.
|
|
350
|
+
|
|
351
|
+
:param int plan_id: The ID of the plan
|
|
352
|
+
:return: A dictionary mapping control IDs to implementation IDs
|
|
353
|
+
:rtype: Dict[int, int]
|
|
354
|
+
"""
|
|
355
|
+
logger.info("Getting control id map by plan...")
|
|
356
|
+
response = cls._get_api_handler().get(
|
|
357
|
+
endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
|
|
358
|
+
)
|
|
359
|
+
if response and response.ok:
|
|
360
|
+
logger.info("Fetched control id map by plan successfully.")
|
|
361
|
+
return {ci["control"]["id"]: ci["id"] for ci in response.json()}
|
|
362
|
+
logger.info("Unable to get control id map by plan.")
|
|
363
|
+
return {}
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def post_implementation(
|
|
367
|
+
app: Application, implementation: "ControlImplementation"
|
|
368
|
+
) -> Union[requests.Response, Dict]:
|
|
369
|
+
"""
|
|
370
|
+
Post a control implementation to RegScale via API
|
|
371
|
+
|
|
372
|
+
:param Application app:
|
|
373
|
+
:param ControlImplementation implementation:
|
|
374
|
+
:return: Response from RegScale API or the response if the response is not ok
|
|
375
|
+
:rtype: Union[requests.Response, Dict]
|
|
376
|
+
"""
|
|
377
|
+
api = Api()
|
|
378
|
+
headers = {
|
|
379
|
+
"accept": "*/*",
|
|
380
|
+
"Authorization": app.config["token"],
|
|
381
|
+
"Content-Type": PATCH_CONTENT_TYPE,
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
res = api.post(
|
|
385
|
+
app.config["domain"] + "/api/controlimplementation",
|
|
386
|
+
headers=headers,
|
|
387
|
+
json=implementation.dict(),
|
|
388
|
+
)
|
|
389
|
+
if not res.raise_for_status() and res.status_code == 200:
|
|
390
|
+
return res.json()
|
|
391
|
+
else:
|
|
392
|
+
return res
|
|
393
|
+
|
|
394
|
+
@staticmethod
|
|
395
|
+
def update(app: Application, implementation: "ControlImplementation") -> Union[requests.Response, Dict]:
|
|
396
|
+
"""
|
|
397
|
+
Update Method for ControlImplementation
|
|
398
|
+
|
|
399
|
+
:param Application app: Application instance
|
|
400
|
+
:param ControlImplementation implementation: ControlImplementation instance
|
|
401
|
+
:return: A control implementation dict or a response object
|
|
402
|
+
:rtype: Union[requests.Response, Dict]
|
|
403
|
+
"""
|
|
404
|
+
api = Api()
|
|
405
|
+
|
|
406
|
+
res = api.put(
|
|
407
|
+
app.config["domain"] + f"/api/controlimplementation/{implementation.id}",
|
|
408
|
+
json=implementation.dict(),
|
|
409
|
+
)
|
|
410
|
+
if not res.raise_for_status() and res.status_code == 200:
|
|
411
|
+
return res.json()
|
|
412
|
+
else:
|
|
413
|
+
return res
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def fetch_existing_implementations(app: Application, regscale_parent_id: int, regscale_module: str) -> List[Dict]:
|
|
417
|
+
"""
|
|
418
|
+
Fetch existing implementations for the provided id and module from RegScale
|
|
419
|
+
|
|
420
|
+
:param Application app: Application instance
|
|
421
|
+
:param int regscale_parent_id: RegScale Parent ID
|
|
422
|
+
:param str regscale_module: RegScale Parent Module
|
|
423
|
+
:return: list of existing implementations
|
|
424
|
+
:rtype: List[Dict]
|
|
425
|
+
"""
|
|
426
|
+
api = Api()
|
|
427
|
+
existing_implementations = []
|
|
428
|
+
existing_implementations_response = api.get(
|
|
429
|
+
url=app.config["domain"]
|
|
430
|
+
+ "/api/controlimplementation"
|
|
431
|
+
+ f"/getAllByParent/{regscale_parent_id}/{regscale_module}"
|
|
432
|
+
)
|
|
433
|
+
if existing_implementations_response.ok:
|
|
434
|
+
existing_implementations = existing_implementations_response.json()
|
|
435
|
+
return existing_implementations
|
|
436
|
+
|
|
437
|
+
@staticmethod
|
|
438
|
+
def _extract_text_and_log(
|
|
439
|
+
element: Element, imp: "ControlImplementation", debug_logger: logging.Logger
|
|
440
|
+
) -> Optional[str]:
|
|
441
|
+
"""
|
|
442
|
+
Extracts and logs text from an XML element.
|
|
443
|
+
|
|
444
|
+
:param Element element: The XML element.
|
|
445
|
+
:param ControlImplementation imp: The control implementation instance.
|
|
446
|
+
:param logging.Logger debug_logger: Logger for debugging.
|
|
447
|
+
:return: Stripped text from the element.
|
|
448
|
+
:rtype: Optional[str]
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
text = element.text.strip() if element.text else None
|
|
452
|
+
if text:
|
|
453
|
+
debug_logger.debug("Text: %s", text)
|
|
454
|
+
imp.implementation = text
|
|
455
|
+
return text
|
|
456
|
+
|
|
457
|
+
@staticmethod
|
|
458
|
+
def _update_implementation_status(element: Element, imp: "ControlImplementation") -> None:
|
|
459
|
+
"""
|
|
460
|
+
Updates the implementation statuses and control origin of the control based on the element.
|
|
461
|
+
|
|
462
|
+
:param Element element: The XML element.
|
|
463
|
+
:param ControlImplementation imp: The control implementation instance.
|
|
464
|
+
:rtype: None
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
def update_status_from_value(value: str, status_map: Dict[str, tuple]) -> Optional[tuple]:
|
|
468
|
+
"""
|
|
469
|
+
Updates the implementation status based on the value
|
|
470
|
+
|
|
471
|
+
:param str value: The value to update the status from
|
|
472
|
+
:param Dict[str, tuple] status_map: The status mapping
|
|
473
|
+
:return: The status and the flag attribute
|
|
474
|
+
:rtype: Optional[tuple]
|
|
475
|
+
"""
|
|
476
|
+
if value in status_map:
|
|
477
|
+
status, flag_attr = status_map[value]
|
|
478
|
+
setattr(imp, flag_attr, True)
|
|
479
|
+
return status
|
|
480
|
+
logger.warning(f"Invalid value: {value}")
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
status_mapping = {
|
|
484
|
+
"implemented": (ControlImplementationStatus.FullyImplemented, "bStatusImplemented"),
|
|
485
|
+
"partial": (ControlImplementationStatus.PartiallyImplemented, "bStatusPartiallyImplemented"),
|
|
486
|
+
"not-applicable": (ControlImplementationStatus.NA, "bStatusNotApplicable"),
|
|
487
|
+
"planned": (ControlImplementationStatus.Planned, "bStatusPlanned"),
|
|
488
|
+
"alternative": (ControlImplementationStatus.Alternative, "bStatusAlternative"),
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
responsibility_mapping = {
|
|
492
|
+
"sp-corporate": (ControlImplementationOrigin.Provider, "bServiceProviderCorporate"),
|
|
493
|
+
"sp-system": (ControlImplementationOrigin.ProviderSS, "bServiceProviderSystemSpecific"),
|
|
494
|
+
"customer-configured": (ControlImplementationOrigin.CustomerConfigured, "bConfiguredByCustomer"),
|
|
495
|
+
"customer-provided": (ControlImplementationOrigin.CustomerProvided, "bProvidedByCustomer"),
|
|
496
|
+
"inherited": (ControlImplementationOrigin.Inherited, "bInherited"),
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if "name" in element.attrib:
|
|
500
|
+
if element.attrib["name"] == "implementation-status":
|
|
501
|
+
imp.status = update_status_from_value(element.attrib.get("value"), status_mapping)
|
|
502
|
+
|
|
503
|
+
if element.attrib["name"] == "control-origination":
|
|
504
|
+
imp.responsibility = update_status_from_value(element.attrib.get("value"), responsibility_mapping)
|
|
505
|
+
|
|
506
|
+
@staticmethod
|
|
507
|
+
def from_oscal_element(app: Application, obj: Element, control: dict) -> "ControlImplementation":
|
|
508
|
+
"""
|
|
509
|
+
Create RegScale ControlImplementation from XML element.
|
|
510
|
+
|
|
511
|
+
:param Application app: RegScale CLI Application object.
|
|
512
|
+
:param Element obj: Element object.
|
|
513
|
+
:param dict control: Control dictionary.
|
|
514
|
+
:return: ControlImplementation class.
|
|
515
|
+
:rtype: ControlImplementation
|
|
516
|
+
"""
|
|
517
|
+
user = app.config["userId"]
|
|
518
|
+
imp = ControlImplementation(controlOwnerId=user, status="notimplemented", controlID=control["id"])
|
|
519
|
+
|
|
520
|
+
for element in obj.iter():
|
|
521
|
+
ControlImplementation._extract_text_and_log(element, imp, logger)
|
|
522
|
+
|
|
523
|
+
# This try catch is tied to modification to catalogs object returned by above API call
|
|
524
|
+
# The otherId field is to be added to new OSCAL catalogs which will be migrated for existing customers.
|
|
525
|
+
# If otherId exists use it to match to control otherwise use original controlId
|
|
526
|
+
# Handle case where otherId does not exist in catalog object and do not throw an error
|
|
527
|
+
|
|
528
|
+
# if otherid exists in catalog object make sure it has something in it before matching
|
|
529
|
+
# this case may exist while new catalogs are being migrated to for customers
|
|
530
|
+
if len(control.get("otherId", [])) > 0:
|
|
531
|
+
imp.control = control["otherId"]
|
|
532
|
+
else:
|
|
533
|
+
logger.debug("Warning: OtherId (machine readable) not found on record.")
|
|
534
|
+
imp.control = control["controlId"]
|
|
535
|
+
|
|
536
|
+
for name, value in element.attrib.items():
|
|
537
|
+
logger.debug(f"Property: {name}, Value: {value}")
|
|
538
|
+
ControlImplementation._update_implementation_status(element, imp)
|
|
539
|
+
|
|
540
|
+
return imp
|
|
541
|
+
|
|
542
|
+
@staticmethod
|
|
543
|
+
def from_dict(obj: Any) -> "ControlImplementation":
|
|
544
|
+
"""
|
|
545
|
+
Create ControlImplementation from dictionary
|
|
546
|
+
|
|
547
|
+
:param Any obj: Object to create ControlImplementation from
|
|
548
|
+
:return: ControlImplementation class
|
|
549
|
+
:rtype: ControlImplementation
|
|
550
|
+
"""
|
|
551
|
+
if "id" in obj:
|
|
552
|
+
del obj["id"]
|
|
553
|
+
return ControlImplementation(**obj)
|
|
554
|
+
|
|
555
|
+
def __hash__(self) -> int:
|
|
556
|
+
"""
|
|
557
|
+
Hash function for ControlImplementation
|
|
558
|
+
|
|
559
|
+
:return: Hash of ControlImplementation
|
|
560
|
+
:rtype: int
|
|
561
|
+
"""
|
|
562
|
+
return hash(
|
|
563
|
+
(
|
|
564
|
+
self.controlID,
|
|
565
|
+
self.controlOwnerId,
|
|
566
|
+
self.status,
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
@staticmethod
|
|
571
|
+
def post_batch_implementation(
|
|
572
|
+
app: Application, implementations: List[Dict]
|
|
573
|
+
) -> Optional[Union[requests.Response, Dict]]:
|
|
574
|
+
"""
|
|
575
|
+
Post a batch of control implementations to the RegScale API
|
|
576
|
+
|
|
577
|
+
:param Application app: RegScale CLI Application object
|
|
578
|
+
:param List[Dict] implementations: list of control implementations to post to RegScale
|
|
579
|
+
:return: Response from RegScale API or the response content if the response is not ok
|
|
580
|
+
:rtype: Optional[Union[requests.Response, Dict]]
|
|
581
|
+
"""
|
|
582
|
+
if len(implementations) > 0:
|
|
583
|
+
api = Api()
|
|
584
|
+
headers = {
|
|
585
|
+
"accept": "*/*",
|
|
586
|
+
"Authorization": app.config["token"],
|
|
587
|
+
"Content-Type": PATCH_CONTENT_TYPE,
|
|
588
|
+
}
|
|
589
|
+
res = api.post(
|
|
590
|
+
url=urljoin(app.config["domain"], "/api/controlImplementation/batchCreate"),
|
|
591
|
+
json=implementations,
|
|
592
|
+
headers=headers,
|
|
593
|
+
)
|
|
594
|
+
if not res.raise_for_status() and res.status_code == 200:
|
|
595
|
+
app.logger.info(f"Created {len(implementations)} Control Implementations, Successfully!")
|
|
596
|
+
return res.json()
|
|
597
|
+
else:
|
|
598
|
+
return res
|
|
599
|
+
|
|
600
|
+
@staticmethod
|
|
601
|
+
def put_batch_implementation(
|
|
602
|
+
app: Application, implementations: List[Dict]
|
|
603
|
+
) -> Optional[Union[requests.Response, Dict]]:
|
|
604
|
+
"""
|
|
605
|
+
Put a batch of control implementations to the RegScale API
|
|
606
|
+
|
|
607
|
+
:param Application app: RegScale CLI Application object
|
|
608
|
+
:param List[Dict] implementations: list of control implementations to post to RegScale
|
|
609
|
+
:return: Response from RegScale API or the response content if the response is not ok
|
|
610
|
+
:rtype: Optional[Union[requests.Response, Dict]]
|
|
611
|
+
"""
|
|
612
|
+
if len(implementations) > 0:
|
|
613
|
+
api = Api()
|
|
614
|
+
headers = {
|
|
615
|
+
"accept": "*/*",
|
|
616
|
+
"Authorization": app.config["token"],
|
|
617
|
+
"Content-Type": PATCH_CONTENT_TYPE,
|
|
618
|
+
}
|
|
619
|
+
res = api.post(
|
|
620
|
+
url=urljoin(app.config["domain"], "/api/controlImplementation/batchUpdate"),
|
|
621
|
+
json=implementations,
|
|
622
|
+
headers=headers,
|
|
623
|
+
)
|
|
624
|
+
if not res.raise_for_status() and res.status_code == 200:
|
|
625
|
+
app.logger.info(f"Updated {len(implementations)} Control Implementations, Successfully!")
|
|
626
|
+
return res.json()
|
|
627
|
+
else:
|
|
628
|
+
return res
|
|
629
|
+
|
|
630
|
+
@staticmethod
|
|
631
|
+
def get_existing_control_implementations(parent_id: int) -> Dict:
|
|
632
|
+
"""
|
|
633
|
+
Fetch existing control implementations as dict with control id as the key used for
|
|
634
|
+
automating control implementation creation
|
|
635
|
+
|
|
636
|
+
:param int parent_id: parent control id
|
|
637
|
+
:return: Dictionary of existing control implementations
|
|
638
|
+
:rtype: Dict
|
|
639
|
+
"""
|
|
640
|
+
app = Application()
|
|
641
|
+
api = Api()
|
|
642
|
+
domain = app.config.get("domain")
|
|
643
|
+
existing_implementation_dict = {}
|
|
644
|
+
get_url = urljoin(domain, f"/api/controlImplementation/getAllByPlan/{parent_id}")
|
|
645
|
+
response = api.get(get_url)
|
|
646
|
+
if response.ok:
|
|
647
|
+
existing_control_implementations_json = response.json()
|
|
648
|
+
for cim in existing_control_implementations_json:
|
|
649
|
+
existing_implementation_dict[cim.get("controlName")] = cim
|
|
650
|
+
logger.info(f"Found {len(existing_implementation_dict)} existing control implementations")
|
|
651
|
+
elif response.status_code == 404:
|
|
652
|
+
logger.info(f"No existing control implementations found for {parent_id}")
|
|
653
|
+
else:
|
|
654
|
+
logger.warning(f"Unable to get existing control implementations. {response.content}")
|
|
655
|
+
return existing_implementation_dict
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def create_control_implementations(
|
|
659
|
+
cls,
|
|
660
|
+
controls: list,
|
|
661
|
+
parent_id: int,
|
|
662
|
+
parent_module: str,
|
|
663
|
+
existing_implementation_dict: dict,
|
|
664
|
+
full_controls: dict,
|
|
665
|
+
partial_controls: dict,
|
|
666
|
+
failing_controls: dict,
|
|
667
|
+
include_not_implemented: Optional[bool] = False,
|
|
668
|
+
) -> None:
|
|
669
|
+
"""
|
|
670
|
+
Creates and updates control implementations based on given controls
|
|
671
|
+
|
|
672
|
+
:param list controls: List of control details
|
|
673
|
+
:param int parent_id: Identifier for the parent control
|
|
674
|
+
:param str parent_module: Name of the parent module
|
|
675
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
676
|
+
:param dict full_controls: Dictionary of fully implemented controls
|
|
677
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
678
|
+
:param dict failing_controls: Dictionary of failing controls
|
|
679
|
+
:param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
|
|
680
|
+
:rtype: None
|
|
681
|
+
"""
|
|
682
|
+
app = Application()
|
|
683
|
+
user_id = app.config.get("userId")
|
|
684
|
+
|
|
685
|
+
to_create, to_update = cls.process_controls(
|
|
686
|
+
controls,
|
|
687
|
+
parent_id,
|
|
688
|
+
parent_module,
|
|
689
|
+
existing_implementation_dict,
|
|
690
|
+
full_controls,
|
|
691
|
+
partial_controls,
|
|
692
|
+
failing_controls,
|
|
693
|
+
user_id,
|
|
694
|
+
include_not_implemented,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
cls.post_batch_if_needed(app, to_create, ControlImplementation.post_batch_implementation)
|
|
698
|
+
cls.put_batch_if_needed(app, to_update, ControlImplementation.put_batch_implementation)
|
|
699
|
+
|
|
700
|
+
@classmethod
|
|
701
|
+
def process_controls(
|
|
702
|
+
cls,
|
|
703
|
+
controls: list,
|
|
704
|
+
parent_id: int,
|
|
705
|
+
parent_module: str,
|
|
706
|
+
existing_implementation_dict: dict,
|
|
707
|
+
full_controls: dict,
|
|
708
|
+
partial_controls: dict,
|
|
709
|
+
failing_controls: dict,
|
|
710
|
+
user_id: Optional[str] = None,
|
|
711
|
+
include_not_implemented: Optional[bool] = False,
|
|
712
|
+
) -> tuple[list, list]:
|
|
713
|
+
"""
|
|
714
|
+
Processes each control for creation or update
|
|
715
|
+
|
|
716
|
+
:param list controls: List of control details
|
|
717
|
+
:param int parent_id: Identifier for the parent control
|
|
718
|
+
:param str parent_module: Name of the parent module
|
|
719
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
720
|
+
:param dict full_controls: Dictionary of fully implemented controls
|
|
721
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
722
|
+
:param dict failing_controls: Dictionary of failing controls
|
|
723
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
724
|
+
:param Optional[bool] include_not_implemented: Whether to include not implemented controls, defaults to False
|
|
725
|
+
:return: Tuple containing lists of controls to create and update
|
|
726
|
+
:rtype: tuple[list, list]
|
|
727
|
+
"""
|
|
728
|
+
to_create = []
|
|
729
|
+
to_update = []
|
|
730
|
+
|
|
731
|
+
for control in controls:
|
|
732
|
+
# if otherid exists in catalog object make sure it has something in it before matching
|
|
733
|
+
# this case may exist while new catalogs are being migrated to for customers
|
|
734
|
+
if len(control.get("otherId", [])) > 0:
|
|
735
|
+
lower_case_control_id = control["otherId"].lower()
|
|
736
|
+
else:
|
|
737
|
+
lower_case_control_id = control["controlId"].lower()
|
|
738
|
+
|
|
739
|
+
status = cls.check_implementation(full_controls, partial_controls, failing_controls, lower_case_control_id)
|
|
740
|
+
if not include_not_implemented and status == ControlImplementationStatus.NotImplemented.value:
|
|
741
|
+
continue
|
|
742
|
+
|
|
743
|
+
if len(control.get("otherId", [])) > 0:
|
|
744
|
+
controlid = control["otherId"]
|
|
745
|
+
else:
|
|
746
|
+
controlid = control["controlId"]
|
|
747
|
+
|
|
748
|
+
if controlid not in existing_implementation_dict:
|
|
749
|
+
cim = cls.create_new_control_implementation(control, parent_id, parent_module, status, user_id)
|
|
750
|
+
to_create.append(cim)
|
|
751
|
+
else:
|
|
752
|
+
cls.update_existing_control_implementation(
|
|
753
|
+
control, existing_implementation_dict, status, to_update, user_id
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
return to_create, to_update
|
|
757
|
+
|
|
758
|
+
@staticmethod
|
|
759
|
+
def create_new_control_implementation(
|
|
760
|
+
control: dict,
|
|
761
|
+
parent_id: int,
|
|
762
|
+
parent_module: str,
|
|
763
|
+
status: str,
|
|
764
|
+
user_id: Optional[str] = None,
|
|
765
|
+
) -> "ControlImplementation":
|
|
766
|
+
"""
|
|
767
|
+
Creates a new control implementation object
|
|
768
|
+
|
|
769
|
+
:param dict control: Control details
|
|
770
|
+
:param int parent_id: Identifier for the parent control
|
|
771
|
+
:param str parent_module: Name of the parent module
|
|
772
|
+
:param str status: Status of the control implementation
|
|
773
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
774
|
+
:return: New control implementation object
|
|
775
|
+
:rtype: ControlImplementation
|
|
776
|
+
"""
|
|
777
|
+
cim = ControlImplementation(
|
|
778
|
+
controlOwnerId=user_id,
|
|
779
|
+
dateLastAssessed=get_current_datetime(),
|
|
780
|
+
implementation=control.get("implementation", None),
|
|
781
|
+
status=status,
|
|
782
|
+
controlID=control["id"],
|
|
783
|
+
parentId=parent_id,
|
|
784
|
+
parentModule=parent_module,
|
|
785
|
+
createdById=user_id,
|
|
786
|
+
dateCreated=get_current_datetime(),
|
|
787
|
+
lastUpdatedById=user_id,
|
|
788
|
+
dateLastUpdated=get_current_datetime(),
|
|
789
|
+
).dict()
|
|
790
|
+
cim["controlSource"] = "Baseline"
|
|
791
|
+
return cim
|
|
792
|
+
|
|
793
|
+
@classmethod
|
|
794
|
+
def update_existing_control_implementation(
|
|
795
|
+
cls,
|
|
796
|
+
control: dict,
|
|
797
|
+
existing_implementation_dict: dict,
|
|
798
|
+
status: str,
|
|
799
|
+
to_update: list,
|
|
800
|
+
user_id: Optional[str] = None,
|
|
801
|
+
):
|
|
802
|
+
"""
|
|
803
|
+
Updates an existing control implementation
|
|
804
|
+
|
|
805
|
+
:param dict control: Control details
|
|
806
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
807
|
+
:param str status: Status of the control implementation
|
|
808
|
+
:param list to_update: List of controls to update
|
|
809
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
810
|
+
"""
|
|
811
|
+
existing_imp = existing_implementation_dict[control["controlId"]]
|
|
812
|
+
existing_imp.update(
|
|
813
|
+
{
|
|
814
|
+
"implementation": control.get("implementation"),
|
|
815
|
+
"status": status,
|
|
816
|
+
"dateLastAssessed": get_current_datetime(),
|
|
817
|
+
"lastUpdatedById": user_id,
|
|
818
|
+
"dateLastUpdated": get_current_datetime(),
|
|
819
|
+
}
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
remove_keys(existing_imp, ["createdBy", "systemRole", "controlOwner", "lastUpdatedBy"])
|
|
823
|
+
|
|
824
|
+
if existing_imp not in to_update:
|
|
825
|
+
to_update.append(existing_imp)
|
|
826
|
+
|
|
827
|
+
@staticmethod
|
|
828
|
+
def post_batch_if_needed(
|
|
829
|
+
app: Application,
|
|
830
|
+
to_create: list,
|
|
831
|
+
post_function: Callable[[Application, list], None],
|
|
832
|
+
) -> None:
|
|
833
|
+
"""
|
|
834
|
+
Posts a batch of new implementations if the list is not empty
|
|
835
|
+
|
|
836
|
+
:param Application app: RegScale CLI application object
|
|
837
|
+
:param list to_create: List of new implementations to post
|
|
838
|
+
:param Callable[[Application, list], None] post_function: The function to call for posting the batch, if needed
|
|
839
|
+
:rtype: None
|
|
840
|
+
"""
|
|
841
|
+
if to_create:
|
|
842
|
+
post_function(app, to_create)
|
|
843
|
+
|
|
844
|
+
@staticmethod
|
|
845
|
+
def put_batch_if_needed(
|
|
846
|
+
app: Application,
|
|
847
|
+
to_update: list,
|
|
848
|
+
put_function: Callable[[Application, list], None],
|
|
849
|
+
) -> None:
|
|
850
|
+
"""
|
|
851
|
+
Puts a batch of updated implementations if the list is not empty
|
|
852
|
+
|
|
853
|
+
:param Application app: RegScale CLI application object
|
|
854
|
+
:param list to_update: List of implementations to update
|
|
855
|
+
:param Callable[[Application, list], None] put_function: The function to call for putting the batch, if needed
|
|
856
|
+
"""
|
|
857
|
+
if to_update:
|
|
858
|
+
put_function(app, to_update)
|
|
859
|
+
|
|
860
|
+
@staticmethod
|
|
861
|
+
def check_implementation(
|
|
862
|
+
full_controls: dict,
|
|
863
|
+
partial_controls: dict,
|
|
864
|
+
failing_controls: dict,
|
|
865
|
+
control_id: str,
|
|
866
|
+
) -> str:
|
|
867
|
+
"""
|
|
868
|
+
Checks the status of a control implementation
|
|
869
|
+
|
|
870
|
+
:param dict full_controls: Dictionary of passing controls
|
|
871
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
872
|
+
:param dict failing_controls: Dictionary of failing control implementations
|
|
873
|
+
:param str control_id: control id
|
|
874
|
+
:return: status of control implementation
|
|
875
|
+
:rtype: str
|
|
876
|
+
"""
|
|
877
|
+
if control_id in full_controls.keys():
|
|
878
|
+
logger.debug(f"Found fully implemented control: {control_id}")
|
|
879
|
+
return ControlImplementationStatus.FullyImplemented.value
|
|
880
|
+
elif control_id in partial_controls.keys():
|
|
881
|
+
logger.debug(f"Found partially implemented control: {control_id}")
|
|
882
|
+
return ControlImplementationStatus.PartiallyImplemented.value
|
|
883
|
+
elif control_id in failing_controls.keys():
|
|
884
|
+
logger.debug(f"Found failing control: {control_id}")
|
|
885
|
+
return ControlImplementationStatus.InRemediation.value
|
|
886
|
+
else:
|
|
887
|
+
logger.debug(f"Found not implemented control: {control_id}")
|
|
888
|
+
return ControlImplementationStatus.NotImplemented.value
|
|
889
|
+
|
|
890
|
+
@classmethod
|
|
891
|
+
def get_sort_position_dict(cls) -> dict:
|
|
892
|
+
"""
|
|
893
|
+
Overrides the base method.
|
|
894
|
+
|
|
895
|
+
:return: dict The sort position in the list of properties
|
|
896
|
+
:rtype: dict
|
|
897
|
+
"""
|
|
898
|
+
return {
|
|
899
|
+
"id": 1,
|
|
900
|
+
"controlOwnerId": 2,
|
|
901
|
+
"status": 3,
|
|
902
|
+
"controlID": 4,
|
|
903
|
+
"parentId": 5,
|
|
904
|
+
"parentModule": 6,
|
|
905
|
+
"control": 7,
|
|
906
|
+
"controlName": 8,
|
|
907
|
+
"controlTitle": 9,
|
|
908
|
+
"description": 10,
|
|
909
|
+
"createdById": -1,
|
|
910
|
+
"uuid": -1,
|
|
911
|
+
"policy": 11,
|
|
912
|
+
"implementation": 12,
|
|
913
|
+
"dateLastAssessed": 13,
|
|
914
|
+
"lastAssessmentResult": 14,
|
|
915
|
+
"practiceLevel": 15,
|
|
916
|
+
"processLevel": 16,
|
|
917
|
+
"cyberFunction": 17,
|
|
918
|
+
"implementationType": 18,
|
|
919
|
+
"implementationMethod": 19,
|
|
920
|
+
"qdWellDesigned": 20,
|
|
921
|
+
"qdProcedures": 21,
|
|
922
|
+
"qdSegregation": 22,
|
|
923
|
+
"qdFlowdown": 23,
|
|
924
|
+
"qdAutomated": 24,
|
|
925
|
+
"qdOverall": 25,
|
|
926
|
+
"qiResources": 26,
|
|
927
|
+
"qiMaturity": 27,
|
|
928
|
+
"qiReporting": 28,
|
|
929
|
+
"qiVendorCompliance": 29,
|
|
930
|
+
"qiIssues": 30,
|
|
931
|
+
"qiOverall": 31,
|
|
932
|
+
"responsibility": 32,
|
|
933
|
+
"inheritedControlId": 33,
|
|
934
|
+
"inheritedRequirementId": 34,
|
|
935
|
+
"inheritedSecurityPlanId": 35,
|
|
936
|
+
"inheritedPolicyId": 36,
|
|
937
|
+
"dateCreated": -1,
|
|
938
|
+
"lastUpdatedById": -1,
|
|
939
|
+
"dateLastUpdated": -1,
|
|
940
|
+
"weight": 37,
|
|
941
|
+
"isPublic": -1,
|
|
942
|
+
"inheritable": 38,
|
|
943
|
+
"systemRoleId": 39,
|
|
944
|
+
"plannedImplementationDate": 40,
|
|
945
|
+
"stepsToImplement": 41,
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
@classmethod
|
|
949
|
+
def get_enum_values(cls, field_name: str) -> list:
|
|
950
|
+
"""
|
|
951
|
+
Overrides the base method.
|
|
952
|
+
|
|
953
|
+
:param str field_name: The property name to provide enum values for
|
|
954
|
+
:return: list of strings
|
|
955
|
+
:rtype: list
|
|
956
|
+
"""
|
|
957
|
+
if field_name == "status":
|
|
958
|
+
return [imp_status.value for imp_status in ControlImplementationStatus]
|
|
959
|
+
if field_name == "responsibility":
|
|
960
|
+
return ["Provider", "Customer", "Shared", "Not Applicable"]
|
|
961
|
+
return cls.get_bool_enums(field_name)
|
|
962
|
+
|
|
963
|
+
@classmethod
|
|
964
|
+
def get_lookup_field(cls, field_name: str) -> str:
|
|
965
|
+
"""
|
|
966
|
+
Overrides the base method.
|
|
967
|
+
|
|
968
|
+
:param str field_name: The property name to provide enum values for
|
|
969
|
+
:return: str the field name to look up
|
|
970
|
+
:rtype: str
|
|
971
|
+
"""
|
|
972
|
+
lookup_fields = {
|
|
973
|
+
"controlOwnerId": "user",
|
|
974
|
+
"controlID": "",
|
|
975
|
+
"inheritedControlId": "",
|
|
976
|
+
"inheritedRequirementId": "",
|
|
977
|
+
"inheritedSecurityPlanId": "",
|
|
978
|
+
"inheritedPolicyId": "",
|
|
979
|
+
"systemRoleId": "",
|
|
980
|
+
}
|
|
981
|
+
if field_name in lookup_fields.keys():
|
|
982
|
+
return lookup_fields[field_name]
|
|
983
|
+
return ""
|
|
984
|
+
|
|
985
|
+
@classmethod
|
|
986
|
+
def is_date_field(cls, field_name: str) -> bool:
|
|
987
|
+
"""
|
|
988
|
+
Overrides the base method.
|
|
989
|
+
|
|
990
|
+
:param str field_name: The property name to provide enum values for
|
|
991
|
+
:return: bool if the field should be formatted as a date
|
|
992
|
+
:rtype: bool
|
|
993
|
+
"""
|
|
994
|
+
return field_name in ["dateLastAssessed", "plannedImplementationDate"]
|
|
995
|
+
|
|
996
|
+
@classmethod
|
|
997
|
+
def get_export_query(cls, app: Application, parent_id: int, parent_module: str) -> list:
|
|
998
|
+
"""
|
|
999
|
+
Overrides the base method.
|
|
1000
|
+
|
|
1001
|
+
:param Application app: RegScale Application object
|
|
1002
|
+
:param int parent_id: RegScale ID of parent
|
|
1003
|
+
:param str parent_module: Module of parent
|
|
1004
|
+
:return: list GraphQL response from RegScale
|
|
1005
|
+
:rtype: list
|
|
1006
|
+
"""
|
|
1007
|
+
body = """
|
|
1008
|
+
query{
|
|
1009
|
+
controlImplementations (skip: 0, take: 50, where: {parentId: {eq: parent_id} parentModule: {eq: "parent_module"}}) {
|
|
1010
|
+
items {
|
|
1011
|
+
id
|
|
1012
|
+
controlID
|
|
1013
|
+
controlOwner {
|
|
1014
|
+
firstName
|
|
1015
|
+
lastName
|
|
1016
|
+
userName
|
|
1017
|
+
}
|
|
1018
|
+
control {
|
|
1019
|
+
title
|
|
1020
|
+
description
|
|
1021
|
+
controlId
|
|
1022
|
+
}
|
|
1023
|
+
status
|
|
1024
|
+
policy
|
|
1025
|
+
implementation
|
|
1026
|
+
responsibility
|
|
1027
|
+
inheritable
|
|
1028
|
+
parentId
|
|
1029
|
+
parentModule
|
|
1030
|
+
}
|
|
1031
|
+
totalCount
|
|
1032
|
+
pageInfo {
|
|
1033
|
+
hasNextPage
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}""".replace(
|
|
1037
|
+
"parent_module", parent_module
|
|
1038
|
+
).replace(
|
|
1039
|
+
"parent_id", str(parent_id)
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
api = Api()
|
|
1043
|
+
existing_implementation_data = api.graph(query=body)
|
|
1044
|
+
|
|
1045
|
+
if existing_implementation_data["controlImplementations"]["totalCount"] > 0:
|
|
1046
|
+
raw_data = existing_implementation_data["controlImplementations"]["items"]
|
|
1047
|
+
moded_data = []
|
|
1048
|
+
for item in raw_data:
|
|
1049
|
+
moded_item = {}
|
|
1050
|
+
moded_item["id"] = item["id"]
|
|
1051
|
+
moded_item["controlID"] = item["controlID"]
|
|
1052
|
+
moded_item["controlOwnerId"] = (
|
|
1053
|
+
str(item["controlOwner"]["lastName"]).strip()
|
|
1054
|
+
+ ", "
|
|
1055
|
+
+ str(item["controlOwner"]["firstName"]).strip()
|
|
1056
|
+
+ " ("
|
|
1057
|
+
+ str(item["controlOwner"]["userName"]).strip()
|
|
1058
|
+
+ ")"
|
|
1059
|
+
)
|
|
1060
|
+
moded_item["controlName"] = item["control"]["controlId"]
|
|
1061
|
+
moded_item["controlTitle"] = item["control"]["title"]
|
|
1062
|
+
moded_item["description"] = item["control"]["description"]
|
|
1063
|
+
moded_item["status"] = item["status"]
|
|
1064
|
+
moded_item["policy"] = item["policy"]
|
|
1065
|
+
moded_item["implementation"] = item["implementation"]
|
|
1066
|
+
moded_item["responsibility"] = item["responsibility"]
|
|
1067
|
+
moded_item["inheritable"] = item["inheritable"]
|
|
1068
|
+
moded_data.append(moded_item)
|
|
1069
|
+
return moded_data
|
|
1070
|
+
return []
|
|
1071
|
+
|
|
1072
|
+
@classmethod
|
|
1073
|
+
def use_query(cls) -> bool:
|
|
1074
|
+
"""
|
|
1075
|
+
Overrides the base method.
|
|
1076
|
+
|
|
1077
|
+
:return: bool
|
|
1078
|
+
:rtype: bool
|
|
1079
|
+
"""
|
|
1080
|
+
return True
|
|
1081
|
+
|
|
1082
|
+
@classmethod
|
|
1083
|
+
def get_extra_fields(cls) -> list:
|
|
1084
|
+
"""
|
|
1085
|
+
Overrides the base method.
|
|
1086
|
+
|
|
1087
|
+
:return: list of extra field names
|
|
1088
|
+
:rtype: list
|
|
1089
|
+
"""
|
|
1090
|
+
return ["controlName", "controlTitle", "description"]
|
|
1091
|
+
|
|
1092
|
+
@classmethod
|
|
1093
|
+
def get_include_fields(cls) -> list:
|
|
1094
|
+
"""
|
|
1095
|
+
Overrides the base method.
|
|
1096
|
+
|
|
1097
|
+
:return: list of field names
|
|
1098
|
+
:rtype: list
|
|
1099
|
+
"""
|
|
1100
|
+
return [
|
|
1101
|
+
"dateLastAssessed",
|
|
1102
|
+
"lastAssessmentResult",
|
|
1103
|
+
"practiceLevel",
|
|
1104
|
+
"processLevel",
|
|
1105
|
+
"cyberFunction",
|
|
1106
|
+
"implementationType",
|
|
1107
|
+
"implementationMethod",
|
|
1108
|
+
]
|
|
1109
|
+
|
|
1110
|
+
@classmethod
|
|
1111
|
+
def is_new_excel_record_allowed(cls) -> bool:
|
|
1112
|
+
"""
|
|
1113
|
+
Overrides the base method.
|
|
1114
|
+
|
|
1115
|
+
:return: bool indicating if the field is required
|
|
1116
|
+
:rtype: bool
|
|
1117
|
+
"""
|
|
1118
|
+
return False
|
|
1119
|
+
|
|
1120
|
+
def add_role(self, role_id: int):
|
|
1121
|
+
"""
|
|
1122
|
+
Add role to the control implementation
|
|
1123
|
+
"""
|
|
1124
|
+
if not self.id or self.id == 0:
|
|
1125
|
+
logger.error("Control Implementation ID is required to add role")
|
|
1126
|
+
ImplementationRole.add_role(
|
|
1127
|
+
role_id=role_id, control_implementation_id=self.id, parent_module=self._module_string
|
|
1128
|
+
)
|