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 @@
|
|
|
1
|
+
"""Tests for Wiz core modules"""
|
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Comprehensive unit tests for Wiz Authentication Module"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from unittest.mock import patch, MagicMock, Mock
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from regscale.integrations.commercial.wizv2.core.auth import (
|
|
10
|
+
wiz_authenticate,
|
|
11
|
+
get_token,
|
|
12
|
+
generate_authentication_params,
|
|
13
|
+
AUTH0_URLS,
|
|
14
|
+
COGNITO_URLS,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
PATH = "regscale.integrations.commercial.wizv2.core.auth"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestGenerateAuthenticationParams:
|
|
21
|
+
"""Test the generate_authentication_params function"""
|
|
22
|
+
|
|
23
|
+
def test_auth0_url_generates_correct_params(self):
|
|
24
|
+
"""Test that Auth0 URLs generate correct authentication parameters"""
|
|
25
|
+
client_id = "test_client_id"
|
|
26
|
+
client_secret = "test_client_secret"
|
|
27
|
+
|
|
28
|
+
for auth0_url in AUTH0_URLS:
|
|
29
|
+
params = generate_authentication_params(client_id, client_secret, auth0_url)
|
|
30
|
+
assert params == {
|
|
31
|
+
"grant_type": "client_credentials",
|
|
32
|
+
"audience": "beyond-api",
|
|
33
|
+
"client_id": client_id,
|
|
34
|
+
"client_secret": client_secret,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def test_cognito_url_generates_correct_params(self):
|
|
38
|
+
"""Test that Cognito URLs generate correct authentication parameters"""
|
|
39
|
+
client_id = "test_client_id"
|
|
40
|
+
client_secret = "test_client_secret"
|
|
41
|
+
|
|
42
|
+
for cognito_url in COGNITO_URLS:
|
|
43
|
+
params = generate_authentication_params(client_id, client_secret, cognito_url)
|
|
44
|
+
assert params == {
|
|
45
|
+
"grant_type": "client_credentials",
|
|
46
|
+
"audience": "wiz-api",
|
|
47
|
+
"client_id": client_id,
|
|
48
|
+
"client_secret": client_secret,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def test_invalid_url_raises_error(self):
|
|
52
|
+
"""Test that an invalid URL raises ValueError"""
|
|
53
|
+
client_id = "test_client_id"
|
|
54
|
+
client_secret = "test_client_secret"
|
|
55
|
+
invalid_url = "https://invalid.example.com/oauth/token"
|
|
56
|
+
|
|
57
|
+
with pytest.raises(ValueError, match="Invalid Token URL"):
|
|
58
|
+
generate_authentication_params(client_id, client_secret, invalid_url)
|
|
59
|
+
|
|
60
|
+
def test_empty_credentials_still_processed(self):
|
|
61
|
+
"""Test that empty credentials are still processed correctly"""
|
|
62
|
+
client_id = ""
|
|
63
|
+
client_secret = ""
|
|
64
|
+
|
|
65
|
+
params = generate_authentication_params(client_id, client_secret, AUTH0_URLS[0])
|
|
66
|
+
assert params["client_id"] == ""
|
|
67
|
+
assert params["client_secret"] == ""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestGetToken:
|
|
71
|
+
"""Test the get_token function"""
|
|
72
|
+
|
|
73
|
+
@patch(f"{PATH}.Api")
|
|
74
|
+
def test_successful_token_retrieval_first_url(self, mock_api_class):
|
|
75
|
+
"""Test successful token retrieval on first URL attempt"""
|
|
76
|
+
mock_api = MagicMock()
|
|
77
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
78
|
+
mock_api.app = MagicMock()
|
|
79
|
+
mock_api.app.save_config = MagicMock()
|
|
80
|
+
|
|
81
|
+
mock_response = MagicMock()
|
|
82
|
+
mock_response.ok = True
|
|
83
|
+
mock_response.status_code = 200
|
|
84
|
+
mock_response.reason = "OK"
|
|
85
|
+
mock_response.json.return_value = {
|
|
86
|
+
"access_token": "test_token_123",
|
|
87
|
+
"scope": "read:all write:all",
|
|
88
|
+
}
|
|
89
|
+
mock_api.post.return_value = mock_response
|
|
90
|
+
|
|
91
|
+
token, scope = get_token(
|
|
92
|
+
api=mock_api,
|
|
93
|
+
client_id="test_client",
|
|
94
|
+
client_secret="test_secret",
|
|
95
|
+
token_url=AUTH0_URLS[0],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
assert token == "test_token_123"
|
|
99
|
+
assert scope == "read:all write:all"
|
|
100
|
+
mock_api.post.assert_called_once()
|
|
101
|
+
|
|
102
|
+
@patch(f"{PATH}.error_and_exit")
|
|
103
|
+
@patch(f"{PATH}.Api")
|
|
104
|
+
def test_unauthorized_fallback_to_cognito(self, mock_api_class, mock_error_exit):
|
|
105
|
+
"""Test fallback to Cognito URL on unauthorized response"""
|
|
106
|
+
mock_api = MagicMock()
|
|
107
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
108
|
+
mock_api.app = MagicMock()
|
|
109
|
+
mock_api.app.save_config = MagicMock()
|
|
110
|
+
|
|
111
|
+
# First response: Unauthorized
|
|
112
|
+
mock_response_unauthorized = MagicMock()
|
|
113
|
+
mock_response_unauthorized.ok = False
|
|
114
|
+
mock_response_unauthorized.status_code = requests.codes.unauthorized
|
|
115
|
+
mock_response_unauthorized.reason = "Unauthorized"
|
|
116
|
+
|
|
117
|
+
# Second response: Success with Cognito
|
|
118
|
+
mock_response_success = MagicMock()
|
|
119
|
+
mock_response_success.ok = True
|
|
120
|
+
mock_response_success.status_code = 200
|
|
121
|
+
mock_response_success.reason = "OK"
|
|
122
|
+
mock_response_success.json.return_value = {
|
|
123
|
+
"access_token": "cognito_token_456",
|
|
124
|
+
"scope": "wiz-api",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
mock_api.post.side_effect = [mock_response_unauthorized, mock_response_success]
|
|
128
|
+
|
|
129
|
+
token, scope = get_token(
|
|
130
|
+
api=mock_api,
|
|
131
|
+
client_id="test_client",
|
|
132
|
+
client_secret="test_secret",
|
|
133
|
+
token_url=AUTH0_URLS[0],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert token == "cognito_token_456"
|
|
137
|
+
assert scope == "wiz-api"
|
|
138
|
+
assert mock_api.post.call_count == 2
|
|
139
|
+
mock_api.app.save_config.assert_called_once()
|
|
140
|
+
assert mock_api.config["wizAuthUrl"] == COGNITO_URLS[0]
|
|
141
|
+
|
|
142
|
+
@patch(f"{PATH}.error_and_exit")
|
|
143
|
+
@patch(f"{PATH}.Api")
|
|
144
|
+
def test_unauthorized_fallback_fails_with_request_exception(self, mock_api_class, mock_error_exit):
|
|
145
|
+
"""Test that RequestException during fallback is handled properly"""
|
|
146
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
147
|
+
mock_api = MagicMock()
|
|
148
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
149
|
+
mock_api.app = MagicMock()
|
|
150
|
+
|
|
151
|
+
# First response: Unauthorized
|
|
152
|
+
mock_response_unauthorized = MagicMock()
|
|
153
|
+
mock_response_unauthorized.ok = False
|
|
154
|
+
mock_response_unauthorized.status_code = requests.codes.unauthorized
|
|
155
|
+
mock_response_unauthorized.reason = "Unauthorized"
|
|
156
|
+
mock_response_unauthorized.text = "Invalid credentials"
|
|
157
|
+
|
|
158
|
+
# Second call raises RequestException
|
|
159
|
+
mock_api.post.side_effect = [mock_response_unauthorized, requests.RequestException("Network error")]
|
|
160
|
+
|
|
161
|
+
with pytest.raises(SystemExit):
|
|
162
|
+
get_token(
|
|
163
|
+
api=mock_api,
|
|
164
|
+
client_id="test_client",
|
|
165
|
+
client_secret="test_secret",
|
|
166
|
+
token_url=AUTH0_URLS[0],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
mock_error_exit.assert_called_once()
|
|
170
|
+
|
|
171
|
+
@patch(f"{PATH}.error_and_exit")
|
|
172
|
+
@patch(f"{PATH}.Api")
|
|
173
|
+
def test_non_200_status_code_error(self, mock_api_class, mock_error_exit):
|
|
174
|
+
"""Test that non-200 status codes trigger error_and_exit"""
|
|
175
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
176
|
+
mock_api = MagicMock()
|
|
177
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
178
|
+
mock_api.app = MagicMock()
|
|
179
|
+
|
|
180
|
+
mock_response = MagicMock()
|
|
181
|
+
mock_response.ok = False
|
|
182
|
+
mock_response.status_code = 500
|
|
183
|
+
mock_response.reason = "Internal Server Error"
|
|
184
|
+
mock_response.text = "Server error occurred"
|
|
185
|
+
mock_api.post.return_value = mock_response
|
|
186
|
+
|
|
187
|
+
with pytest.raises(SystemExit):
|
|
188
|
+
get_token(
|
|
189
|
+
api=mock_api,
|
|
190
|
+
client_id="test_client",
|
|
191
|
+
client_secret="test_secret",
|
|
192
|
+
token_url=AUTH0_URLS[0],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
mock_error_exit.assert_called_once()
|
|
196
|
+
error_message = mock_error_exit.call_args[0][0]
|
|
197
|
+
assert "Error authenticating to Wiz" in error_message
|
|
198
|
+
assert "500" in error_message
|
|
199
|
+
|
|
200
|
+
@patch(f"{PATH}.error_and_exit")
|
|
201
|
+
@patch(f"{PATH}.Api")
|
|
202
|
+
def test_missing_access_token_in_response(self, mock_api_class, mock_error_exit):
|
|
203
|
+
"""Test error when access_token is missing from response"""
|
|
204
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
205
|
+
mock_api = MagicMock()
|
|
206
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
207
|
+
mock_api.app = MagicMock()
|
|
208
|
+
|
|
209
|
+
mock_response = MagicMock()
|
|
210
|
+
mock_response.ok = True
|
|
211
|
+
mock_response.status_code = 200
|
|
212
|
+
mock_response.reason = "OK"
|
|
213
|
+
mock_response.json.return_value = {
|
|
214
|
+
"scope": "read:all",
|
|
215
|
+
"message": "Token generation failed",
|
|
216
|
+
}
|
|
217
|
+
mock_api.post.return_value = mock_response
|
|
218
|
+
|
|
219
|
+
with pytest.raises(SystemExit):
|
|
220
|
+
get_token(
|
|
221
|
+
api=mock_api,
|
|
222
|
+
client_id="test_client",
|
|
223
|
+
client_secret="test_secret",
|
|
224
|
+
token_url=AUTH0_URLS[0],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
mock_error_exit.assert_called_once()
|
|
228
|
+
error_message = mock_error_exit.call_args[0][0]
|
|
229
|
+
assert "Could not retrieve token from Wiz" in error_message
|
|
230
|
+
|
|
231
|
+
@patch(f"{PATH}.Api")
|
|
232
|
+
def test_response_with_null_scope(self, mock_api_class):
|
|
233
|
+
"""Test successful token retrieval when scope is None"""
|
|
234
|
+
mock_api = MagicMock()
|
|
235
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
236
|
+
mock_api.app = MagicMock()
|
|
237
|
+
|
|
238
|
+
mock_response = MagicMock()
|
|
239
|
+
mock_response.ok = True
|
|
240
|
+
mock_response.status_code = 200
|
|
241
|
+
mock_response.reason = "OK"
|
|
242
|
+
mock_response.json.return_value = {
|
|
243
|
+
"access_token": "test_token_123",
|
|
244
|
+
"scope": None,
|
|
245
|
+
}
|
|
246
|
+
mock_api.post.return_value = mock_response
|
|
247
|
+
|
|
248
|
+
token, scope = get_token(
|
|
249
|
+
api=mock_api,
|
|
250
|
+
client_id="test_client",
|
|
251
|
+
client_secret="test_secret",
|
|
252
|
+
token_url=AUTH0_URLS[0],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assert token == "test_token_123"
|
|
256
|
+
assert scope is None
|
|
257
|
+
|
|
258
|
+
@patch(f"{PATH}.Api")
|
|
259
|
+
def test_cognito_url_direct_success(self, mock_api_class):
|
|
260
|
+
"""Test successful authentication directly with Cognito URL"""
|
|
261
|
+
mock_api = MagicMock()
|
|
262
|
+
mock_api.config = {"wizAuthUrl": COGNITO_URLS[0]}
|
|
263
|
+
mock_api.app = MagicMock()
|
|
264
|
+
|
|
265
|
+
mock_response = MagicMock()
|
|
266
|
+
mock_response.ok = True
|
|
267
|
+
mock_response.status_code = 200
|
|
268
|
+
mock_response.reason = "OK"
|
|
269
|
+
mock_response.json.return_value = {
|
|
270
|
+
"access_token": "cognito_direct_token",
|
|
271
|
+
"scope": "wiz-api full",
|
|
272
|
+
}
|
|
273
|
+
mock_api.post.return_value = mock_response
|
|
274
|
+
|
|
275
|
+
token, scope = get_token(
|
|
276
|
+
api=mock_api,
|
|
277
|
+
client_id="cognito_client",
|
|
278
|
+
client_secret="cognito_secret",
|
|
279
|
+
token_url=COGNITO_URLS[0],
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
assert token == "cognito_direct_token"
|
|
283
|
+
assert scope == "wiz-api full"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class TestWizAuthenticate:
|
|
287
|
+
"""Test the wiz_authenticate function"""
|
|
288
|
+
|
|
289
|
+
@patch(f"{PATH}.get_token")
|
|
290
|
+
@patch(f"{PATH}.WizVariables")
|
|
291
|
+
@patch(f"{PATH}.Api")
|
|
292
|
+
@patch(f"{PATH}.check_license")
|
|
293
|
+
def test_successful_authentication_with_env_variables(
|
|
294
|
+
self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
|
|
295
|
+
):
|
|
296
|
+
"""Test successful authentication using environment variables"""
|
|
297
|
+
mock_app = MagicMock()
|
|
298
|
+
mock_app.config = {
|
|
299
|
+
"wizAuthUrl": AUTH0_URLS[0],
|
|
300
|
+
}
|
|
301
|
+
mock_app.save_config = MagicMock()
|
|
302
|
+
mock_check_license.return_value = mock_app
|
|
303
|
+
|
|
304
|
+
mock_api = MagicMock()
|
|
305
|
+
mock_api_class.return_value = mock_api
|
|
306
|
+
|
|
307
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
308
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
309
|
+
|
|
310
|
+
mock_get_token.return_value = ("test_token_789", "read:all write:all")
|
|
311
|
+
|
|
312
|
+
token = wiz_authenticate()
|
|
313
|
+
|
|
314
|
+
assert token == "test_token_789"
|
|
315
|
+
mock_get_token.assert_called_once_with(
|
|
316
|
+
api=mock_api,
|
|
317
|
+
client_id="env_client_id",
|
|
318
|
+
client_secret="env_client_secret",
|
|
319
|
+
token_url=AUTH0_URLS[0],
|
|
320
|
+
)
|
|
321
|
+
mock_app.save_config.assert_called_once()
|
|
322
|
+
saved_config = mock_app.save_config.call_args[0][0]
|
|
323
|
+
assert saved_config["wizAccessToken"] == "test_token_789"
|
|
324
|
+
assert saved_config["wizScope"] == "read:all write:all"
|
|
325
|
+
|
|
326
|
+
@patch(f"{PATH}.get_token")
|
|
327
|
+
@patch(f"{PATH}.WizVariables")
|
|
328
|
+
@patch(f"{PATH}.Api")
|
|
329
|
+
@patch(f"{PATH}.check_license")
|
|
330
|
+
def test_successful_authentication_with_cli_parameters(
|
|
331
|
+
self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
|
|
332
|
+
):
|
|
333
|
+
"""Test successful authentication using CLI-provided parameters"""
|
|
334
|
+
mock_app = MagicMock()
|
|
335
|
+
mock_app.config = {
|
|
336
|
+
"wizAuthUrl": COGNITO_URLS[0],
|
|
337
|
+
}
|
|
338
|
+
mock_app.save_config = MagicMock()
|
|
339
|
+
mock_check_license.return_value = mock_app
|
|
340
|
+
|
|
341
|
+
mock_api = MagicMock()
|
|
342
|
+
mock_api_class.return_value = mock_api
|
|
343
|
+
|
|
344
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
345
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
346
|
+
|
|
347
|
+
mock_get_token.return_value = ("cli_token_999", "wiz-api")
|
|
348
|
+
|
|
349
|
+
token = wiz_authenticate(client_id="cli_client_id", client_secret="cli_client_secret")
|
|
350
|
+
|
|
351
|
+
assert token == "cli_token_999"
|
|
352
|
+
mock_get_token.assert_called_once_with(
|
|
353
|
+
api=mock_api,
|
|
354
|
+
client_id="cli_client_id",
|
|
355
|
+
client_secret="cli_client_secret",
|
|
356
|
+
token_url=COGNITO_URLS[0],
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
@patch(f"{PATH}.error_and_exit")
|
|
360
|
+
@patch(f"{PATH}.WizVariables")
|
|
361
|
+
@patch(f"{PATH}.Api")
|
|
362
|
+
@patch(f"{PATH}.check_license")
|
|
363
|
+
def test_missing_client_id_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
|
|
364
|
+
"""Test error when client ID is missing"""
|
|
365
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
366
|
+
mock_app = MagicMock()
|
|
367
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
368
|
+
mock_check_license.return_value = mock_app
|
|
369
|
+
|
|
370
|
+
mock_api = MagicMock()
|
|
371
|
+
mock_api_class.return_value = mock_api
|
|
372
|
+
|
|
373
|
+
mock_wiz_vars.wizClientId = None
|
|
374
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
375
|
+
|
|
376
|
+
with pytest.raises(SystemExit):
|
|
377
|
+
wiz_authenticate()
|
|
378
|
+
|
|
379
|
+
mock_error_exit.assert_called_once()
|
|
380
|
+
error_message = mock_error_exit.call_args[0][0]
|
|
381
|
+
assert "No Wiz Client ID provided" in error_message
|
|
382
|
+
|
|
383
|
+
@patch(f"{PATH}.error_and_exit")
|
|
384
|
+
@patch(f"{PATH}.WizVariables")
|
|
385
|
+
@patch(f"{PATH}.Api")
|
|
386
|
+
@patch(f"{PATH}.check_license")
|
|
387
|
+
def test_missing_client_secret_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
|
|
388
|
+
"""Test error when client secret is missing"""
|
|
389
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
390
|
+
mock_app = MagicMock()
|
|
391
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
392
|
+
mock_check_license.return_value = mock_app
|
|
393
|
+
|
|
394
|
+
mock_api = MagicMock()
|
|
395
|
+
mock_api_class.return_value = mock_api
|
|
396
|
+
|
|
397
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
398
|
+
mock_wiz_vars.wizClientSecret = None
|
|
399
|
+
|
|
400
|
+
with pytest.raises(SystemExit):
|
|
401
|
+
wiz_authenticate()
|
|
402
|
+
|
|
403
|
+
mock_error_exit.assert_called_once()
|
|
404
|
+
error_message = mock_error_exit.call_args[0][0]
|
|
405
|
+
assert "No Wiz Client Secret provided" in error_message
|
|
406
|
+
|
|
407
|
+
@patch(f"{PATH}.error_and_exit")
|
|
408
|
+
@patch(f"{PATH}.WizVariables")
|
|
409
|
+
@patch(f"{PATH}.Api")
|
|
410
|
+
@patch(f"{PATH}.check_license")
|
|
411
|
+
def test_missing_auth_url_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
|
|
412
|
+
"""Test error when wizAuthUrl is missing from config"""
|
|
413
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
414
|
+
mock_app = MagicMock()
|
|
415
|
+
mock_app.config = {}
|
|
416
|
+
mock_check_license.return_value = mock_app
|
|
417
|
+
|
|
418
|
+
mock_api = MagicMock()
|
|
419
|
+
mock_api_class.return_value = mock_api
|
|
420
|
+
|
|
421
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
422
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
423
|
+
|
|
424
|
+
with pytest.raises(SystemExit):
|
|
425
|
+
wiz_authenticate()
|
|
426
|
+
|
|
427
|
+
mock_error_exit.assert_called_once()
|
|
428
|
+
error_message = mock_error_exit.call_args[0][0]
|
|
429
|
+
assert "No Wiz Authentication URL provided" in error_message
|
|
430
|
+
|
|
431
|
+
@patch(f"{PATH}.get_token")
|
|
432
|
+
@patch(f"{PATH}.WizVariables")
|
|
433
|
+
@patch(f"{PATH}.Api")
|
|
434
|
+
@patch(f"{PATH}.check_license")
|
|
435
|
+
def test_empty_string_credentials_treated_as_missing(
|
|
436
|
+
self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
|
|
437
|
+
):
|
|
438
|
+
"""Test that empty string credentials are treated as missing"""
|
|
439
|
+
mock_app = MagicMock()
|
|
440
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
441
|
+
mock_check_license.return_value = mock_app
|
|
442
|
+
|
|
443
|
+
mock_api = MagicMock()
|
|
444
|
+
mock_api_class.return_value = mock_api
|
|
445
|
+
|
|
446
|
+
# Empty strings should be treated as falsy
|
|
447
|
+
mock_wiz_vars.wizClientId = ""
|
|
448
|
+
mock_wiz_vars.wizClientSecret = "valid_secret"
|
|
449
|
+
|
|
450
|
+
# Since empty string client_id is provided via CLI, it should override env
|
|
451
|
+
# But empty strings are falsy, so error should trigger
|
|
452
|
+
from regscale.core.app.utils.app_utils import error_and_exit
|
|
453
|
+
|
|
454
|
+
with patch(f"{PATH}.error_and_exit") as mock_error_exit:
|
|
455
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
456
|
+
|
|
457
|
+
with pytest.raises(SystemExit):
|
|
458
|
+
wiz_authenticate(client_id="", client_secret="valid_secret")
|
|
459
|
+
|
|
460
|
+
mock_error_exit.assert_called()
|
|
461
|
+
|
|
462
|
+
@patch(f"{PATH}.get_token")
|
|
463
|
+
@patch(f"{PATH}.WizVariables")
|
|
464
|
+
@patch(f"{PATH}.Api")
|
|
465
|
+
@patch(f"{PATH}.check_license")
|
|
466
|
+
def test_cli_parameters_override_environment_variables(
|
|
467
|
+
self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
|
|
468
|
+
):
|
|
469
|
+
"""Test that CLI parameters override environment variables"""
|
|
470
|
+
mock_app = MagicMock()
|
|
471
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
472
|
+
mock_app.save_config = MagicMock()
|
|
473
|
+
mock_check_license.return_value = mock_app
|
|
474
|
+
|
|
475
|
+
mock_api = MagicMock()
|
|
476
|
+
mock_api_class.return_value = mock_api
|
|
477
|
+
|
|
478
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
479
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
480
|
+
|
|
481
|
+
mock_get_token.return_value = ("override_token", "override_scope")
|
|
482
|
+
|
|
483
|
+
wiz_authenticate(client_id="cli_client_id", client_secret="cli_client_secret")
|
|
484
|
+
|
|
485
|
+
# Verify CLI parameters were used, not env variables
|
|
486
|
+
mock_get_token.assert_called_once_with(
|
|
487
|
+
api=mock_api,
|
|
488
|
+
client_id="cli_client_id",
|
|
489
|
+
client_secret="cli_client_secret",
|
|
490
|
+
token_url=AUTH0_URLS[0],
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
@patch(f"{PATH}.get_token")
|
|
494
|
+
@patch(f"{PATH}.WizVariables")
|
|
495
|
+
@patch(f"{PATH}.Api")
|
|
496
|
+
@patch(f"{PATH}.check_license")
|
|
497
|
+
def test_token_and_scope_saved_to_config(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
|
|
498
|
+
"""Test that token and scope are properly saved to config"""
|
|
499
|
+
mock_app = MagicMock()
|
|
500
|
+
mock_app.config = {
|
|
501
|
+
"wizAuthUrl": AUTH0_URLS[0],
|
|
502
|
+
"existingKey": "existingValue",
|
|
503
|
+
}
|
|
504
|
+
mock_app.save_config = MagicMock()
|
|
505
|
+
mock_check_license.return_value = mock_app
|
|
506
|
+
|
|
507
|
+
mock_api = MagicMock()
|
|
508
|
+
mock_api_class.return_value = mock_api
|
|
509
|
+
|
|
510
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
511
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
512
|
+
|
|
513
|
+
mock_get_token.return_value = ("new_token_123", "full_scope")
|
|
514
|
+
|
|
515
|
+
wiz_authenticate()
|
|
516
|
+
|
|
517
|
+
mock_app.save_config.assert_called_once()
|
|
518
|
+
saved_config = mock_app.save_config.call_args[0][0]
|
|
519
|
+
assert saved_config["wizAccessToken"] == "new_token_123"
|
|
520
|
+
assert saved_config["wizScope"] == "full_scope"
|
|
521
|
+
assert saved_config["existingKey"] == "existingValue"
|
|
522
|
+
assert saved_config["wizAuthUrl"] == AUTH0_URLS[0]
|
|
523
|
+
|
|
524
|
+
@patch(f"{PATH}.get_token")
|
|
525
|
+
@patch(f"{PATH}.WizVariables")
|
|
526
|
+
@patch(f"{PATH}.Api")
|
|
527
|
+
@patch(f"{PATH}.check_license")
|
|
528
|
+
def test_partial_cli_parameters(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
|
|
529
|
+
"""Test authentication with partial CLI parameters (only client_id)"""
|
|
530
|
+
mock_app = MagicMock()
|
|
531
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
532
|
+
mock_app.save_config = MagicMock()
|
|
533
|
+
mock_check_license.return_value = mock_app
|
|
534
|
+
|
|
535
|
+
mock_api = MagicMock()
|
|
536
|
+
mock_api_class.return_value = mock_api
|
|
537
|
+
|
|
538
|
+
mock_wiz_vars.wizClientId = "env_client_id"
|
|
539
|
+
mock_wiz_vars.wizClientSecret = "env_client_secret"
|
|
540
|
+
|
|
541
|
+
mock_get_token.return_value = ("partial_token", "partial_scope")
|
|
542
|
+
|
|
543
|
+
wiz_authenticate(client_id="cli_client_id")
|
|
544
|
+
|
|
545
|
+
# CLI client_id should be used, env client_secret should be used
|
|
546
|
+
mock_get_token.assert_called_once_with(
|
|
547
|
+
api=mock_api,
|
|
548
|
+
client_id="cli_client_id",
|
|
549
|
+
client_secret="env_client_secret",
|
|
550
|
+
token_url=AUTH0_URLS[0],
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class TestAuthenticationIntegration:
|
|
555
|
+
"""Integration tests for authentication flow"""
|
|
556
|
+
|
|
557
|
+
@patch(f"{PATH}.get_token")
|
|
558
|
+
@patch(f"{PATH}.WizVariables")
|
|
559
|
+
@patch(f"{PATH}.Api")
|
|
560
|
+
@patch(f"{PATH}.check_license")
|
|
561
|
+
def test_complete_auth_flow_with_all_urls(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
|
|
562
|
+
"""Test complete authentication flow with all supported URLs"""
|
|
563
|
+
all_urls = AUTH0_URLS + COGNITO_URLS
|
|
564
|
+
|
|
565
|
+
for url in all_urls:
|
|
566
|
+
mock_app = MagicMock()
|
|
567
|
+
mock_app.config = {"wizAuthUrl": url}
|
|
568
|
+
mock_app.save_config = MagicMock()
|
|
569
|
+
mock_check_license.return_value = mock_app
|
|
570
|
+
|
|
571
|
+
mock_api = MagicMock()
|
|
572
|
+
mock_api_class.return_value = mock_api
|
|
573
|
+
|
|
574
|
+
mock_wiz_vars.wizClientId = "test_client"
|
|
575
|
+
mock_wiz_vars.wizClientSecret = "test_secret"
|
|
576
|
+
|
|
577
|
+
mock_get_token.return_value = (f"token_for_{url}", "scope")
|
|
578
|
+
|
|
579
|
+
token = wiz_authenticate()
|
|
580
|
+
|
|
581
|
+
assert token == f"token_for_{url}"
|
|
582
|
+
mock_get_token.assert_called_with(
|
|
583
|
+
api=mock_api,
|
|
584
|
+
client_id="test_client",
|
|
585
|
+
client_secret="test_secret",
|
|
586
|
+
token_url=url,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
@patch(f"{PATH}.error_and_exit")
|
|
590
|
+
@patch(f"{PATH}.get_token")
|
|
591
|
+
@patch(f"{PATH}.WizVariables")
|
|
592
|
+
@patch(f"{PATH}.Api")
|
|
593
|
+
@patch(f"{PATH}.check_license")
|
|
594
|
+
def test_get_token_failure_propagates_to_wiz_authenticate(
|
|
595
|
+
self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token, mock_error_exit
|
|
596
|
+
):
|
|
597
|
+
"""Test that get_token failures properly propagate to wiz_authenticate"""
|
|
598
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
599
|
+
mock_app = MagicMock()
|
|
600
|
+
mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
601
|
+
mock_check_license.return_value = mock_app
|
|
602
|
+
|
|
603
|
+
mock_api = MagicMock()
|
|
604
|
+
mock_api_class.return_value = mock_api
|
|
605
|
+
|
|
606
|
+
mock_wiz_vars.wizClientId = "test_client"
|
|
607
|
+
mock_wiz_vars.wizClientSecret = "test_secret"
|
|
608
|
+
|
|
609
|
+
mock_get_token.side_effect = SystemExit(1)
|
|
610
|
+
|
|
611
|
+
with pytest.raises(SystemExit):
|
|
612
|
+
wiz_authenticate()
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class TestEdgeCases:
|
|
616
|
+
"""Test edge cases and boundary conditions"""
|
|
617
|
+
|
|
618
|
+
def test_auth0_urls_list_not_empty(self):
|
|
619
|
+
"""Test that AUTH0_URLS is not empty"""
|
|
620
|
+
assert len(AUTH0_URLS) > 0
|
|
621
|
+
|
|
622
|
+
def test_cognito_urls_list_not_empty(self):
|
|
623
|
+
"""Test that COGNITO_URLS is not empty"""
|
|
624
|
+
assert len(COGNITO_URLS) > 0
|
|
625
|
+
|
|
626
|
+
def test_all_auth0_urls_are_unique(self):
|
|
627
|
+
"""Test that all Auth0 URLs are unique"""
|
|
628
|
+
assert len(AUTH0_URLS) == len(set(AUTH0_URLS))
|
|
629
|
+
|
|
630
|
+
def test_all_cognito_urls_are_unique(self):
|
|
631
|
+
"""Test that all Cognito URLs are unique"""
|
|
632
|
+
assert len(COGNITO_URLS) == len(set(COGNITO_URLS))
|
|
633
|
+
|
|
634
|
+
def test_no_url_overlap_between_auth0_and_cognito(self):
|
|
635
|
+
"""Test that Auth0 and Cognito URLs don't overlap"""
|
|
636
|
+
auth0_set = set(AUTH0_URLS)
|
|
637
|
+
cognito_set = set(COGNITO_URLS)
|
|
638
|
+
assert len(auth0_set.intersection(cognito_set)) == 0
|
|
639
|
+
|
|
640
|
+
def test_all_urls_are_strings(self):
|
|
641
|
+
"""Test that all URLs are strings"""
|
|
642
|
+
for url in AUTH0_URLS + COGNITO_URLS:
|
|
643
|
+
assert isinstance(url, str)
|
|
644
|
+
|
|
645
|
+
def test_all_urls_are_https(self):
|
|
646
|
+
"""Test that all URLs use HTTPS"""
|
|
647
|
+
for url in AUTH0_URLS + COGNITO_URLS:
|
|
648
|
+
assert url.startswith("https://")
|
|
649
|
+
|
|
650
|
+
@patch(f"{PATH}.Api")
|
|
651
|
+
def test_get_token_handles_response_without_scope(self, mock_api_class):
|
|
652
|
+
"""Test get_token when response doesn't include scope key"""
|
|
653
|
+
mock_api = MagicMock()
|
|
654
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
655
|
+
mock_api.app = MagicMock()
|
|
656
|
+
|
|
657
|
+
mock_response = MagicMock()
|
|
658
|
+
mock_response.ok = True
|
|
659
|
+
mock_response.status_code = 200
|
|
660
|
+
mock_response.reason = "OK"
|
|
661
|
+
# Response without scope key
|
|
662
|
+
mock_response.json.return_value = {
|
|
663
|
+
"access_token": "token_without_scope",
|
|
664
|
+
}
|
|
665
|
+
mock_api.post.return_value = mock_response
|
|
666
|
+
|
|
667
|
+
token, scope = get_token(
|
|
668
|
+
api=mock_api,
|
|
669
|
+
client_id="test_client",
|
|
670
|
+
client_secret="test_secret",
|
|
671
|
+
token_url=AUTH0_URLS[0],
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
assert token == "token_without_scope"
|
|
675
|
+
assert scope is None
|
|
676
|
+
|
|
677
|
+
@patch(f"{PATH}.error_and_exit")
|
|
678
|
+
@patch(f"{PATH}.Api")
|
|
679
|
+
def test_get_token_handles_empty_json_response(self, mock_api_class, mock_error_exit):
|
|
680
|
+
"""Test get_token when response JSON is empty"""
|
|
681
|
+
mock_error_exit.side_effect = SystemExit(1)
|
|
682
|
+
mock_api = MagicMock()
|
|
683
|
+
mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
|
|
684
|
+
mock_api.app = MagicMock()
|
|
685
|
+
|
|
686
|
+
mock_response = MagicMock()
|
|
687
|
+
mock_response.ok = True
|
|
688
|
+
mock_response.status_code = 200
|
|
689
|
+
mock_response.reason = "OK"
|
|
690
|
+
mock_response.json.return_value = {}
|
|
691
|
+
mock_api.post.return_value = mock_response
|
|
692
|
+
|
|
693
|
+
with pytest.raises(SystemExit):
|
|
694
|
+
get_token(
|
|
695
|
+
api=mock_api,
|
|
696
|
+
client_id="test_client",
|
|
697
|
+
client_secret="test_secret",
|
|
698
|
+
token_url=AUTH0_URLS[0],
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
mock_error_exit.assert_called_once()
|