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
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integrates CSAM into RegScale"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
from rich.progress import track
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from regscale.core.app.api import Api
|
|
12
|
+
from regscale.core.app.application import Application
|
|
13
|
+
from regscale.core.utils.date import format_to_regscale_iso, date_obj
|
|
14
|
+
from regscale.core.app.utils.app_utils import error_and_exit, filter_list
|
|
15
|
+
from regscale.models.regscale_models import (
|
|
16
|
+
Organization,
|
|
17
|
+
SecurityPlan,
|
|
18
|
+
User,
|
|
19
|
+
)
|
|
20
|
+
from regscale.models.regscale_models.module import Module
|
|
21
|
+
from regscale.models.regscale_models.form_field_value import FormFieldValue
|
|
22
|
+
from regscale.integrations.public.csam.csam_poam import import_csam_poams
|
|
23
|
+
from regscale.integrations.public.csam.csam_controls import (
|
|
24
|
+
import_csam_controls,
|
|
25
|
+
set_inheritable,
|
|
26
|
+
import_csam_inheritance,
|
|
27
|
+
)
|
|
28
|
+
from regscale.integrations.public.csam.csam_agency_defined import update_ssp_agency_details
|
|
29
|
+
from regscale.integrations.public.csam.csam_common import (
|
|
30
|
+
retrieve_ssps_custom_form_map,
|
|
31
|
+
retrieve_custom_form_ssps_map,
|
|
32
|
+
retrieve_from_csam,
|
|
33
|
+
set_custom_fields,
|
|
34
|
+
fix_form_field_value,
|
|
35
|
+
CSAM_FIELD_NAME,
|
|
36
|
+
FISMA_FIELD_NAME,
|
|
37
|
+
SSP_BASIC_TAB,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger("regscale")
|
|
42
|
+
console = Console()
|
|
43
|
+
|
|
44
|
+
####################################################################################################
|
|
45
|
+
#
|
|
46
|
+
# IMPORT SSP / POAM FROM DoJ's CSAM GRC
|
|
47
|
+
# CSAM API Docs: https://csam.dhs.gov/CSAM/api/docs/index.html (required PIV)
|
|
48
|
+
#
|
|
49
|
+
####################################################################################################
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
SSP_SYSTEM_TAB = "System Information"
|
|
53
|
+
SSP_FINANCIAL_TAB = "Financial Info"
|
|
54
|
+
SSP_PRIVACY_TAB = "Privacy-Details"
|
|
55
|
+
SSP_CONTINGENCY_TAB = "Continuity and Incident Response"
|
|
56
|
+
|
|
57
|
+
CUSTOM_FIELDS_BASIC_LIST = [
|
|
58
|
+
"acronym",
|
|
59
|
+
"Classification",
|
|
60
|
+
"FISMA Reportable",
|
|
61
|
+
"Contractor System",
|
|
62
|
+
"Authorization Process",
|
|
63
|
+
"ATO Date",
|
|
64
|
+
"ATO Status",
|
|
65
|
+
"Critical Infrastructure",
|
|
66
|
+
"Mission Essential",
|
|
67
|
+
"uiiCode",
|
|
68
|
+
"HVA Identifier",
|
|
69
|
+
"External Web Interface",
|
|
70
|
+
"CFO Designation",
|
|
71
|
+
"Law Enforcement Sensitive",
|
|
72
|
+
CSAM_FIELD_NAME,
|
|
73
|
+
FISMA_FIELD_NAME,
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@click.group()
|
|
78
|
+
def csam():
|
|
79
|
+
"""Integrate CSAM."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@csam.command(name="import_ssp")
|
|
83
|
+
def import_ssp():
|
|
84
|
+
"""
|
|
85
|
+
Import SSP from CSAM
|
|
86
|
+
Into RegScale
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
import_csam_ssp()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@csam.command(name="import_poam")
|
|
93
|
+
def import_poam():
|
|
94
|
+
"""
|
|
95
|
+
Import POAMS from CSAM
|
|
96
|
+
Into RegScale
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
import_csam_poams()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def import_csam_ssp():
|
|
103
|
+
"""
|
|
104
|
+
Import SSPs from CSAM
|
|
105
|
+
Into RegScale
|
|
106
|
+
According to a filter in init.yaml
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
logger.info("Gathering reference info...")
|
|
110
|
+
# Check Custom Fields exist
|
|
111
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
112
|
+
CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Get a map of existing custom forms
|
|
116
|
+
ssp_map = retrieve_custom_form_ssps_map(
|
|
117
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Get a list of orgs and create a map to id
|
|
121
|
+
orgs = Organization.get_list()
|
|
122
|
+
org_map = {org.name: org.id for org in orgs}
|
|
123
|
+
|
|
124
|
+
# Grab the data from CSAM
|
|
125
|
+
app = Application()
|
|
126
|
+
csam_filter = app.config.get("csamFilter", None)
|
|
127
|
+
|
|
128
|
+
logger.info("Retrieving systems from CSAM...")
|
|
129
|
+
results = retrieve_from_csam(
|
|
130
|
+
csam_endpoint="/CSAM/api/v1/systems",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if not results:
|
|
134
|
+
error_and_exit("Failure to retrieve plans from CSAM")
|
|
135
|
+
else:
|
|
136
|
+
logger.info("Retrieved plans from CSAM, parsing results...")
|
|
137
|
+
|
|
138
|
+
results = filter_list(results, csam_filter)
|
|
139
|
+
if not results:
|
|
140
|
+
error_and_exit(
|
|
141
|
+
"No results match filter in CSAM. \
|
|
142
|
+
Please check your CSAM configuration."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
logger.info("Importing systems... ")
|
|
146
|
+
# Parse the results
|
|
147
|
+
updated_ssps = []
|
|
148
|
+
updated_ssps = save_ssp_front_matter(
|
|
149
|
+
results=results,
|
|
150
|
+
ssp_map=ssp_map,
|
|
151
|
+
custom_fields_basic_map=custom_fields_basic_map,
|
|
152
|
+
org_map=org_map,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Now have to get the system details for each system
|
|
156
|
+
update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
|
|
157
|
+
|
|
158
|
+
# Import the authorization process and status
|
|
159
|
+
import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
|
|
160
|
+
|
|
161
|
+
# Import the Privacy date
|
|
162
|
+
import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
|
|
163
|
+
|
|
164
|
+
# Import the Contingency & IR data
|
|
165
|
+
import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
|
|
166
|
+
|
|
167
|
+
# Import the controls
|
|
168
|
+
import_csam_controls(import_ids=[ssp.id for ssp in updated_ssps])
|
|
169
|
+
|
|
170
|
+
# Set inheritance if system type = program
|
|
171
|
+
for result in results:
|
|
172
|
+
if result.get("systemType") == "Program":
|
|
173
|
+
# Get the RegScale SSP Id
|
|
174
|
+
program_id = ssp_map.get(result["externalId"])
|
|
175
|
+
if not program_id:
|
|
176
|
+
logger.error(
|
|
177
|
+
f"Could not find RegScale SSP for CSAM id: {result['externalId']}. \
|
|
178
|
+
Please create or import the Security Plan prior to importing inheritance."
|
|
179
|
+
)
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
# Set the inheritable flag
|
|
183
|
+
set_inheritable(regscale_id=program_id)
|
|
184
|
+
|
|
185
|
+
# Import the Inheritance
|
|
186
|
+
import_csam_inheritance(import_ids=[ssp.id for ssp in updated_ssps])
|
|
187
|
+
|
|
188
|
+
# Import the POCs
|
|
189
|
+
import_csam_pocs(import_ids=[ssp.id for ssp in updated_ssps])
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def sync_csam_ssps():
|
|
193
|
+
|
|
194
|
+
logger.info("Gathering reference info...")
|
|
195
|
+
# Check Custom Fields exist
|
|
196
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields(
|
|
197
|
+
CUSTOM_FIELDS_BASIC_LIST, "securityplans", SSP_BASIC_TAB
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Get a map of existing custom forms
|
|
201
|
+
ssp_map = retrieve_custom_form_ssps_map(
|
|
202
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[FISMA_FIELD_NAME]
|
|
203
|
+
)
|
|
204
|
+
csam_map = retrieve_custom_form_ssps_map(
|
|
205
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Get a list of orgs and create a map to id
|
|
209
|
+
orgs = Organization.get_list()
|
|
210
|
+
org_map = {org.name: org.id for org in orgs}
|
|
211
|
+
|
|
212
|
+
csam_list = []
|
|
213
|
+
for id in csam_map.keys():
|
|
214
|
+
csam_list.append(int(id))
|
|
215
|
+
csam_filter = {"id": csam_list}
|
|
216
|
+
|
|
217
|
+
logger.info("Retrieving systems from CSAM...")
|
|
218
|
+
results = retrieve_from_csam(
|
|
219
|
+
csam_endpoint="/CSAM/api/v1/systems",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
results = filter_list(results, csam_filter)
|
|
223
|
+
|
|
224
|
+
logger.info("Syncing systems... ")
|
|
225
|
+
# Parse the results
|
|
226
|
+
updated_ssps = []
|
|
227
|
+
updated_ssps = save_ssp_front_matter(
|
|
228
|
+
results=results,
|
|
229
|
+
ssp_map=ssp_map,
|
|
230
|
+
custom_fields_basic_map=custom_fields_basic_map,
|
|
231
|
+
org_map=org_map,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Now have to get the system details for each system
|
|
235
|
+
update_ssp_agency_details(updated_ssps, custom_fields_basic_map)
|
|
236
|
+
|
|
237
|
+
# Import the authorization process and status
|
|
238
|
+
import_csam_authorization(import_ids=[ssp.id for ssp in updated_ssps])
|
|
239
|
+
|
|
240
|
+
# Import the Privacy date
|
|
241
|
+
import_csam_privacy_info(import_ids=[ssp.id for ssp in updated_ssps])
|
|
242
|
+
|
|
243
|
+
# Import the Contingency & IR data
|
|
244
|
+
import_csam_contingency(import_ids=[ssp.id for ssp in updated_ssps])
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def import_csam_pocs(import_ids: Optional[List[int]] = None):
|
|
248
|
+
"""
|
|
249
|
+
Import the Points of Contact from CSAM
|
|
250
|
+
Into RegScale
|
|
251
|
+
"""
|
|
252
|
+
custom_fields_pocs_list = [
|
|
253
|
+
"Certifying Official",
|
|
254
|
+
"Alternate Information System Security Manager",
|
|
255
|
+
"Alternate Information System Security Officer",
|
|
256
|
+
]
|
|
257
|
+
# Check Custom Fields exist
|
|
258
|
+
custom_fields_pocs_map = FormFieldValue.check_custom_fields(
|
|
259
|
+
custom_fields_pocs_list, "securityplans", "Points of Contact"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Get existing ssps by CSAM Id
|
|
263
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
264
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
265
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
plans = import_ids if import_ids else list(ssp_map.keys())
|
|
269
|
+
|
|
270
|
+
# Get a list of users and create a map to id
|
|
271
|
+
users = User.get_all()
|
|
272
|
+
user_map = {user.userName: user.id for user in users}
|
|
273
|
+
|
|
274
|
+
# TO DO... Add the rest of the logic
|
|
275
|
+
# Delete these lines: Added to shut up sonarqube
|
|
276
|
+
logger.debug(f"Custom Fields Map: {custom_fields_pocs_map}, User Map: {user_map}")
|
|
277
|
+
logger.debug(f"SSP Map: {ssp_map}, Plans: {plans}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def import_csam_privacy_info(import_ids: Optional[List[int]] = None):
|
|
281
|
+
"""
|
|
282
|
+
Import the Privacy Info from CSAM
|
|
283
|
+
Into RegScale
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
# Get existing ssps by CSAM Id
|
|
287
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
288
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
289
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
293
|
+
|
|
294
|
+
updated_ssps = []
|
|
295
|
+
if len(ssps) == 0:
|
|
296
|
+
return
|
|
297
|
+
for index in track(
|
|
298
|
+
range(len(ssps)),
|
|
299
|
+
description=f"Importing {len(ssps)} SSP privacy...",
|
|
300
|
+
):
|
|
301
|
+
ssp = ssps[index]
|
|
302
|
+
system_id = ssp_map.get(ssp)
|
|
303
|
+
if not system_id:
|
|
304
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
305
|
+
continue
|
|
306
|
+
else:
|
|
307
|
+
updated_ssps.append(ssp)
|
|
308
|
+
|
|
309
|
+
result = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/privacy")
|
|
310
|
+
if len(result) == 0:
|
|
311
|
+
logger.error(f"Could not retrieve privacy for CSAM ID {system_id}. RegScale SSP id: {ssp}")
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
pia_date = result.get("privacyImpactAssessmentDateCompleted")
|
|
315
|
+
pta_date = result.get("privacyThresholdAnalysisDateCompleted")
|
|
316
|
+
|
|
317
|
+
# Get SORN Status
|
|
318
|
+
result = retrieve_from_csam(
|
|
319
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{system_id}/sorn",
|
|
320
|
+
)
|
|
321
|
+
if len(result) == 0:
|
|
322
|
+
logger.debug(f"Could not retrieve SORN for CSAM ID {system_id}. RegScale SSP id: {ssp}")
|
|
323
|
+
continue
|
|
324
|
+
sorn_date = 0
|
|
325
|
+
sorn_id = ""
|
|
326
|
+
for sorn_status in result:
|
|
327
|
+
if date_obj(sorn_status.get("publishedDate")) > date_obj(sorn_date):
|
|
328
|
+
sorn_date = sorn_status.get("publishedDate")
|
|
329
|
+
sorn_id = sorn_status.get("systemOfRecordsNoticeId").strip()
|
|
330
|
+
|
|
331
|
+
# Set the records
|
|
332
|
+
record = {"pia_date": pia_date, "pta_date": pta_date, "sorn_date": sorn_date, "sorn_id": sorn_id}
|
|
333
|
+
save_privacy_records(regscale_id=ssp, record=record)
|
|
334
|
+
|
|
335
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with privacy data")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def save_privacy_records(regscale_id: int, record: dict):
|
|
339
|
+
|
|
340
|
+
custom_fields_privacy_list = ["PIA Date", "PTA Date", "SORN Date", "SORN Id"]
|
|
341
|
+
|
|
342
|
+
# Check for custom fields
|
|
343
|
+
custom_fields_map = FormFieldValue.check_custom_fields(custom_fields_privacy_list, "securityplans", SSP_PRIVACY_TAB)
|
|
344
|
+
|
|
345
|
+
privacy_fields = []
|
|
346
|
+
if record.get("pia_date"):
|
|
347
|
+
privacy_fields.append(
|
|
348
|
+
{
|
|
349
|
+
"record_id": regscale_id,
|
|
350
|
+
"record_module": "securityplans",
|
|
351
|
+
"form_field_id": custom_fields_map["PIA Date"],
|
|
352
|
+
"field_value": format_to_regscale_iso(record.get("pia_date")),
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
if record.get("pta_date"):
|
|
356
|
+
privacy_fields.append(
|
|
357
|
+
{
|
|
358
|
+
"record_id": regscale_id,
|
|
359
|
+
"record_module": "securityplans",
|
|
360
|
+
"form_field_id": custom_fields_map["PTA Date"],
|
|
361
|
+
"field_value": format_to_regscale_iso(record.get("pta_date")),
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
if record.get("sorn_date"):
|
|
365
|
+
privacy_fields.append(
|
|
366
|
+
{
|
|
367
|
+
"record_id": regscale_id,
|
|
368
|
+
"record_module": "securityplans",
|
|
369
|
+
"form_field_id": custom_fields_map["SORN Date"],
|
|
370
|
+
"field_value": format_to_regscale_iso(record.get("sorn_date")),
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
if record.get("sorn_id"):
|
|
374
|
+
privacy_fields.append(
|
|
375
|
+
{
|
|
376
|
+
"record_id": regscale_id,
|
|
377
|
+
"record_module": "securityplans",
|
|
378
|
+
"form_field_id": custom_fields_map["SORN Id"],
|
|
379
|
+
"field_value": str(record.get("sorn_id")),
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
if len(privacy_fields) > 0:
|
|
383
|
+
privacy_fields = fix_form_field_value(privacy_fields)
|
|
384
|
+
FormFieldValue.save_custom_fields(privacy_fields)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def import_csam_authorization(import_ids: Optional[List[int]] = None):
|
|
388
|
+
"""
|
|
389
|
+
Update the Authorization of the SSPs
|
|
390
|
+
This requires a call to the /system/{id}/securityauthorization
|
|
391
|
+
endpoint
|
|
392
|
+
|
|
393
|
+
:param list import_ids: Filtered list of SSPs
|
|
394
|
+
:return: None
|
|
395
|
+
"""
|
|
396
|
+
# Get existing ssps by CSAM Id
|
|
397
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
398
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
399
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
403
|
+
|
|
404
|
+
updated_ssps = []
|
|
405
|
+
field_values = []
|
|
406
|
+
if len(ssps) == 0:
|
|
407
|
+
return
|
|
408
|
+
for index in track(
|
|
409
|
+
range(len(ssps)),
|
|
410
|
+
description=f"Importing {len(ssps)} SSP authorization...",
|
|
411
|
+
):
|
|
412
|
+
ssp = ssps[index]
|
|
413
|
+
csam_id = ssp_map.get(ssp)
|
|
414
|
+
if not csam_id:
|
|
415
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
416
|
+
continue
|
|
417
|
+
else:
|
|
418
|
+
updated_ssps.append(ssp)
|
|
419
|
+
|
|
420
|
+
result = retrieve_from_csam(
|
|
421
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/securityauthorization",
|
|
422
|
+
)
|
|
423
|
+
if len(result) == 0:
|
|
424
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
425
|
+
continue
|
|
426
|
+
# Set the authorization expiration date
|
|
427
|
+
ssp_obj = SecurityPlan.get_object(object_id=ssp)
|
|
428
|
+
if ssp_obj:
|
|
429
|
+
ssp_obj.authorizationTerminationDate = result.get("authorizationExpirationDate")
|
|
430
|
+
ssp_obj.save()
|
|
431
|
+
else:
|
|
432
|
+
logger.debug(f"Failed to retrieve Security Plan id: {ssp}")
|
|
433
|
+
# Get the custom fields
|
|
434
|
+
field_values.append(
|
|
435
|
+
{
|
|
436
|
+
"record_id": ssp,
|
|
437
|
+
"record_module": "securityplans",
|
|
438
|
+
"form_field_id": custom_fields_basic_map["Authorization Process"],
|
|
439
|
+
"field_value": str(result.get("authorizationProcess")),
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
field_values.append(
|
|
443
|
+
{
|
|
444
|
+
"record_id": ssp,
|
|
445
|
+
"record_module": "securityplans",
|
|
446
|
+
"form_field_id": custom_fields_basic_map["ATO Date"],
|
|
447
|
+
"field_value": str(result.get("lastAuthorizationDate")),
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
field_values.append(
|
|
451
|
+
{
|
|
452
|
+
"record_id": ssp,
|
|
453
|
+
"record_module": "securityplans",
|
|
454
|
+
"form_field_id": custom_fields_basic_map["ATO Status"],
|
|
455
|
+
"field_value": str(result.get("authorizationStatus")),
|
|
456
|
+
}
|
|
457
|
+
)
|
|
458
|
+
# Save the Custom Fields
|
|
459
|
+
if len(field_values) > 0:
|
|
460
|
+
field_values = fix_form_field_value(field_values)
|
|
461
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
462
|
+
|
|
463
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with authorization data")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def import_csam_contingency(import_ids: Optional[List[int]] = None):
|
|
467
|
+
"""
|
|
468
|
+
Update the Contingency & IR of the SSPs
|
|
469
|
+
This requires a call to the /systems/<system_id>/continuityresponse
|
|
470
|
+
endpoint
|
|
471
|
+
|
|
472
|
+
:param list import_ids: Filtered list of SSPs
|
|
473
|
+
:return: None
|
|
474
|
+
"""
|
|
475
|
+
# Continuity & IR Fields
|
|
476
|
+
# Goes to "Continuity and Incident Response" Tab
|
|
477
|
+
# /CSAM/api/v1/systems/<system_id>/continuityresponse
|
|
478
|
+
continuity_map = {
|
|
479
|
+
"maximumTolerableDowntime": "MTD",
|
|
480
|
+
"recoveryTimeObjective": "RTO",
|
|
481
|
+
"recoveryPointObjective": "RPO",
|
|
482
|
+
"businessImpactAnalysisDateCompleted": "BIA Completed",
|
|
483
|
+
"businessImpactAnalysisNextDueDate": "BIA Next Due Date",
|
|
484
|
+
"contingencyPlanDateCompleted": "CP Completed",
|
|
485
|
+
"contingencyPlanNextDueDate": "CP Next Due Date",
|
|
486
|
+
"contingencyPlanTrainingDateCompleted": "CP Training Completed",
|
|
487
|
+
"contingencyPlanTrainingNextDueDate": "CP Training Next Due Date",
|
|
488
|
+
"contingencyPlanTestNextDueDate": "CP Test Next Due Date",
|
|
489
|
+
"incidentResponsePlanDateCompleted": "IRP Completed",
|
|
490
|
+
"incidentResponsePlanNextDueDate": "IRP Next Due Date",
|
|
491
|
+
"incidentResponsePlanTrainingDateCompleted": "IRP Training Completed",
|
|
492
|
+
"incidentResponsePlanTrainingNextDueDate": "IRP Training Next Due Date",
|
|
493
|
+
"incidentResponsePlanTestNextDueDate": "IRP Test Next Due Date",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
# /CSAM/api/v1/systems/<system_id>/continuitytest
|
|
497
|
+
# testItem == "Contingency Plan (CP)""
|
|
498
|
+
continuity_test_map = {"testType": "CP Test Type", "dateTested": "CP Date Tested", "outcome": "CP Test Outcome"}
|
|
499
|
+
# testItem == "Incident Response Plan (IRP)"
|
|
500
|
+
irp_test_map = {"testType": "IRP Test Type", "dateTested": "IRP Date Tested", "outcome": "IRP Test Outcome"}
|
|
501
|
+
|
|
502
|
+
# /CSAM/api/v1/systems/{system_id}/additionalstatus
|
|
503
|
+
# name == "Contingency Plan Review"
|
|
504
|
+
contingency_plan_map = {
|
|
505
|
+
"dateCompleted": "CPR Completed",
|
|
506
|
+
"nextDueDate": "CPR Next Due Date",
|
|
507
|
+
"expirationDate": "CPR Expiration Date",
|
|
508
|
+
}
|
|
509
|
+
continuity_ir_fields = []
|
|
510
|
+
continuity_ir_fields = continuity_ir_fields + list(continuity_map.values())
|
|
511
|
+
continuity_ir_fields = continuity_ir_fields + list(continuity_test_map.values())
|
|
512
|
+
continuity_ir_fields = continuity_ir_fields + list(irp_test_map.values())
|
|
513
|
+
continuity_ir_fields = continuity_ir_fields + list(contingency_plan_map.values())
|
|
514
|
+
continuity_ir_fields_map = FormFieldValue.check_custom_fields(
|
|
515
|
+
continuity_ir_fields, "securityplans", "Continuity and Incident Response"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# name == "Document Review Approval"
|
|
519
|
+
# goes to "Document Review" Tab
|
|
520
|
+
doc_approv_map = {
|
|
521
|
+
"dateCompleted": "Doc Review Completed",
|
|
522
|
+
"nextDueDate": "Doc Review Next Due Date",
|
|
523
|
+
"expirationDate": "Doc Review Expiration Date",
|
|
524
|
+
}
|
|
525
|
+
doc_approv_fields = list(doc_approv_map.values())
|
|
526
|
+
doc_approv_fields_map = FormFieldValue.check_custom_fields(doc_approv_fields, "securityplans", "Document Review")
|
|
527
|
+
|
|
528
|
+
# Get existing ssps by CSAM Id
|
|
529
|
+
custom_fields_basic_map = FormFieldValue.check_custom_fields([CSAM_FIELD_NAME], "securityplans", SSP_BASIC_TAB)
|
|
530
|
+
ssp_map = retrieve_ssps_custom_form_map(
|
|
531
|
+
tab_name=SSP_BASIC_TAB, field_form_id=custom_fields_basic_map[CSAM_FIELD_NAME]
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
ssps = import_ids if import_ids else list(ssp_map.keys())
|
|
535
|
+
|
|
536
|
+
updated_ssps = []
|
|
537
|
+
field_values = []
|
|
538
|
+
if len(ssps) == 0:
|
|
539
|
+
return
|
|
540
|
+
for index in track(
|
|
541
|
+
range(len(ssps)),
|
|
542
|
+
description=f"Importing {len(ssps)} SSP contingency data...",
|
|
543
|
+
):
|
|
544
|
+
ssp = ssps[index]
|
|
545
|
+
csam_id = ssp_map.get(ssp)
|
|
546
|
+
if not csam_id:
|
|
547
|
+
logger.error(f"Could not find CSAM ID for SSP id: {ssp}")
|
|
548
|
+
continue
|
|
549
|
+
else:
|
|
550
|
+
updated_ssps.append(ssp)
|
|
551
|
+
|
|
552
|
+
continuity_response = get_continuity_response_fields(
|
|
553
|
+
ssp=ssp, csam_id=csam_id, continuity_ir_fields_map=continuity_ir_fields_map, continuity_map=continuity_map
|
|
554
|
+
)
|
|
555
|
+
field_values = field_values + continuity_response
|
|
556
|
+
|
|
557
|
+
continuity_test = get_continuity_test_fields(
|
|
558
|
+
ssp=ssp,
|
|
559
|
+
csam_id=csam_id,
|
|
560
|
+
continuity_ir_fields_map=continuity_ir_fields_map,
|
|
561
|
+
continuity_test_map=continuity_test_map,
|
|
562
|
+
irp_test_map=irp_test_map,
|
|
563
|
+
)
|
|
564
|
+
field_values = field_values + continuity_test
|
|
565
|
+
|
|
566
|
+
additional_status = get_additional_status_fields(
|
|
567
|
+
ssp=ssp,
|
|
568
|
+
csam_id=csam_id,
|
|
569
|
+
continuity_ir_fields_map=continuity_ir_fields_map,
|
|
570
|
+
contingency_plan_map=contingency_plan_map,
|
|
571
|
+
doc_approv_fields_map=doc_approv_fields_map,
|
|
572
|
+
doc_approv_map=doc_approv_map,
|
|
573
|
+
)
|
|
574
|
+
field_values = field_values + additional_status
|
|
575
|
+
|
|
576
|
+
# Save the Custom Fields
|
|
577
|
+
if len(field_values) > 0:
|
|
578
|
+
field_values = fix_form_field_value(field_values)
|
|
579
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
580
|
+
|
|
581
|
+
logger.info(f"Updated {len(updated_ssps)} Security Plans with contingency data")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def get_continuity_response_fields(ssp: int, csam_id: int, continuity_map: dict, continuity_ir_fields_map: dict):
|
|
585
|
+
# Get the data from /continuityresponse
|
|
586
|
+
result = retrieve_from_csam(
|
|
587
|
+
csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuityresponse",
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
if not result:
|
|
591
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
592
|
+
return []
|
|
593
|
+
|
|
594
|
+
response_data = result[0]
|
|
595
|
+
|
|
596
|
+
# Pre-compute the field mapping to avoid nested lookups
|
|
597
|
+
field_mapping = {
|
|
598
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
599
|
+
for field, mapped_name in continuity_map.items()
|
|
600
|
+
if mapped_name in continuity_ir_fields_map
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
# Build field values with single-level lookup
|
|
604
|
+
return [
|
|
605
|
+
{
|
|
606
|
+
"record_id": ssp,
|
|
607
|
+
"record_module": "securityplans",
|
|
608
|
+
"form_field_id": field_id,
|
|
609
|
+
"field_value": str(response_data[field]),
|
|
610
|
+
}
|
|
611
|
+
for field, field_id in field_mapping.items()
|
|
612
|
+
if field in response_data
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def get_continuity_test_fields(
|
|
617
|
+
ssp: int, csam_id: int, continuity_ir_fields_map: dict, continuity_test_map: dict, irp_test_map: dict
|
|
618
|
+
):
|
|
619
|
+
# Get the data from continuitytest
|
|
620
|
+
results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/continuitytest")
|
|
621
|
+
|
|
622
|
+
if not results:
|
|
623
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
624
|
+
return []
|
|
625
|
+
|
|
626
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
627
|
+
cp_field_mapping = {
|
|
628
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
629
|
+
for field, mapped_name in continuity_test_map.items()
|
|
630
|
+
if mapped_name in continuity_ir_fields_map
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
irp_field_mapping = {
|
|
634
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
635
|
+
for field, mapped_name in irp_test_map.items()
|
|
636
|
+
if mapped_name in continuity_ir_fields_map
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
field_values = []
|
|
640
|
+
for result in results:
|
|
641
|
+
test_item = result.get("testItem")
|
|
642
|
+
|
|
643
|
+
# Process Contingency Plan (CP) fields
|
|
644
|
+
if test_item == "Contingency Plan (CP)":
|
|
645
|
+
field_values.extend(
|
|
646
|
+
[
|
|
647
|
+
{
|
|
648
|
+
"record_id": ssp,
|
|
649
|
+
"record_module": "securityplans",
|
|
650
|
+
"form_field_id": field_id,
|
|
651
|
+
"field_value": str(result[field]),
|
|
652
|
+
}
|
|
653
|
+
for field, field_id in cp_field_mapping.items()
|
|
654
|
+
if field in result
|
|
655
|
+
]
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Process Incident Response Plan (IRP) fields
|
|
659
|
+
elif test_item == "Incident Response Plan (IRP)":
|
|
660
|
+
field_values.extend(
|
|
661
|
+
[
|
|
662
|
+
{
|
|
663
|
+
"record_id": ssp,
|
|
664
|
+
"record_module": "securityplans",
|
|
665
|
+
"form_field_id": field_id,
|
|
666
|
+
"field_value": str(result[field]),
|
|
667
|
+
}
|
|
668
|
+
for field, field_id in irp_field_mapping.items()
|
|
669
|
+
if field in result
|
|
670
|
+
]
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
return field_values
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def get_additional_status_fields(
|
|
677
|
+
ssp: int,
|
|
678
|
+
csam_id: int,
|
|
679
|
+
continuity_ir_fields_map: dict,
|
|
680
|
+
contingency_plan_map: dict,
|
|
681
|
+
doc_approv_fields_map: dict,
|
|
682
|
+
doc_approv_map: dict,
|
|
683
|
+
):
|
|
684
|
+
# Get the data from additional status
|
|
685
|
+
results = retrieve_from_csam(csam_endpoint=f"/CSAM/api/v1/systems/{csam_id}/additionalstatus")
|
|
686
|
+
|
|
687
|
+
if not results:
|
|
688
|
+
logger.error(f"Could not retrieve details for CSAM ID {csam_id}. RegScale SSP id: {ssp}")
|
|
689
|
+
return []
|
|
690
|
+
|
|
691
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
692
|
+
cp_field_mapping = {
|
|
693
|
+
field: continuity_ir_fields_map[mapped_name]
|
|
694
|
+
for field, mapped_name in contingency_plan_map.items()
|
|
695
|
+
if mapped_name in continuity_ir_fields_map
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
doc_field_mapping = {
|
|
699
|
+
field: doc_approv_fields_map[mapped_name]
|
|
700
|
+
for field, mapped_name in doc_approv_map.items()
|
|
701
|
+
if mapped_name in doc_approv_fields_map
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
field_values = []
|
|
705
|
+
for result in results:
|
|
706
|
+
result_name = result.get("name")
|
|
707
|
+
|
|
708
|
+
# Process Contingency Plan Review fields
|
|
709
|
+
if result_name == "Contingency Plan Review":
|
|
710
|
+
field_values.extend(
|
|
711
|
+
[
|
|
712
|
+
{
|
|
713
|
+
"record_id": ssp,
|
|
714
|
+
"record_module": "securityplans",
|
|
715
|
+
"form_field_id": field_id,
|
|
716
|
+
"field_value": str(result[field]),
|
|
717
|
+
}
|
|
718
|
+
for field, field_id in cp_field_mapping.items()
|
|
719
|
+
if field in result
|
|
720
|
+
]
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Process Document Review Approval fields
|
|
724
|
+
elif result_name == "Document Review Approval":
|
|
725
|
+
field_values.extend(
|
|
726
|
+
[
|
|
727
|
+
{
|
|
728
|
+
"record_id": ssp,
|
|
729
|
+
"record_module": "securityplans",
|
|
730
|
+
"form_field_id": field_id,
|
|
731
|
+
"field_value": str(result[field]),
|
|
732
|
+
}
|
|
733
|
+
for field, field_id in doc_field_mapping.items()
|
|
734
|
+
if field in result
|
|
735
|
+
]
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
return field_values
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def update_ssp_general(ssp: SecurityPlan, record: dict, org_map: dict) -> SecurityPlan:
|
|
742
|
+
"""
|
|
743
|
+
Update or Create the SSP Record
|
|
744
|
+
Based upon the values in Record
|
|
745
|
+
|
|
746
|
+
:param SecurityPlan ssp: RegScale Security Plan
|
|
747
|
+
:param dict record: record of values
|
|
748
|
+
:param dict org_map: map of org names to orgId
|
|
749
|
+
:return: SecurityPlan Object
|
|
750
|
+
:return_type: SecurityPlan
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
ssp.otherIdentifier = record["id"]
|
|
754
|
+
ssp.overallCategorization = record["categorization"]
|
|
755
|
+
ssp.confidentiality = record["categorization"]
|
|
756
|
+
ssp.integrity = record["categorization"]
|
|
757
|
+
ssp.availability = record["categorization"]
|
|
758
|
+
ssp.status = record["operationalStatus"]
|
|
759
|
+
ssp.systemType = record["systemType"]
|
|
760
|
+
ssp.description = record["purpose"]
|
|
761
|
+
ssp.defaultAssessmentDays = 0
|
|
762
|
+
if record["organization"] and org_map.get(record["organization"]):
|
|
763
|
+
ssp.orgId = org_map.get(record["organization"])
|
|
764
|
+
|
|
765
|
+
if ssp.id == 0:
|
|
766
|
+
new_ssp = ssp.create()
|
|
767
|
+
else:
|
|
768
|
+
new_ssp = ssp.save()
|
|
769
|
+
|
|
770
|
+
return new_ssp
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def save_ssp_front_matter(results: list, ssp_map: dict, custom_fields_basic_map: dict, org_map: dict) -> list:
|
|
774
|
+
"""
|
|
775
|
+
Save the SSP data from the /systems endpoint
|
|
776
|
+
|
|
777
|
+
:param list results: list of results from CSAM
|
|
778
|
+
:param dict ssp_map: map of existing SSPs in RegScale
|
|
779
|
+
:param dict custom_fields_basic_map: map of custom fields in RegScale
|
|
780
|
+
:param dict org_map: map of existing orgs in RegScale
|
|
781
|
+
:return: list of updated SSPs
|
|
782
|
+
:return_type: List[SecurityPlan]
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
updated_ssps = []
|
|
786
|
+
for index in track(
|
|
787
|
+
range(len(results)),
|
|
788
|
+
description=f"Importing {len(results)} SSP front matter...",
|
|
789
|
+
):
|
|
790
|
+
result = results[index]
|
|
791
|
+
|
|
792
|
+
# Get the existing SSP:
|
|
793
|
+
ssp_id = ssp_map.get(result["externalId"])
|
|
794
|
+
if ssp_id:
|
|
795
|
+
ssp = SecurityPlan.get_object(ssp_id)
|
|
796
|
+
else:
|
|
797
|
+
ssp = SecurityPlan(systemName=result["name"])
|
|
798
|
+
# Update the SSP
|
|
799
|
+
ssp = update_ssp_general(ssp, result, org_map)
|
|
800
|
+
|
|
801
|
+
# Grab the Custom Fields
|
|
802
|
+
field_values = set_front_matter_fields(ssp=ssp, result=result, custom_fields_basic_map=custom_fields_basic_map)
|
|
803
|
+
|
|
804
|
+
# System Custom Fields
|
|
805
|
+
field_values = fix_form_field_value(field_values)
|
|
806
|
+
FormFieldValue.save_custom_fields(field_values)
|
|
807
|
+
updated_ssps.append(ssp)
|
|
808
|
+
logger.info(f"Updated {len(results)} Security Plans Front Matter")
|
|
809
|
+
return updated_ssps
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def set_front_matter_fields(ssp: SecurityPlan, result: dict, custom_fields_basic_map: dict) -> list:
|
|
813
|
+
"""
|
|
814
|
+
parse the front matter custom fields
|
|
815
|
+
and return a list of field values to be saved
|
|
816
|
+
|
|
817
|
+
:param SecurityPlan ssp: RegScale Security Plan object
|
|
818
|
+
:param dict result: response from CSAM
|
|
819
|
+
:param dict custom_fields_basic_map: map of basic custom fields
|
|
820
|
+
:return: list of dictionaries with field values
|
|
821
|
+
:return_type: list
|
|
822
|
+
"""
|
|
823
|
+
custom_fields_financial_list = [
|
|
824
|
+
"Financial System",
|
|
825
|
+
"omb Exhibit",
|
|
826
|
+
"Investment Name",
|
|
827
|
+
"Portfolio",
|
|
828
|
+
"Prior Fy Funding",
|
|
829
|
+
"Current Fy Funding",
|
|
830
|
+
"Next Fy Funding",
|
|
831
|
+
"Funding Import Status",
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
custom_fields_financial_map = FormFieldValue.check_custom_fields(
|
|
835
|
+
custom_fields_financial_list, "securityplans", SSP_FINANCIAL_TAB
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
custom_fields_map = {
|
|
839
|
+
"acronym": "acronym",
|
|
840
|
+
"classification": "Classification",
|
|
841
|
+
"fismaReportable": "FISMA Reportable",
|
|
842
|
+
"contractorSystem": "Contractor System",
|
|
843
|
+
"criticalInfrastructure": "Critical Infrastructure",
|
|
844
|
+
"missionCritical": "Mission Essential",
|
|
845
|
+
"uiiCode": "uiiCode",
|
|
846
|
+
}
|
|
847
|
+
custom_fields_fin_map = {
|
|
848
|
+
"financialSystem": "Financial System",
|
|
849
|
+
"ombExhibit": "omb Exhibit",
|
|
850
|
+
"investmentName": "Investment Name",
|
|
851
|
+
"portfolio": "Portfolio",
|
|
852
|
+
"priorFyFunding": "Prior Fy Funding",
|
|
853
|
+
"currentFyFunding": "Current Fy Funding",
|
|
854
|
+
"nextFyFunding": "Next Fy Funding",
|
|
855
|
+
"fundingImportStatus": "Funding Import Status",
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
# Pre-compute field mappings to avoid nested lookups
|
|
859
|
+
basic_field_mapping = {
|
|
860
|
+
field: custom_fields_basic_map[mapped_name]
|
|
861
|
+
for field, mapped_name in custom_fields_map.items()
|
|
862
|
+
if mapped_name in custom_fields_basic_map and field in result
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
financial_field_mapping = {
|
|
866
|
+
field: custom_fields_financial_map[mapped_name]
|
|
867
|
+
for field, mapped_name in custom_fields_fin_map.items()
|
|
868
|
+
if mapped_name in custom_fields_financial_map and field in result
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
# Start with required ID fields
|
|
872
|
+
field_values = [
|
|
873
|
+
{
|
|
874
|
+
"record_id": ssp.id,
|
|
875
|
+
"record_module": "securityplans",
|
|
876
|
+
"form_field_id": custom_fields_basic_map[FISMA_FIELD_NAME],
|
|
877
|
+
"field_value": str(result["externalId"]),
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"record_id": ssp.id,
|
|
881
|
+
"record_module": "securityplans",
|
|
882
|
+
"form_field_id": custom_fields_basic_map[CSAM_FIELD_NAME],
|
|
883
|
+
"field_value": str(result["id"]),
|
|
884
|
+
},
|
|
885
|
+
]
|
|
886
|
+
|
|
887
|
+
# Process basic tab fields
|
|
888
|
+
field_values.extend(_create_basic_field_values(ssp.id, result, basic_field_mapping))
|
|
889
|
+
|
|
890
|
+
# Process financial tab fields
|
|
891
|
+
field_values.extend(_create_financial_field_values(ssp.id, result, financial_field_mapping))
|
|
892
|
+
|
|
893
|
+
return field_values
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def _create_basic_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
|
|
897
|
+
"""Helper function to create basic field values with proper type handling"""
|
|
898
|
+
field_values = []
|
|
899
|
+
for field, field_id in field_mapping.items():
|
|
900
|
+
value = result.get(field)
|
|
901
|
+
if isinstance(value, bool):
|
|
902
|
+
field_value = "Yes" if value else "No"
|
|
903
|
+
else:
|
|
904
|
+
field_value = str(value)
|
|
905
|
+
|
|
906
|
+
field_values.append(
|
|
907
|
+
{
|
|
908
|
+
"record_id": record_id,
|
|
909
|
+
"record_module": "securityplans",
|
|
910
|
+
"form_field_id": field_id,
|
|
911
|
+
"field_value": field_value,
|
|
912
|
+
}
|
|
913
|
+
)
|
|
914
|
+
return field_values
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _create_financial_field_values(record_id: int, result: dict, field_mapping: dict) -> list:
|
|
918
|
+
"""Helper function to create financial field values with proper handling of funding fields"""
|
|
919
|
+
funding_fields = ["priorFyFunding", "currentFyFunding", "nextFyFunding"]
|
|
920
|
+
|
|
921
|
+
field_values = []
|
|
922
|
+
for field, field_id in field_mapping.items():
|
|
923
|
+
value = result.get(field)
|
|
924
|
+
# Handle blank dollar values
|
|
925
|
+
if field in funding_fields:
|
|
926
|
+
field_value = str(value) if value else "0"
|
|
927
|
+
else:
|
|
928
|
+
field_value = str(value)
|
|
929
|
+
|
|
930
|
+
field_values.append(
|
|
931
|
+
{
|
|
932
|
+
"record_id": record_id,
|
|
933
|
+
"record_module": "securityplans",
|
|
934
|
+
"form_field_id": field_id,
|
|
935
|
+
"field_value": field_value,
|
|
936
|
+
}
|
|
937
|
+
)
|
|
938
|
+
return field_values
|