regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/api.py +5 -2
- regscale/core/app/application.py +36 -6
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/evidence.py +727 -204
- regscale/core/app/internal/login.py +4 -2
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +86 -12
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/core/login.py +21 -4
- regscale/core/utils/async_graphql_client.py +363 -0
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/code_gen.py +109 -24
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +30 -2
- regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
- regscale/integrations/commercial/aws/cli.py +3107 -54
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
- regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
- regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +1072 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/jira.py +489 -153
- regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
- regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
- regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
- regscale/integrations/commercial/qualys/__init__.py +167 -68
- regscale/integrations/commercial/qualys/scanner.py +305 -39
- regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
- regscale/integrations/commercial/sicura/api.py +79 -42
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +83 -44
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +133 -16
- regscale/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/commercial/synqly/query_builder.py +536 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +146 -5
- regscale/integrations/commercial/tenablev2/scanner.py +1 -3
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +191 -76
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +776 -28
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +1031 -441
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +1036 -151
- regscale/integrations/control_matcher.py +432 -0
- regscale/integrations/due_date_handler.py +333 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +14 -0
- regscale/integrations/public/cci_importer.py +834 -0
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +938 -0
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +77 -6
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam/scanner.py +75 -7
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +1961 -430
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +805 -11
- regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
- regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
- regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +4 -2
- regscale/models/regscale_models/__init__.py +7 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/catalog.py +1 -1
- regscale/models/regscale_models/compliance_settings.py +251 -1
- regscale/models/regscale_models/component.py +1 -0
- regscale/models/regscale_models/control_implementation.py +236 -41
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/form_field_value.py +5 -3
- regscale/models/regscale_models/inheritance.py +44 -0
- regscale/models/regscale_models/issue.py +301 -102
- regscale/models/regscale_models/milestone.py +33 -14
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +310 -73
- regscale/models/regscale_models/security_plan.py +4 -2
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/regscale.py +25 -4
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- regscale/validation/record.py +23 -1
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
- tests/core/__init__.py +0 -0
- tests/core/utils/__init__.py +0 -0
- tests/core/utils/test_async_graphql_client.py +472 -0
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
- tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
- tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3742 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +349 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +1053 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +1152 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
- tests/regscale/integrations/test_control_matcher.py +1421 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_issue.py +378 -1
- tests/regscale/models/test_module_integration.py +582 -0
- tests/regscale/models/test_tenable_integrations.py +811 -105
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
- regscale/integrations/public/fedramp/parts_mapper.py +0 -107
- /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
# standard python imports
|
|
5
5
|
import logging
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
8
9
|
from urllib.parse import urljoin
|
|
9
10
|
|
|
10
11
|
import requests
|
|
11
12
|
from lxml.etree import Element
|
|
12
|
-
from pydantic import ConfigDict, Field
|
|
13
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
13
14
|
|
|
14
15
|
from regscale.core.app.api import Api
|
|
15
16
|
from regscale.core.app.application import Application
|
|
@@ -19,7 +20,6 @@ from regscale.models.regscale_models.implementation_role import ImplementationRo
|
|
|
19
20
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
20
21
|
from regscale.models.regscale_models.security_control import SecurityControl
|
|
21
22
|
|
|
22
|
-
|
|
23
23
|
logger = logging.getLogger("regscale")
|
|
24
24
|
PATCH_CONTENT_TYPE = "application/json-patch+json"
|
|
25
25
|
|
|
@@ -41,6 +41,8 @@ class ControlImplementationStatus(str, Enum):
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class ImplementationControlOrigin(str, Enum):
|
|
44
|
+
"""Control Implementation Origination"""
|
|
45
|
+
|
|
44
46
|
SERVICE_PROVIDER_CORPORATE = "Service Provider Corporate"
|
|
45
47
|
SERVICE_PROVIDER_SYSTEM = "Service Provider System Specific"
|
|
46
48
|
SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid (Corporate and System Specific)"
|
|
@@ -63,6 +65,7 @@ class ControlImplementationOrigin(str, Enum):
|
|
|
63
65
|
CustomerConfigured = "Customer Configured"
|
|
64
66
|
CustomerProvided = "Customer"
|
|
65
67
|
Inherited = "Inherited"
|
|
68
|
+
NotApplicable = "Not Applicable"
|
|
66
69
|
|
|
67
70
|
|
|
68
71
|
class ControlImplementation(RegScaleModel):
|
|
@@ -74,9 +77,10 @@ class ControlImplementation(RegScaleModel):
|
|
|
74
77
|
_get_objects_for_list = True
|
|
75
78
|
|
|
76
79
|
controlOwnerId: str = Field(default_factory=RegScaleModel.get_user_id)
|
|
80
|
+
controlOwnersIds: Optional[List[str]] = Field(default=None)
|
|
77
81
|
status: str # Required
|
|
78
82
|
controlID: int # Required foreign key to Security Control
|
|
79
|
-
status_lst: List[ControlImplementationStatus] = []
|
|
83
|
+
status_lst: List[ControlImplementationStatus] = Field(default=[], exclude=True)
|
|
80
84
|
id: int = 0
|
|
81
85
|
parentId: Optional[int] = None
|
|
82
86
|
parentModule: Optional[str] = None
|
|
@@ -84,7 +88,7 @@ class ControlImplementation(RegScaleModel):
|
|
|
84
88
|
createdById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
85
89
|
uuid: Optional[str] = None
|
|
86
90
|
policy: Optional[str] = None
|
|
87
|
-
implementation: Optional[str] =
|
|
91
|
+
implementation: Optional[str] = Field(default="N/A")
|
|
88
92
|
dateLastAssessed: Optional[str] = None
|
|
89
93
|
lastAssessmentResult: Optional[str] = None
|
|
90
94
|
practiceLevel: Optional[str] = None
|
|
@@ -104,7 +108,9 @@ class ControlImplementation(RegScaleModel):
|
|
|
104
108
|
qiVendorCompliance: Optional[str] = None
|
|
105
109
|
qiIssues: Optional[str] = None
|
|
106
110
|
qiOverall: Optional[str] = None
|
|
107
|
-
responsibility:
|
|
111
|
+
responsibility: str = Field(
|
|
112
|
+
default_factory=lambda: ControlImplementation.get_default_responsibility()
|
|
113
|
+
) # Required field - Control Origination
|
|
108
114
|
inheritedControlId: Optional[int] = None
|
|
109
115
|
inheritedRequirementId: Optional[int] = None
|
|
110
116
|
inheritedSecurityPlanId: Optional[int] = None
|
|
@@ -112,7 +118,7 @@ class ControlImplementation(RegScaleModel):
|
|
|
112
118
|
dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
113
119
|
lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
114
120
|
dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
115
|
-
weight: Optional[
|
|
121
|
+
weight: Optional[float] = None
|
|
116
122
|
isPublic: Optional[bool] = True
|
|
117
123
|
inheritable: Optional[bool] = False
|
|
118
124
|
systemRoleId: Optional[int] = None
|
|
@@ -141,6 +147,20 @@ class ControlImplementation(RegScaleModel):
|
|
|
141
147
|
maturityLevel: Optional[str] = None
|
|
142
148
|
assessmentFrequency: int = 0
|
|
143
149
|
|
|
150
|
+
@field_validator("implementation", mode="before")
|
|
151
|
+
@classmethod
|
|
152
|
+
def validate_implementation(cls, v: Optional[str]) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Validate implementation field - convert empty strings to 'N/A'.
|
|
155
|
+
|
|
156
|
+
:param Optional[str] v: The implementation value
|
|
157
|
+
:return: The validated implementation value
|
|
158
|
+
:rtype: str
|
|
159
|
+
"""
|
|
160
|
+
if v is None or (isinstance(v, str) and v.strip() == ""):
|
|
161
|
+
return "N/A"
|
|
162
|
+
return v
|
|
163
|
+
|
|
144
164
|
def __str__(self):
|
|
145
165
|
return f"Control Implementation {self.id}: {self.controlID}"
|
|
146
166
|
|
|
@@ -153,9 +173,29 @@ class ControlImplementation(RegScaleModel):
|
|
|
153
173
|
"""
|
|
154
174
|
self.status_lst = self._get_status_enum()
|
|
155
175
|
|
|
176
|
+
# Backwards compatibility: Auto-populate controlOwnersIds if not set but controlOwnerId exists
|
|
177
|
+
if self.controlOwnersIds is None and self.controlOwnerId:
|
|
178
|
+
self.controlOwnersIds = [self.controlOwnerId]
|
|
179
|
+
|
|
180
|
+
# Check if responsibility needs to be set (empty string, None, or default value)
|
|
181
|
+
should_update_responsibility = (
|
|
182
|
+
not self.responsibility # Handles empty string or None
|
|
183
|
+
or self.responsibility == self.get_default_responsibility()
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if should_update_responsibility:
|
|
187
|
+
if self.parentId and self.parentModule == "securityplans":
|
|
188
|
+
# Try to get a more specific default based on the actual security plan's compliance settings
|
|
189
|
+
better_default = self.get_default_responsibility(parent_id=self.parentId)
|
|
190
|
+
if better_default and better_default != self.responsibility:
|
|
191
|
+
self.responsibility = better_default
|
|
192
|
+
elif not self.responsibility:
|
|
193
|
+
# If still empty/None and no parent info, set to generic default
|
|
194
|
+
self.responsibility = self.get_default_responsibility()
|
|
195
|
+
|
|
156
196
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
157
197
|
"""
|
|
158
|
-
Override __setattr__ to update status_lst when status changes.
|
|
198
|
+
Override __setattr__ to update status_lst when status changes and handle backwards compatibility.
|
|
159
199
|
|
|
160
200
|
:param str name: The attribute name
|
|
161
201
|
:param Any value: The attribute value
|
|
@@ -164,6 +204,130 @@ class ControlImplementation(RegScaleModel):
|
|
|
164
204
|
super().__setattr__(name, value)
|
|
165
205
|
if name == "status":
|
|
166
206
|
self.status_lst = self._get_status_enum()
|
|
207
|
+
elif name == "controlOwnerId" and value:
|
|
208
|
+
# Backwards compatibility: Auto-populate controlOwnersIds when controlOwnerId is set
|
|
209
|
+
if hasattr(self, "controlOwnersIds") and (
|
|
210
|
+
not hasattr(self, "_controlOwnersIds") or self._controlOwnersIds is None
|
|
211
|
+
):
|
|
212
|
+
super().__setattr__("controlOwnersIds", [value])
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
@lru_cache(maxsize=256)
|
|
216
|
+
def get_default_responsibility(
|
|
217
|
+
cls, parent_id: Optional[int] = None, compliance_setting_id: Optional[int] = None
|
|
218
|
+
) -> str:
|
|
219
|
+
"""
|
|
220
|
+
Get default responsibility (control origination) based on compliance settings.
|
|
221
|
+
|
|
222
|
+
Cached for high-performance bulk operations.
|
|
223
|
+
|
|
224
|
+
:param Optional[int] parent_id: The parent security plan ID to get compliance settings from
|
|
225
|
+
:param Optional[int] compliance_setting_id: Specific compliance setting ID override
|
|
226
|
+
:return: Default responsibility string
|
|
227
|
+
:rtype: str
|
|
228
|
+
"""
|
|
229
|
+
actual_compliance_setting_id = compliance_setting_id or cls._get_compliance_setting_id_from_parent(parent_id)
|
|
230
|
+
|
|
231
|
+
if actual_compliance_setting_id:
|
|
232
|
+
if responsibility := cls._get_responsibility_from_compliance_settings(actual_compliance_setting_id):
|
|
233
|
+
return responsibility
|
|
234
|
+
|
|
235
|
+
return cls._get_fallback_responsibility(actual_compliance_setting_id)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
@lru_cache(maxsize=128)
|
|
239
|
+
def _get_compliance_setting_id_from_parent(cls, parent_id: Optional[int]) -> Optional[int]:
|
|
240
|
+
"""
|
|
241
|
+
Get compliance setting ID from parent security plan.
|
|
242
|
+
|
|
243
|
+
Cached to avoid repeated API calls for the same security plan.
|
|
244
|
+
"""
|
|
245
|
+
if parent_id is None:
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
from regscale.models.regscale_models.security_plan import SecurityPlan
|
|
250
|
+
|
|
251
|
+
security_plan: SecurityPlan = SecurityPlan.get_object(parent_id)
|
|
252
|
+
return security_plan.complianceSettingsId if security_plan else None
|
|
253
|
+
except Exception:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
@lru_cache(maxsize=32)
|
|
258
|
+
def _get_responsibility_from_compliance_settings(cls, compliance_setting_id: int) -> Optional[str]:
|
|
259
|
+
"""
|
|
260
|
+
Get default responsibility from compliance settings API using settingsList endpoint.
|
|
261
|
+
|
|
262
|
+
Cached to avoid repeated API calls for the same compliance setting.
|
|
263
|
+
"""
|
|
264
|
+
responsibility = ControlImplementationOrigin.NotApplicable.value
|
|
265
|
+
try:
|
|
266
|
+
from regscale.models.regscale_models.compliance_settings import ComplianceSettings
|
|
267
|
+
|
|
268
|
+
if cs_responsibility := ComplianceSettings.get_default_responsibility_for_compliance_setting(
|
|
269
|
+
compliance_setting_id
|
|
270
|
+
):
|
|
271
|
+
responsibility = cs_responsibility
|
|
272
|
+
except Exception:
|
|
273
|
+
return responsibility
|
|
274
|
+
|
|
275
|
+
return responsibility
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def _get_fallback_responsibility(cls, compliance_setting_id: Optional[int] = None) -> str:
|
|
279
|
+
"""
|
|
280
|
+
Get intelligent fallback responsibility using framework-specific defaults.
|
|
281
|
+
|
|
282
|
+
:param Optional[int] compliance_setting_id: Compliance setting ID to determine framework type
|
|
283
|
+
:return: Fallback responsibility string
|
|
284
|
+
:rtype: str
|
|
285
|
+
"""
|
|
286
|
+
if compliance_setting_id:
|
|
287
|
+
return cls._get_framework_default_responsibility(compliance_setting_id)
|
|
288
|
+
|
|
289
|
+
# Ultimate fallback for unknown compliance settings
|
|
290
|
+
return ControlImplementationOrigin.NotApplicable.value
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def _get_framework_default_responsibility(cls, compliance_setting_id: int) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Get default responsibility for a specific compliance framework.
|
|
296
|
+
|
|
297
|
+
:param int compliance_setting_id: The compliance setting ID (1=RegScale, 2=FedRAMP, 3=PCI, 4=DoD, 5=CMMC)
|
|
298
|
+
:return: Default responsibility string
|
|
299
|
+
:rtype: str
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
from regscale.models.regscale_models.compliance_settings import ComplianceSettings
|
|
303
|
+
|
|
304
|
+
default_value = ComplianceSettings.get_default_responsibility_for_compliance_setting(compliance_setting_id)
|
|
305
|
+
if default_value:
|
|
306
|
+
return default_value
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
# Framework-specific fallbacks if API fails
|
|
311
|
+
fallback_map = {
|
|
312
|
+
1: "Provider", # RegScale Default
|
|
313
|
+
2: ImplementationControlOrigin.SERVICE_PROVIDER_CORPORATE.value, # FedRAMP
|
|
314
|
+
3: ImplementationControlOrigin.SERVICE_PROVIDER_CORPORATE.value, # PCI
|
|
315
|
+
4: "System-Specific", # DoD
|
|
316
|
+
5: "Provider", # CMMC
|
|
317
|
+
}
|
|
318
|
+
return fallback_map.get(compliance_setting_id, "Service Provider Corporate")
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def clear_responsibility_cache(cls) -> None:
|
|
322
|
+
"""
|
|
323
|
+
Clear the responsibility lookup cache.
|
|
324
|
+
|
|
325
|
+
Call this method when compliance settings have been updated to ensure
|
|
326
|
+
fresh data is retrieved from the API.
|
|
327
|
+
"""
|
|
328
|
+
cls.get_default_responsibility.cache_clear()
|
|
329
|
+
cls._get_compliance_setting_id_from_parent.cache_clear()
|
|
330
|
+
cls._get_responsibility_from_compliance_settings.cache_clear()
|
|
167
331
|
|
|
168
332
|
@classmethod
|
|
169
333
|
def _get_additional_endpoints(cls) -> ConfigDict:
|
|
@@ -325,15 +489,15 @@ class ControlImplementation(RegScaleModel):
|
|
|
325
489
|
:return: A dictionary mapping control IDs to implementation IDs
|
|
326
490
|
:rtype: Dict[str, int]
|
|
327
491
|
"""
|
|
328
|
-
logger.
|
|
492
|
+
logger.debug("Getting control label map by parent...")
|
|
329
493
|
response = cls._get_api_handler().get(
|
|
330
494
|
endpoint=cls.get_endpoint("get_all_by_parent").format(intParentID=parent_id, strModule=parent_module)
|
|
331
495
|
)
|
|
332
496
|
|
|
333
497
|
if response and response.ok:
|
|
334
|
-
logger.
|
|
498
|
+
logger.debug("Fetched control label map by parent successfully.")
|
|
335
499
|
return {parentheses_to_dot(ci["controlName"]): ci["id"] for ci in response.json()}
|
|
336
|
-
logger.
|
|
500
|
+
logger.debug("Unable to get control label map by parent.")
|
|
337
501
|
return {}
|
|
338
502
|
|
|
339
503
|
@classmethod
|
|
@@ -345,14 +509,14 @@ class ControlImplementation(RegScaleModel):
|
|
|
345
509
|
:return: A dictionary mapping control IDs to implementation IDs
|
|
346
510
|
:rtype: Dict[int, int]
|
|
347
511
|
"""
|
|
348
|
-
logger.
|
|
512
|
+
logger.debug("Getting control id map by parent...")
|
|
349
513
|
response = cls._get_api_handler().get(
|
|
350
514
|
endpoint=cls.get_endpoint("get_all_by_parent").format(intParentID=parent_id, strModule=parent_module)
|
|
351
515
|
)
|
|
352
516
|
if response and response.ok:
|
|
353
|
-
logger.
|
|
517
|
+
logger.debug("Fetched control id map by parent successfully.")
|
|
354
518
|
return {ci["controlID"]: ci["id"] for ci in response.json()}
|
|
355
|
-
logger.
|
|
519
|
+
logger.debug("Unable to get control id map by parent.")
|
|
356
520
|
return {}
|
|
357
521
|
|
|
358
522
|
@classmethod
|
|
@@ -364,14 +528,14 @@ class ControlImplementation(RegScaleModel):
|
|
|
364
528
|
:return: A dictionary mapping control IDs to implementation IDs
|
|
365
529
|
:rtype: Dict[str, int]
|
|
366
530
|
"""
|
|
367
|
-
logger.
|
|
531
|
+
logger.debug("Getting control label map by plan...")
|
|
368
532
|
response = cls._get_api_handler().get(
|
|
369
533
|
endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
|
|
370
534
|
)
|
|
371
535
|
if response and response.ok:
|
|
372
|
-
logger.
|
|
536
|
+
logger.debug("Fetched control label map by plan successfully.")
|
|
373
537
|
return {parentheses_to_dot(ci["control"]["controlId"]): ci["id"] for ci in response.json()}
|
|
374
|
-
logger.
|
|
538
|
+
logger.warning("Unable to get control label map by plan.")
|
|
375
539
|
return {}
|
|
376
540
|
|
|
377
541
|
@classmethod
|
|
@@ -428,14 +592,14 @@ class ControlImplementation(RegScaleModel):
|
|
|
428
592
|
:return: A dictionary mapping control IDs to implementation IDs
|
|
429
593
|
:rtype: Dict[int, int]
|
|
430
594
|
"""
|
|
431
|
-
logger.
|
|
595
|
+
logger.debug("Getting control id map by plan...")
|
|
432
596
|
response = cls._get_api_handler().get(
|
|
433
597
|
endpoint=cls.get_endpoint("get_all_by_plan_with_controls").format(int_security_plan=plan_id)
|
|
434
598
|
)
|
|
435
599
|
if response and response.ok:
|
|
436
|
-
logger.
|
|
600
|
+
logger.debug("Fetched control id map by plan successfully.")
|
|
437
601
|
return {ci["control"]["id"]: ci["id"] for ci in response.json()}
|
|
438
|
-
logger.
|
|
602
|
+
logger.warning("Unable to get control id map by plan.")
|
|
439
603
|
return {}
|
|
440
604
|
|
|
441
605
|
@staticmethod
|
|
@@ -723,9 +887,9 @@ class ControlImplementation(RegScaleModel):
|
|
|
723
887
|
existing_control_implementations_json = response.json()
|
|
724
888
|
for cim in existing_control_implementations_json:
|
|
725
889
|
existing_implementation_dict[cim.get("controlName")] = cim
|
|
726
|
-
logger.
|
|
890
|
+
logger.debug(f"Found {len(existing_implementation_dict)} existing control implementations")
|
|
727
891
|
elif response.status_code == 404:
|
|
728
|
-
logger.
|
|
892
|
+
logger.debug(f"No existing control implementations found for {parent_id}")
|
|
729
893
|
else:
|
|
730
894
|
logger.warning(f"Unable to get existing control implementations. {response.content}")
|
|
731
895
|
return existing_implementation_dict
|
|
@@ -1007,7 +1171,7 @@ class ControlImplementation(RegScaleModel):
|
|
|
1007
1171
|
if field_name == "status":
|
|
1008
1172
|
return [imp_status.value for imp_status in ControlImplementationStatus]
|
|
1009
1173
|
if field_name == "responsibility":
|
|
1010
|
-
return [
|
|
1174
|
+
return [origin.value for origin in ControlImplementationOrigin]
|
|
1011
1175
|
return cls.get_bool_enums(field_name)
|
|
1012
1176
|
|
|
1013
1177
|
@classmethod
|
|
@@ -1054,6 +1218,8 @@ class ControlImplementation(RegScaleModel):
|
|
|
1054
1218
|
:return: list GraphQL response from RegScale
|
|
1055
1219
|
:rtype: list
|
|
1056
1220
|
"""
|
|
1221
|
+
from regscale.core.app.internal.control_editor import _extract_control_owner_display
|
|
1222
|
+
|
|
1057
1223
|
body = """
|
|
1058
1224
|
query{
|
|
1059
1225
|
controlImplementations (skip: 0, take: 50, where: {parentId: {eq: parent_id} parentModule: {eq: "parent_module"}}) {
|
|
@@ -1099,22 +1265,25 @@ class ControlImplementation(RegScaleModel):
|
|
|
1099
1265
|
moded_item = {}
|
|
1100
1266
|
moded_item["id"] = item["id"]
|
|
1101
1267
|
moded_item["controlID"] = item["controlID"]
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
moded_item["
|
|
1117
|
-
moded_item["
|
|
1268
|
+
|
|
1269
|
+
# Extract control owner display using centralized method
|
|
1270
|
+
moded_item["controlOwnerId"] = _extract_control_owner_display(item)
|
|
1271
|
+
|
|
1272
|
+
# Handle case where control or its fields might be None
|
|
1273
|
+
if item.get("control") and item["control"] is not None:
|
|
1274
|
+
moded_item["controlName"] = item["control"].get("controlId", "")
|
|
1275
|
+
moded_item["controlTitle"] = item["control"].get("title", "")
|
|
1276
|
+
moded_item["description"] = item["control"].get("description", "")
|
|
1277
|
+
else:
|
|
1278
|
+
moded_item["controlName"] = ""
|
|
1279
|
+
moded_item["controlTitle"] = ""
|
|
1280
|
+
moded_item["description"] = ""
|
|
1281
|
+
|
|
1282
|
+
moded_item["status"] = item.get("status", "")
|
|
1283
|
+
moded_item["policy"] = item.get("policy", "")
|
|
1284
|
+
moded_item["implementation"] = item.get("implementation", "")
|
|
1285
|
+
moded_item["responsibility"] = item.get("responsibility", "")
|
|
1286
|
+
moded_item["inheritable"] = item.get("inheritable", False)
|
|
1118
1287
|
moded_data.append(moded_item)
|
|
1119
1288
|
return moded_data
|
|
1120
1289
|
return []
|
|
@@ -1187,8 +1356,34 @@ class ControlImplementation(RegScaleModel):
|
|
|
1187
1356
|
:return: list of control implementations, or None if not found
|
|
1188
1357
|
:rtype: Optional[list[dict]]
|
|
1189
1358
|
"""
|
|
1190
|
-
|
|
1191
|
-
|
|
1359
|
+
query = {
|
|
1360
|
+
"parentID": 0,
|
|
1361
|
+
"module": "",
|
|
1362
|
+
"friendlyName": "",
|
|
1363
|
+
"workbench": "",
|
|
1364
|
+
"base": "",
|
|
1365
|
+
"sort": "sortId",
|
|
1366
|
+
"direction": "Ascending",
|
|
1367
|
+
"simpleSearch": "",
|
|
1368
|
+
"page": 1,
|
|
1369
|
+
"pageSize": 1000,
|
|
1370
|
+
"query": {
|
|
1371
|
+
"id": 0,
|
|
1372
|
+
"viewName": "",
|
|
1373
|
+
"module": "",
|
|
1374
|
+
"scope": "",
|
|
1375
|
+
"createdById": "",
|
|
1376
|
+
"dateCreated": None,
|
|
1377
|
+
"parameters": [],
|
|
1378
|
+
},
|
|
1379
|
+
"groupBy": "",
|
|
1380
|
+
"intDays": 0,
|
|
1381
|
+
"subTab": True,
|
|
1382
|
+
}
|
|
1383
|
+
query["parentId"] = regscale_id
|
|
1384
|
+
query["module"] = regscale_module
|
|
1385
|
+
endpoint = cls.get_endpoint("filter_control_implementations")
|
|
1386
|
+
response = cls._get_api_handler().post(endpoint=endpoint, data=query)
|
|
1192
1387
|
if response and response.ok:
|
|
1193
1388
|
return response.json()
|
|
1194
1389
|
return None
|
|
@@ -250,13 +250,82 @@ class ControlObjective(RegScaleModel):
|
|
|
250
250
|
return control_objectives
|
|
251
251
|
|
|
252
252
|
|
|
253
|
+
def _process_objective_ccis(objective: ControlObjective, ccis_to_control_ids: dict[str, set[int]]) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Process CCI IDs from a control objective.
|
|
256
|
+
|
|
257
|
+
:param ControlObjective objective: The control objective to process
|
|
258
|
+
:param dict ccis_to_control_ids: Dictionary to update with CCI mappings
|
|
259
|
+
:return: None
|
|
260
|
+
"""
|
|
261
|
+
if not objective.otherId:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
cci_ids = objective.otherId.split(",")
|
|
265
|
+
for cci_id in cci_ids:
|
|
266
|
+
cci_id = cci_id.strip()
|
|
267
|
+
if cci_id and cci_id.startswith("CCI-"):
|
|
268
|
+
ccis_to_control_ids[cci_id].add(objective.securityControlId)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _fetch_cci_objectives_batch(parent_id: int, skip: int, take: int) -> list[ControlObjective]:
|
|
272
|
+
"""
|
|
273
|
+
Fetch a batch of CCI objectives.
|
|
274
|
+
|
|
275
|
+
:param int parent_id: The parent ID
|
|
276
|
+
:param int skip: Number of items to skip
|
|
277
|
+
:param int take: Number of items to take
|
|
278
|
+
:return: List of control objectives
|
|
279
|
+
:rtype: list[ControlObjective]
|
|
280
|
+
"""
|
|
281
|
+
return ControlObjective.fetch_control_objectives_by_other_id(
|
|
282
|
+
parent_id=parent_id, other_id_contains="CCI-", skip=skip, take=take
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
253
286
|
def map_ccis_to_control_ids(parent_id: int) -> dict:
|
|
287
|
+
"""
|
|
288
|
+
Map CCI IDs to control IDs with pagination support.
|
|
289
|
+
|
|
290
|
+
:param int parent_id: The parent ID to fetch objectives for
|
|
291
|
+
:return: Dictionary mapping CCI IDs to sets of control IDs
|
|
292
|
+
:rtype: dict
|
|
293
|
+
"""
|
|
294
|
+
import logging
|
|
295
|
+
|
|
296
|
+
logger = logging.getLogger("regscale")
|
|
254
297
|
ccis_to_control_ids: dict[str, set[int]] = defaultdict(set)
|
|
255
|
-
objectives = ControlObjective.fetch_control_objectives_by_other_id(parent_id=parent_id, other_id_contains="CCI-")
|
|
256
298
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
299
|
+
try:
|
|
300
|
+
skip = 0
|
|
301
|
+
take = 50 # Use 50 as RegScale API limit
|
|
302
|
+
total_fetched = 0
|
|
303
|
+
max_iterations = 100 # Increase safety limit since batch size is smaller
|
|
304
|
+
|
|
305
|
+
for _ in range(max_iterations):
|
|
306
|
+
objectives = _fetch_cci_objectives_batch(parent_id, skip, take)
|
|
307
|
+
if not objectives:
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
# Process each objective
|
|
311
|
+
for objective in objectives:
|
|
312
|
+
_process_objective_ccis(objective, ccis_to_control_ids)
|
|
313
|
+
|
|
314
|
+
total_fetched += len(objectives)
|
|
315
|
+
|
|
316
|
+
# Check if we've reached the end
|
|
317
|
+
if len(objectives) < take:
|
|
318
|
+
break
|
|
319
|
+
|
|
320
|
+
skip += take
|
|
321
|
+
else:
|
|
322
|
+
logger.warning(f"Reached max iterations ({max_iterations}). Total fetched: {total_fetched}")
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.debug(f"Error fetching CCI to control map: {e}")
|
|
326
|
+
return {}
|
|
327
|
+
|
|
328
|
+
if ccis_to_control_ids:
|
|
329
|
+
logger.debug(f"Mapped {len(ccis_to_control_ids)} unique CCIs to controls")
|
|
261
330
|
|
|
262
331
|
return ccis_to_control_ids
|
|
@@ -385,6 +385,8 @@ class File(BaseModel):
|
|
|
385
385
|
file_type_header = "application/gzip"
|
|
386
386
|
elif file_type == ".msg":
|
|
387
387
|
file_type_header = "application/vnd.ms-outlook"
|
|
388
|
+
elif file_type == ".jsonl":
|
|
389
|
+
file_type_header = "application/jsonl+json"
|
|
388
390
|
else:
|
|
389
391
|
logger = logging.getLogger("regscale")
|
|
390
392
|
logger.warning(f"Unacceptable file type for upload: {file_type}")
|
|
@@ -122,7 +122,6 @@ class FormFieldValue(RegScaleModel):
|
|
|
122
122
|
f"The following custom fields are missing:\n \
|
|
123
123
|
{missing_custom_fields}\n \
|
|
124
124
|
Load these custom fields in RegScale \
|
|
125
|
-
using the file: security_plan_custom_fields.json\
|
|
126
125
|
and run this command again"
|
|
127
126
|
)
|
|
128
127
|
|
|
@@ -132,7 +131,8 @@ class FormFieldValue(RegScaleModel):
|
|
|
132
131
|
def save_custom_fields(form_field_values: list):
|
|
133
132
|
"""
|
|
134
133
|
Populate Custom Fields form a list of dict of
|
|
135
|
-
record_id,
|
|
134
|
+
record_id: int, record_module: str, form_field_id: int,
|
|
135
|
+
and field_value: Any
|
|
136
136
|
|
|
137
137
|
:param list form_field_values: list of custom form
|
|
138
138
|
fields, values, and the record to which to post
|
|
@@ -148,5 +148,7 @@ class FormFieldValue(RegScaleModel):
|
|
|
148
148
|
]
|
|
149
149
|
if data:
|
|
150
150
|
FormFieldValue.save_custom_data(
|
|
151
|
-
record_id=form_field_value
|
|
151
|
+
record_id=form_field_value.get("record_id"),
|
|
152
|
+
module_name=form_field_value.get("record_module", "securityplans"),
|
|
153
|
+
data=data,
|
|
152
154
|
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for Inheritance in the application"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
8
|
+
|
|
9
|
+
from regscale.core.app.api import Api
|
|
10
|
+
from regscale.core.app.logz import create_logger
|
|
11
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
12
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Inheritance(RegScaleModel):
|
|
16
|
+
"""
|
|
17
|
+
Inherited Control model
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_module_slug = "inheritance"
|
|
21
|
+
|
|
22
|
+
id: int = 0
|
|
23
|
+
recordId: int = 0
|
|
24
|
+
recordModule: str
|
|
25
|
+
policyId: Optional[int] = None
|
|
26
|
+
planId: Optional[int] = None
|
|
27
|
+
dateInherited: Optional[str] = Field(default_factory=get_current_datetime)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
31
|
+
"""
|
|
32
|
+
Get additional endpoints for the Inherited Controls model.
|
|
33
|
+
|
|
34
|
+
:return: A dictionary of additional endpoints
|
|
35
|
+
:rtype: ConfigDict
|
|
36
|
+
"""
|
|
37
|
+
return ConfigDict( # type: ignore
|
|
38
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}/{strModule}",
|
|
39
|
+
get_all_by_control="/api/{model_slug}/getAllByBaseControl/{control_id}",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
## Note: There are no endpoints in this module for
|
|
43
|
+
# get (get_object)
|
|
44
|
+
# post (save/update)
|