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
|
@@ -84,6 +84,35 @@ class IssueStatus(str, Enum):
|
|
|
84
84
|
return self.value
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
class IssueIdentification(str, Enum):
|
|
88
|
+
"""Issue Identification"""
|
|
89
|
+
|
|
90
|
+
A123Review = "A-123 Review"
|
|
91
|
+
AssessmentAuditInternal = "Assessment/Audit (Internal)"
|
|
92
|
+
AssessmentAuditExternal = "Assessment/Audit (External)"
|
|
93
|
+
CriticalControlReview = "Critical Control Review"
|
|
94
|
+
FDCCUSGCB = "FDCC/USGCB"
|
|
95
|
+
GAOAudit = "GAO Audit"
|
|
96
|
+
IGAudit = "IG Audit"
|
|
97
|
+
IncidentResponseLessonsLearned = "Incident Response Lessons Learned"
|
|
98
|
+
ITAR = "ITAR"
|
|
99
|
+
PenetrationTest = "Penetration Test"
|
|
100
|
+
RiskAssessment = "Risk Assessment"
|
|
101
|
+
SecurityAuthorization = "Security Authorization"
|
|
102
|
+
SecurityControlAssessment = "Security Control Assessment"
|
|
103
|
+
VulnerabilityAssessment = "Vulnerability Assessment"
|
|
104
|
+
Other = "Other"
|
|
105
|
+
|
|
106
|
+
def __str__(self) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Return the value of the Enum as a string
|
|
109
|
+
|
|
110
|
+
:return: The value of the Enum as a string
|
|
111
|
+
:rtype: str
|
|
112
|
+
"""
|
|
113
|
+
return self.value
|
|
114
|
+
|
|
115
|
+
|
|
87
116
|
class Issue(RegScaleModel):
|
|
88
117
|
"""Issue Model"""
|
|
89
118
|
|
|
@@ -455,6 +484,7 @@ class Issue(RegScaleModel):
|
|
|
455
484
|
key: str,
|
|
456
485
|
start_date: Union[str, datetime.datetime, None] = None,
|
|
457
486
|
dt_format: Optional[str] = "%Y-%m-%dT%H:%M:%S",
|
|
487
|
+
default_days: Optional[int] = 60,
|
|
458
488
|
) -> str:
|
|
459
489
|
"""
|
|
460
490
|
Function to return due date based on the severity of the issue; the values are in the init.yaml
|
|
@@ -465,6 +495,7 @@ class Issue(RegScaleModel):
|
|
|
465
495
|
:param str key: The key to use for init.yaml from the issues section to determine the due date
|
|
466
496
|
:param Union[str, datetime.datetime, None] start_date: The date to start the due date calculation from, defaults to current date
|
|
467
497
|
:param Optional[str] dt_format: String of the date format to use, defaults to "%Y-%m-%dT%H:%M:%S"
|
|
498
|
+
:param Optional[int] default_days: Default number of days to return if no values match, defaults to 60
|
|
468
499
|
:return: Due date for the issue
|
|
469
500
|
:rtype: str
|
|
470
501
|
"""
|
|
@@ -479,19 +510,18 @@ class Issue(RegScaleModel):
|
|
|
479
510
|
if isinstance(severity, IssueSeverity):
|
|
480
511
|
severity = severity.value
|
|
481
512
|
elif severity.lower() not in [severity.value.lower() for severity in IssueSeverity]:
|
|
482
|
-
severity =
|
|
513
|
+
severity = cls.assign_severity(severity)
|
|
483
514
|
|
|
484
515
|
if severity == IssueSeverity.Critical.value:
|
|
485
|
-
days = cls._get_days_for_values(["critical"], config, key)
|
|
486
|
-
start_date = start_date + datetime.timedelta(days=days)
|
|
516
|
+
days = cls._get_days_for_values(["critical"], config, key, default_days)
|
|
487
517
|
elif severity == IssueSeverity.High.value:
|
|
488
|
-
days = cls._get_days_for_values(["high"], config, key)
|
|
518
|
+
days = cls._get_days_for_values(["high"], config, key, default_days)
|
|
489
519
|
elif severity == IssueSeverity.Moderate.value:
|
|
490
|
-
days = cls._get_days_for_values(["moderate", "medium"], config, key)
|
|
520
|
+
days = cls._get_days_for_values(["moderate", "medium"], config, key, default_days)
|
|
491
521
|
elif severity == IssueSeverity.Low.value:
|
|
492
|
-
days = cls._get_days_for_values(["low", "minor"], config, key)
|
|
522
|
+
days = cls._get_days_for_values(["low", "minor"], config, key, default_days)
|
|
493
523
|
else:
|
|
494
|
-
days =
|
|
524
|
+
days = default_days
|
|
495
525
|
due_date = start_date + datetime.timedelta(days=days)
|
|
496
526
|
return due_date.strftime(dt_format)
|
|
497
527
|
|
|
@@ -526,6 +556,7 @@ class Issue(RegScaleModel):
|
|
|
526
556
|
"low": IssueSeverity.Low.value,
|
|
527
557
|
"moderate": IssueSeverity.Moderate.value,
|
|
528
558
|
"high": IssueSeverity.High.value,
|
|
559
|
+
"critical": IssueSeverity.Critical.value,
|
|
529
560
|
}
|
|
530
561
|
severity = IssueSeverity.NotAssigned.value
|
|
531
562
|
# see if the value is an int or float
|
|
@@ -542,8 +573,10 @@ class Issue(RegScaleModel):
|
|
|
542
573
|
severity = severity_levels["low"]
|
|
543
574
|
elif value.lower() in ["medium", "moderate", "major"]:
|
|
544
575
|
severity = severity_levels["moderate"]
|
|
545
|
-
elif value.lower() in ["high", "
|
|
576
|
+
elif value.lower() in ["high", "highest", "blocker"]:
|
|
546
577
|
severity = severity_levels["high"]
|
|
578
|
+
elif value.lower() in ["critical"]:
|
|
579
|
+
severity = severity_levels["critical"]
|
|
547
580
|
elif value in list(severity_levels.values()):
|
|
548
581
|
severity = value
|
|
549
582
|
return severity
|
|
@@ -914,103 +947,285 @@ class Issue(RegScaleModel):
|
|
|
914
947
|
"""
|
|
915
948
|
import logging
|
|
916
949
|
|
|
917
|
-
cache_disabled = cls._is_cache_disabled()
|
|
918
|
-
use_cache: bool = not cache_disabled
|
|
919
|
-
|
|
920
950
|
logger = logging.getLogger("regscale")
|
|
951
|
+
|
|
921
952
|
# Check cache first
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
logger.info(f"Using cached open issues data for security plan {plan_id}")
|
|
926
|
-
return cached_data
|
|
927
|
-
|
|
928
|
-
# Performance optimization: Use larger batch size and optimize query
|
|
929
|
-
take = 50 # Increased from 50 to reduce API roundtrips
|
|
930
|
-
skip = 0
|
|
931
|
-
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
953
|
+
cached_data = cls._check_cache(plan_id, logger)
|
|
954
|
+
if cached_data is not None:
|
|
955
|
+
return cached_data
|
|
932
956
|
|
|
933
|
-
|
|
934
|
-
|
|
957
|
+
# Fetch open issues from API
|
|
958
|
+
control_issues = cls._fetch_open_issues_from_api(plan_id, is_component, logger)
|
|
959
|
+
|
|
960
|
+
# Cache the results if caching is enabled
|
|
961
|
+
if not cls._is_cache_disabled():
|
|
962
|
+
cls._cache_data(plan_id, control_issues)
|
|
963
|
+
|
|
964
|
+
return control_issues
|
|
965
|
+
|
|
966
|
+
@classmethod
|
|
967
|
+
def _check_cache(cls, plan_id: int, logger) -> Optional[Dict[int, List[OpenIssueDict]]]:
|
|
968
|
+
"""
|
|
969
|
+
Check cache for open issues data
|
|
970
|
+
|
|
971
|
+
:param int plan_id: The ID of the parent
|
|
972
|
+
:param logger: Logger instance
|
|
973
|
+
:return: Cached data if available and valid, None otherwise
|
|
974
|
+
:rtype: Optional[Dict[int, List[OpenIssueDict]]]
|
|
975
|
+
"""
|
|
976
|
+
if cls._is_cache_disabled():
|
|
977
|
+
return None
|
|
978
|
+
|
|
979
|
+
cached_data = cls._get_from_cache(plan_id)
|
|
980
|
+
if cached_data is not None:
|
|
981
|
+
logger.info(f"Using cached open issues data for security plan {plan_id}")
|
|
982
|
+
return cached_data
|
|
983
|
+
|
|
984
|
+
@classmethod
|
|
985
|
+
def _fetch_open_issues_from_api(cls, plan_id: int, is_component: bool, logger) -> Dict[int, List[OpenIssueDict]]:
|
|
986
|
+
"""
|
|
987
|
+
Fetch open issues from API with pagination
|
|
935
988
|
|
|
989
|
+
:param int plan_id: The ID of the parent
|
|
990
|
+
:param bool is_component: Whether parent is a component
|
|
991
|
+
:param logger: Logger instance
|
|
992
|
+
:return: Dictionary of control IDs to open issues
|
|
993
|
+
:rtype: Dict[int, List[OpenIssueDict]]
|
|
994
|
+
"""
|
|
995
|
+
start_time = time.time()
|
|
936
996
|
module_str = "component" if is_component else "security plan"
|
|
937
|
-
logger.info(
|
|
938
|
-
|
|
939
|
-
)
|
|
940
|
-
|
|
997
|
+
logger.info(f"Fetching open issues for controls and for {module_str} {plan_id}...")
|
|
998
|
+
|
|
999
|
+
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
1000
|
+
|
|
1001
|
+
try:
|
|
1002
|
+
total_fetched = cls._paginate_and_process_issues(plan_id, is_component, control_issues, logger)
|
|
1003
|
+
cls._log_completion(plan_id, total_fetched, len(control_issues), start_time, logger)
|
|
1004
|
+
except Exception as e:
|
|
1005
|
+
logger.error(f"Error fetching open issues for security plan {plan_id}: {e}")
|
|
1006
|
+
return defaultdict(list)
|
|
1007
|
+
|
|
1008
|
+
return control_issues
|
|
1009
|
+
|
|
1010
|
+
@classmethod
|
|
1011
|
+
def _paginate_and_process_issues(
|
|
1012
|
+
cls,
|
|
1013
|
+
plan_id: int,
|
|
1014
|
+
is_component: bool,
|
|
1015
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1016
|
+
logger,
|
|
1017
|
+
) -> int:
|
|
1018
|
+
"""
|
|
1019
|
+
Paginate through API results and process issues using concurrent requests
|
|
1020
|
+
|
|
1021
|
+
:param int plan_id: The ID of the parent
|
|
1022
|
+
:param bool is_component: Whether parent is a component
|
|
1023
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate with results
|
|
1024
|
+
:param logger: Logger instance
|
|
1025
|
+
:return: Total number of items fetched
|
|
1026
|
+
:rtype: int
|
|
1027
|
+
"""
|
|
1028
|
+
take = 50
|
|
1029
|
+
supports_multiple_controls = cls.is_multiple_controls_supported()
|
|
1030
|
+
fields = cls._get_query_fields(supports_multiple_controls)
|
|
1031
|
+
|
|
1032
|
+
# First fetch to get total count
|
|
1033
|
+
query = cls._build_query(plan_id, is_component, 0, take, fields)
|
|
1034
|
+
response = cls._get_api_handler().graph(query)
|
|
1035
|
+
|
|
1036
|
+
items = response.get(cls.get_module_string(), {}).get("items", [])
|
|
1037
|
+
total_count = response.get(cls.get_module_string(), {}).get("totalCount", 0)
|
|
1038
|
+
|
|
1039
|
+
# Process first page
|
|
1040
|
+
cls._process_issue_items(items, supports_multiple_controls, control_issues)
|
|
1041
|
+
logger.info("Fetched first page with %d items, total: %d", len(items), total_count)
|
|
1042
|
+
|
|
1043
|
+
# Use concurrent pagination for remaining pages if there are more
|
|
1044
|
+
if total_count > take:
|
|
1045
|
+
logger.info("Fetching remaining pages concurrently...")
|
|
1046
|
+
from regscale.core.utils.async_graphql_client import run_async_paginated_query
|
|
1047
|
+
|
|
1048
|
+
# Get API credentials
|
|
1049
|
+
api_handler = cls._get_api_handler()
|
|
1050
|
+
domain = api_handler.config.get("domain", "")
|
|
1051
|
+
token = api_handler.config.get("token", "")
|
|
1052
|
+
endpoint = f"{domain}/graphql"
|
|
1053
|
+
headers = {"Authorization": f"{token}"}
|
|
1054
|
+
|
|
1055
|
+
# Create query builder
|
|
1056
|
+
def query_builder(skip: int, page_size: int) -> str:
|
|
1057
|
+
return cls._build_query(plan_id, is_component, skip, page_size, fields)
|
|
1058
|
+
|
|
1059
|
+
# Define token refresh callback
|
|
1060
|
+
def token_refresh() -> str:
|
|
1061
|
+
# Re-read token from config in case it was refreshed
|
|
1062
|
+
return api_handler.config.get("token", "")
|
|
1063
|
+
|
|
1064
|
+
# Fetch remaining pages concurrently
|
|
1065
|
+
remaining_items = run_async_paginated_query(
|
|
1066
|
+
endpoint=endpoint,
|
|
1067
|
+
headers=headers,
|
|
1068
|
+
query_builder=query_builder,
|
|
1069
|
+
topic_key=cls.get_module_string(),
|
|
1070
|
+
total_count=total_count - take, # Subtract first page
|
|
1071
|
+
page_size=take,
|
|
1072
|
+
starting_skip=take, # Start from second page
|
|
1073
|
+
max_concurrent=5,
|
|
1074
|
+
timeout=60,
|
|
1075
|
+
task_name="Open Issues",
|
|
1076
|
+
token_refresh_callback=token_refresh,
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
# Process remaining items
|
|
1080
|
+
for batch_items in [remaining_items[i : i + 100] for i in range(0, len(remaining_items), 100)]:
|
|
1081
|
+
cls._process_issue_items(batch_items, supports_multiple_controls, control_issues)
|
|
1082
|
+
logger.info("Fetched %d items from remaining pages", len(remaining_items))
|
|
1083
|
+
|
|
1084
|
+
return total_count
|
|
1085
|
+
|
|
1086
|
+
@classmethod
|
|
1087
|
+
def _get_query_fields(cls, supports_multiple_controls: bool) -> str:
|
|
1088
|
+
"""
|
|
1089
|
+
Get GraphQL query fields based on control support
|
|
941
1090
|
|
|
942
|
-
|
|
1091
|
+
:param bool supports_multiple_controls: Whether multiple controls are supported
|
|
1092
|
+
:return: GraphQL field selection string
|
|
1093
|
+
:rtype: str
|
|
1094
|
+
"""
|
|
943
1095
|
if supports_multiple_controls:
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
fields = "id, controlId, otherIdentifier, integrationFindingId"
|
|
1096
|
+
return "id, otherIdentifier, integrationFindingId, controlImplementations { id }"
|
|
1097
|
+
return "id, controlId, otherIdentifier, integrationFindingId"
|
|
947
1098
|
|
|
948
|
-
|
|
1099
|
+
@classmethod
|
|
1100
|
+
def _build_query(cls, plan_id: int, is_component: bool, skip: int, take: int, fields: str) -> str:
|
|
1101
|
+
"""
|
|
1102
|
+
Build GraphQL query for fetching open issues
|
|
949
1103
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}}
|
|
1104
|
+
:param int plan_id: The ID of the parent
|
|
1105
|
+
:param bool is_component: Whether parent is a component
|
|
1106
|
+
:param int skip: Number of items to skip
|
|
1107
|
+
:param int take: Number of items to take
|
|
1108
|
+
:param str fields: GraphQL fields to select
|
|
1109
|
+
:return: GraphQL query string
|
|
1110
|
+
:rtype: str
|
|
1111
|
+
"""
|
|
1112
|
+
parent_field = "componentId" if is_component else "securityPlanId"
|
|
1113
|
+
return f"""
|
|
1114
|
+
query GetOpenIssuesByPlanOrComponent {{
|
|
1115
|
+
{cls.get_module_string()}(
|
|
1116
|
+
skip: {skip},
|
|
1117
|
+
take: {take},
|
|
1118
|
+
where: {{
|
|
1119
|
+
{parent_field}: {{eq: {plan_id}}},
|
|
1120
|
+
status: {{eq: "Open"}}
|
|
966
1121
|
}}
|
|
967
|
-
|
|
1122
|
+
) {{
|
|
1123
|
+
items {{ {fields} }}
|
|
1124
|
+
pageInfo {{ hasNextPage }}
|
|
1125
|
+
totalCount
|
|
1126
|
+
}}
|
|
1127
|
+
}}
|
|
1128
|
+
"""
|
|
968
1129
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1130
|
+
@classmethod
|
|
1131
|
+
def _log_progress(cls, skip: int, take: int, items_count: int, total_count: int, logger) -> None:
|
|
1132
|
+
"""
|
|
1133
|
+
Log progress for large datasets
|
|
972
1134
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1135
|
+
:param int skip: Number of items skipped
|
|
1136
|
+
:param int take: Batch size
|
|
1137
|
+
:param int items_count: Number of items in current batch
|
|
1138
|
+
:param int total_count: Total count of items
|
|
1139
|
+
:param logger: Logger instance
|
|
1140
|
+
:rtype: None
|
|
1141
|
+
"""
|
|
1142
|
+
if total_count > 1000:
|
|
1143
|
+
logger.info(
|
|
1144
|
+
f"Processing batch {skip // take + 1} - fetched {items_count} items ({skip + items_count}/{total_count})"
|
|
1145
|
+
)
|
|
978
1146
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1147
|
+
@classmethod
|
|
1148
|
+
def _process_issue_items(
|
|
1149
|
+
cls,
|
|
1150
|
+
items: List[Dict[str, Any]],
|
|
1151
|
+
supports_multiple_controls: bool,
|
|
1152
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1153
|
+
) -> None:
|
|
1154
|
+
"""
|
|
1155
|
+
Process issue items and populate control_issues dictionary
|
|
985
1156
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1157
|
+
:param List[Dict[str, Any]] items: List of issue items from API
|
|
1158
|
+
:param bool supports_multiple_controls: Whether multiple controls are supported
|
|
1159
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1160
|
+
:rtype: None
|
|
1161
|
+
"""
|
|
1162
|
+
for item in items:
|
|
1163
|
+
issue_dict = OpenIssueDict(
|
|
1164
|
+
id=item["id"],
|
|
1165
|
+
otherIdentifier=item.get("otherIdentifier", ""),
|
|
1166
|
+
integrationFindingId=item.get("integrationFindingId", ""),
|
|
1167
|
+
)
|
|
991
1168
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1169
|
+
if supports_multiple_controls:
|
|
1170
|
+
cls._add_issue_to_multiple_controls(item, issue_dict, control_issues)
|
|
1171
|
+
else:
|
|
1172
|
+
cls._add_issue_to_single_control(item, issue_dict, control_issues)
|
|
996
1173
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1174
|
+
@classmethod
|
|
1175
|
+
def _add_issue_to_multiple_controls(
|
|
1176
|
+
cls,
|
|
1177
|
+
item: Dict[str, Any],
|
|
1178
|
+
issue_dict: OpenIssueDict,
|
|
1179
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1180
|
+
) -> None:
|
|
1181
|
+
"""
|
|
1182
|
+
Add issue to multiple control implementations
|
|
1001
1183
|
|
|
1184
|
+
:param Dict[str, Any] item: Issue item from API
|
|
1185
|
+
:param OpenIssueDict issue_dict: Issue dictionary
|
|
1186
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1187
|
+
:rtype: None
|
|
1188
|
+
"""
|
|
1189
|
+
if item.get("controlImplementations"):
|
|
1190
|
+
for control in item.get("controlImplementations", []):
|
|
1191
|
+
control_issues[control["id"]].append(issue_dict)
|
|
1192
|
+
|
|
1193
|
+
@classmethod
|
|
1194
|
+
def _add_issue_to_single_control(
|
|
1195
|
+
cls,
|
|
1196
|
+
item: Dict[str, Any],
|
|
1197
|
+
issue_dict: OpenIssueDict,
|
|
1198
|
+
control_issues: Dict[int, List[OpenIssueDict]],
|
|
1199
|
+
) -> None:
|
|
1200
|
+
"""
|
|
1201
|
+
Add issue to single control
|
|
1202
|
+
|
|
1203
|
+
:param Dict[str, Any] item: Issue item from API
|
|
1204
|
+
:param OpenIssueDict issue_dict: Issue dictionary
|
|
1205
|
+
:param Dict[int, List[OpenIssueDict]] control_issues: Dictionary to populate
|
|
1206
|
+
:rtype: None
|
|
1207
|
+
"""
|
|
1208
|
+
if item.get("controlId"):
|
|
1209
|
+
control_issues[item["controlId"]].append(issue_dict)
|
|
1210
|
+
|
|
1211
|
+
@classmethod
|
|
1212
|
+
def _log_completion(cls, plan_id: int, total_fetched: int, control_count: int, start_time: float, logger) -> None:
|
|
1213
|
+
"""
|
|
1214
|
+
Log completion statistics
|
|
1215
|
+
|
|
1216
|
+
:param int plan_id: The ID of the parent
|
|
1217
|
+
:param int total_fetched: Total number of items fetched
|
|
1218
|
+
:param int control_count: Number of controls with issues
|
|
1219
|
+
:param float start_time: Start time of the operation
|
|
1220
|
+
:param logger: Logger instance
|
|
1221
|
+
:rtype: None
|
|
1222
|
+
"""
|
|
1002
1223
|
elapsed_time = time.time() - start_time
|
|
1003
1224
|
logger.info(
|
|
1004
|
-
f"Finished fetching {total_fetched} open issue(s) for {
|
|
1225
|
+
f"Finished fetching {total_fetched} open issue(s) for {control_count} control(s) "
|
|
1005
1226
|
f"in security plan {plan_id} - took {elapsed_time:.2f} seconds"
|
|
1006
1227
|
)
|
|
1007
1228
|
|
|
1008
|
-
# Cache the results
|
|
1009
|
-
if use_cache:
|
|
1010
|
-
cls._cache_data(plan_id, control_issues)
|
|
1011
|
-
|
|
1012
|
-
return control_issues
|
|
1013
|
-
|
|
1014
1229
|
@classmethod
|
|
1015
1230
|
def get_sort_position_dict(cls) -> Dict[str, int]:
|
|
1016
1231
|
"""
|
|
@@ -1100,36 +1315,20 @@ class Issue(RegScaleModel):
|
|
|
1100
1315
|
}
|
|
1101
1316
|
|
|
1102
1317
|
@classmethod
|
|
1103
|
-
def get_enum_values(cls, field_name: str) -> List[Union[IssueSeverity, IssueStatus, str]]:
|
|
1318
|
+
def get_enum_values(cls, field_name: str) -> List[Union[IssueSeverity, IssueStatus, IssueIdentification, str]]:
|
|
1104
1319
|
"""
|
|
1105
1320
|
Overrides the base method.
|
|
1106
1321
|
|
|
1107
1322
|
:param str field_name: The property name to provide enum values for
|
|
1108
1323
|
:return: List of enum values or strings
|
|
1109
|
-
:rtype: List[Union[IssueSeverity, IssueStatus, str]]
|
|
1324
|
+
:rtype: List[Union[IssueSeverity, IssueStatus, IssueIdentification, str]]
|
|
1110
1325
|
"""
|
|
1111
1326
|
if field_name == "severityLevel":
|
|
1112
1327
|
return [severity.__str__() for severity in IssueSeverity]
|
|
1113
1328
|
if field_name == "status":
|
|
1114
1329
|
return [status.__str__() for status in IssueStatus]
|
|
1115
1330
|
if field_name == "identification":
|
|
1116
|
-
return [
|
|
1117
|
-
"A-123 Review",
|
|
1118
|
-
"Assessment/Audit (External)",
|
|
1119
|
-
"Assessment/Audit (Internal)",
|
|
1120
|
-
"Critical Control Review",
|
|
1121
|
-
"FDCC/USGCB",
|
|
1122
|
-
"GAO Audit",
|
|
1123
|
-
"IG Audit",
|
|
1124
|
-
"Incidnet Response Lessons Learned",
|
|
1125
|
-
"ITAR",
|
|
1126
|
-
"Other",
|
|
1127
|
-
"Penetration Test",
|
|
1128
|
-
"Risk Assessment",
|
|
1129
|
-
"Security Authorization",
|
|
1130
|
-
"Security Control Assessment",
|
|
1131
|
-
"Vulnerability Assessment",
|
|
1132
|
-
]
|
|
1331
|
+
return [identification.__str__() for identification in IssueIdentification]
|
|
1133
1332
|
return cls.get_bool_enums(field_name)
|
|
1134
1333
|
|
|
1135
1334
|
@classmethod
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"""Class for milestone model in RegScale platform"""
|
|
4
4
|
|
|
5
5
|
from typing import Optional
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
from pydantic import Field, field_validator, model_validator
|
|
7
8
|
|
|
8
9
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
9
10
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
@@ -14,27 +15,45 @@ class Milestone(RegScaleModel):
|
|
|
14
15
|
|
|
15
16
|
_module_slug = "milestones"
|
|
16
17
|
_module_string = "milestones"
|
|
18
|
+
_unique_fields = ["title", "parentModule", "parentID"]
|
|
19
|
+
_parent_id_field = "parentID"
|
|
17
20
|
|
|
18
21
|
title: str
|
|
19
22
|
id: int = 0
|
|
20
23
|
isPublic: Optional[bool] = True
|
|
21
24
|
milestoneDate: Optional[str] = Field(default_factory=get_current_datetime)
|
|
22
25
|
responsiblePersonId: Optional[str] = None
|
|
23
|
-
predecessorStepId: Optional[int] =
|
|
26
|
+
predecessorStepId: Optional[int] = None
|
|
24
27
|
completed: Optional[bool] = False
|
|
25
|
-
dateCompleted: Optional[str] =
|
|
28
|
+
dateCompleted: Optional[str] = None
|
|
26
29
|
notes: Optional[str] = ""
|
|
27
30
|
parentID: Optional[int] = None
|
|
28
31
|
parentModule: str = ""
|
|
29
32
|
|
|
30
|
-
@
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
@field_validator("milestoneDate")
|
|
34
|
+
@classmethod
|
|
35
|
+
def validate_milestone_date(cls, v: Optional[str]) -> str:
|
|
36
|
+
"""Ensure milestoneDate is never empty."""
|
|
37
|
+
if not v or v == "":
|
|
38
|
+
return get_current_datetime()
|
|
39
|
+
return v
|
|
40
|
+
|
|
41
|
+
@field_validator("dateCompleted")
|
|
42
|
+
@classmethod
|
|
43
|
+
def set_date_completed(cls, v: Optional[str], info) -> Optional[str]:
|
|
44
|
+
"""Set dateCompleted based on completed field."""
|
|
45
|
+
completed = info.data.get("completed", False)
|
|
46
|
+
if completed and (v is None or v == ""):
|
|
47
|
+
return get_current_datetime()
|
|
48
|
+
if not completed:
|
|
49
|
+
return None
|
|
50
|
+
return v
|
|
51
|
+
|
|
52
|
+
@model_validator(mode="after")
|
|
53
|
+
def validate_completion(self):
|
|
54
|
+
"""Ensure dateCompleted is set when completed is True."""
|
|
55
|
+
if self.completed and (self.dateCompleted is None or self.dateCompleted == ""):
|
|
56
|
+
self.dateCompleted = get_current_datetime()
|
|
57
|
+
elif not self.completed:
|
|
58
|
+
self.dateCompleted = None
|
|
59
|
+
return self
|
|
@@ -15,7 +15,10 @@ class Organization(RegScaleModel):
|
|
|
15
15
|
id: int = 0
|
|
16
16
|
name: Optional[str] = ""
|
|
17
17
|
description: Optional[str] = ""
|
|
18
|
+
orgCode: Optional[str] = ""
|
|
19
|
+
orgUrl: Optional[str] = ""
|
|
18
20
|
status: Optional[str] = "Active"
|
|
21
|
+
externalId: Optional[str] = ""
|
|
19
22
|
|
|
20
23
|
@staticmethod
|
|
21
24
|
def _get_additional_endpoints() -> dict:
|