From 7ba2f0745ecef051ccfdc245800b4ead690e4894 Mon Sep 17 00:00:00 2001 From: William Guilherme Date: Mon, 27 Jul 2026 11:12:10 -0700 Subject: [PATCH 1/2] fix: Add new ZIA and ZPA endpoints, AI Guard OneAPI support (1.9.39) ZIA: added Endpoint DLP coverage (applications, custom applications, application groups, resources, resource groups, rules, and exception sub-rules), DLP Advanced Settings, Outbound Email DLP rule actions, DNS application groups, End User Notification templates, IPS categories, and NSS collectors. ZPA: added Policy Group, Policy Group Rule, and Policy Group Set resources. AI Guard: added full OneAPI support via client.aiguard for detection policies, policy match rules, LLM providers and provider types, LLM provider credentials, LLM applications, and LLM application credentials. The package was renamed from zaiguard to aiguard, with client.zguard kept as a deprecated alias. Policy detection stays on LegacyAIGuardClient because those endpoints are not exposed through OneAPI. Bumped version to 1.9.39 and updated the changelog, release notes, and reference documentation for the new resources. --- .gitignore | 1 + CHANGELOG.md | 176 ++++ CLAUDE.md | 54 + Makefile | 94 +- README.md | 77 ++ docsrc/conf.py | 4 +- docsrc/index.rst | 11 +- docsrc/zs/aiguard/index.rst | 22 + .../aiguard/llm_application_credentials.rst | 14 + docsrc/zs/aiguard/llm_applications.rst | 14 + .../zs/aiguard/llm_provider_credentials.rst | 14 + docsrc/zs/aiguard/llm_providers.rst | 14 + docsrc/zs/aiguard/policies.rst | 14 + docsrc/zs/aiguard/policy_detection.rst | 40 + docsrc/zs/aiguard/policy_match_rules.rst | 14 + docsrc/zs/guides/release_notes.rst | 197 ++++ docsrc/zs/zaiguard/index.rst | 14 - docsrc/zs/zaiguard/policy_detection.rst | 12 - docsrc/zs/zpa/policy_group.rst | 14 + docsrc/zs/zpa/policy_group_rule.rst | 14 + docsrc/zs/zpa/policy_group_set.rst | 14 + poetry.lock | 460 ++++---- pyproject.toml | 2 +- requirements.txt | 4 +- tests/conftest.py | 25 + .../.gitkeep => aiguard/__init__.py} | 0 tests/integration/aiguard/cassettes/.gitkeep | 0 .../TestLlmApplicationCredentials.yaml | 722 +++++++++++++ .../cassettes/TestLlmApplications.yaml | 462 +++++++++ .../cassettes/TestLlmProviderCredentials.yaml | 526 ++++++++++ .../aiguard/cassettes/TestLlmProviders.yaml | 513 +++++++++ .../aiguard/cassettes/TestPolicies.yaml | 501 +++++++++ .../cassettes/TestPolicyMatchRules.yaml | 980 ++++++++++++++++++ tests/integration/aiguard/conftest.py | 94 ++ tests/integration/aiguard/sweep/run_sweep.py | 263 +++++ .../test_llm_application_credentials.py | 147 +++ .../aiguard/test_llm_applications.py | 115 ++ .../aiguard/test_llm_provider_credentials.py | 119 +++ .../integration/aiguard/test_llm_providers.py | 108 ++ tests/integration/aiguard/test_policies.py | 160 +++ .../aiguard/test_policy_match_rules.py | 245 +++++ tests/integration/zaiguard/README.md | 305 ------ .../cassettes/TestPolicyDetection.yaml | 158 --- tests/integration/zaiguard/conftest.py | 151 --- .../zaiguard/test_policy_detection.py | 467 --------- .../zaiguard/test_zaiguard_unit.py | 613 ----------- zscaler/__init__.py | 2 +- zscaler/aiguard/__init__.py | 0 zscaler/aiguard/aiguard_service.py | 84 ++ zscaler/{zaiguard => aiguard}/legacy.py | 6 +- .../aiguard/llm_application_credentials.py | 423 ++++++++ zscaler/aiguard/llm_applications.py | 374 +++++++ zscaler/aiguard/llm_provider_credentials.py | 373 +++++++ zscaler/aiguard/llm_providers.py | 468 +++++++++ .../aiguard/models}/__init__.py | 0 .../models/llm_application_credentials.py | 71 ++ zscaler/aiguard/models/llm_applications.py | 108 ++ .../models/llm_provider_credentials.py | 99 ++ zscaler/aiguard/models/llm_providers.py | 65 ++ zscaler/aiguard/models/policies.py | 207 ++++ .../models/policy_detection.py | 0 zscaler/aiguard/models/policy_match_rules.py | 165 +++ zscaler/aiguard/policies.py | 368 +++++++ .../{zaiguard => aiguard}/policy_detection.py | 28 +- zscaler/aiguard/policy_match_rules.py | 349 +++++++ zscaler/helpers.py | 10 + zscaler/oneapi_client.py | 148 ++- zscaler/oneapi_http_client.py | 28 +- zscaler/oneapi_response.py | 16 + zscaler/request_executor.py | 25 +- zscaler/utils.py | 1 - zscaler/zaiguard/__init__.py | 15 - zscaler/zaiguard/models/__init__.py | 15 - zscaler/zaiguard/zaiguard_service.py | 33 - zscaler/zia/azure_integration.py | 21 +- zscaler/zia/dlp_endpoint_resource.py | 305 ++++++ zscaler/zia/dns_application_groups.py | 274 +++++ .../zia/end_user_notification_templates.py | 311 ++++++ zscaler/zia/endpoint_application_groups.py | 343 ++++++ zscaler/zia/endpoint_applications.py | 402 +++++++ zscaler/zia/endpoint_custom_apps.py | 310 ++++++ zscaler/zia/endpoint_dlp_resource_groups.py | 407 ++++++++ zscaler/zia/endpoint_dlp_rules.py | 410 ++++++++ zscaler/zia/endpoint_dlp_sub_rules.py | 246 +++++ zscaler/zia/http_header_control.py | 6 +- zscaler/zia/ips_categories.py | 103 ++ zscaler/zia/legacy.py | 9 + zscaler/zia/models/dlp_endpoint_resource.py | 261 +++++ zscaler/zia/models/dns_application_groups.py | 62 ++ .../zia/models/endpoint_application_groups.py | 201 ++++ .../endpoint_applications_custom_apps.py | 153 +++ .../endpoint_applications_custom_apps_lite.py | 83 ++ .../models/endpoint_applications_policies.py | 56 + .../models/endpoint_dlp_resource_groups.py | 307 ++++++ .../endpoint_dlp_resource_groups_resources.py | 58 ++ zscaler/zia/models/endpoint_dlp_rules.py | 652 ++++++++++++ .../models/eun_feature_enablement_status.py | 73 ++ zscaler/zia/models/eun_template_product.py | 201 ++++ .../models/eun_user_confirmation_product.py | 105 ++ zscaler/zia/models/ips_categories.py | 68 ++ zscaler/zia/models/nss_collectors.py | 75 ++ .../zia/models/outbound_email_dlp_rules.py | 392 +++++++ zscaler/zia/models/web_dlp_global_options.py | 93 ++ zscaler/zia/nss_collectors.py | 92 ++ zscaler/zia/outbound_email_dlp_rules.py | 456 ++++++++ zscaler/zia/partner_integrations.py | 3 +- zscaler/zia/rule_labels.py | 146 +-- zscaler/zia/web_dlp_global_options.py | 128 +++ zscaler/zia/zia_service.py | 117 +++ zscaler/zpa/models/application_segment.py | 1 - zscaler/zpa/models/policy_group.py | 512 +++++++++ zscaler/zpa/models/policy_group_set.py | 83 ++ .../zpa/models/policy_group_set_summary.py | 113 ++ .../models/policy_group_set_summary_stat.py | 59 ++ zscaler/zpa/models/policy_rule.py | 548 ++++++++++ zscaler/zpa/models/policyset_controller_v2.py | 14 + zscaler/zpa/policy_group.py | 399 +++++++ zscaler/zpa/policy_group_rule.py | 332 ++++++ zscaler/zpa/policy_group_set.py | 359 +++++++ zscaler/zpa/segment_groups.py | 222 ++-- zscaler/zpa/zpa_service.py | 46 +- 121 files changed, 18742 insertions(+), 2324 deletions(-) create mode 100644 docsrc/zs/aiguard/index.rst create mode 100644 docsrc/zs/aiguard/llm_application_credentials.rst create mode 100644 docsrc/zs/aiguard/llm_applications.rst create mode 100644 docsrc/zs/aiguard/llm_provider_credentials.rst create mode 100644 docsrc/zs/aiguard/llm_providers.rst create mode 100644 docsrc/zs/aiguard/policies.rst create mode 100644 docsrc/zs/aiguard/policy_detection.rst create mode 100644 docsrc/zs/aiguard/policy_match_rules.rst delete mode 100644 docsrc/zs/zaiguard/index.rst delete mode 100644 docsrc/zs/zaiguard/policy_detection.rst create mode 100644 docsrc/zs/zpa/policy_group.rst create mode 100644 docsrc/zs/zpa/policy_group_rule.rst create mode 100644 docsrc/zs/zpa/policy_group_set.rst rename tests/integration/{zaiguard/cassettes/.gitkeep => aiguard/__init__.py} (100%) create mode 100644 tests/integration/aiguard/cassettes/.gitkeep create mode 100644 tests/integration/aiguard/cassettes/TestLlmApplicationCredentials.yaml create mode 100644 tests/integration/aiguard/cassettes/TestLlmApplications.yaml create mode 100644 tests/integration/aiguard/cassettes/TestLlmProviderCredentials.yaml create mode 100644 tests/integration/aiguard/cassettes/TestLlmProviders.yaml create mode 100644 tests/integration/aiguard/cassettes/TestPolicies.yaml create mode 100644 tests/integration/aiguard/cassettes/TestPolicyMatchRules.yaml create mode 100644 tests/integration/aiguard/conftest.py create mode 100644 tests/integration/aiguard/sweep/run_sweep.py create mode 100644 tests/integration/aiguard/test_llm_application_credentials.py create mode 100644 tests/integration/aiguard/test_llm_applications.py create mode 100644 tests/integration/aiguard/test_llm_provider_credentials.py create mode 100644 tests/integration/aiguard/test_llm_providers.py create mode 100644 tests/integration/aiguard/test_policies.py create mode 100644 tests/integration/aiguard/test_policy_match_rules.py delete mode 100644 tests/integration/zaiguard/README.md delete mode 100644 tests/integration/zaiguard/cassettes/TestPolicyDetection.yaml delete mode 100644 tests/integration/zaiguard/conftest.py delete mode 100644 tests/integration/zaiguard/test_policy_detection.py delete mode 100644 tests/integration/zaiguard/test_zaiguard_unit.py create mode 100644 zscaler/aiguard/__init__.py create mode 100644 zscaler/aiguard/aiguard_service.py rename zscaler/{zaiguard => aiguard}/legacy.py (98%) create mode 100644 zscaler/aiguard/llm_application_credentials.py create mode 100644 zscaler/aiguard/llm_applications.py create mode 100644 zscaler/aiguard/llm_provider_credentials.py create mode 100644 zscaler/aiguard/llm_providers.py rename {tests/integration/zaiguard => zscaler/aiguard/models}/__init__.py (100%) create mode 100644 zscaler/aiguard/models/llm_application_credentials.py create mode 100644 zscaler/aiguard/models/llm_applications.py create mode 100644 zscaler/aiguard/models/llm_provider_credentials.py create mode 100644 zscaler/aiguard/models/llm_providers.py create mode 100644 zscaler/aiguard/models/policies.py rename zscaler/{zaiguard => aiguard}/models/policy_detection.py (100%) create mode 100644 zscaler/aiguard/models/policy_match_rules.py create mode 100644 zscaler/aiguard/policies.py rename zscaler/{zaiguard => aiguard}/policy_detection.py (88%) create mode 100644 zscaler/aiguard/policy_match_rules.py delete mode 100644 zscaler/zaiguard/__init__.py delete mode 100644 zscaler/zaiguard/models/__init__.py delete mode 100644 zscaler/zaiguard/zaiguard_service.py create mode 100644 zscaler/zia/dlp_endpoint_resource.py create mode 100644 zscaler/zia/dns_application_groups.py create mode 100644 zscaler/zia/end_user_notification_templates.py create mode 100644 zscaler/zia/endpoint_application_groups.py create mode 100644 zscaler/zia/endpoint_applications.py create mode 100644 zscaler/zia/endpoint_custom_apps.py create mode 100644 zscaler/zia/endpoint_dlp_resource_groups.py create mode 100644 zscaler/zia/endpoint_dlp_rules.py create mode 100644 zscaler/zia/endpoint_dlp_sub_rules.py create mode 100644 zscaler/zia/ips_categories.py create mode 100644 zscaler/zia/models/dlp_endpoint_resource.py create mode 100644 zscaler/zia/models/dns_application_groups.py create mode 100644 zscaler/zia/models/endpoint_application_groups.py create mode 100644 zscaler/zia/models/endpoint_applications_custom_apps.py create mode 100644 zscaler/zia/models/endpoint_applications_custom_apps_lite.py create mode 100644 zscaler/zia/models/endpoint_applications_policies.py create mode 100644 zscaler/zia/models/endpoint_dlp_resource_groups.py create mode 100644 zscaler/zia/models/endpoint_dlp_resource_groups_resources.py create mode 100644 zscaler/zia/models/endpoint_dlp_rules.py create mode 100644 zscaler/zia/models/eun_feature_enablement_status.py create mode 100644 zscaler/zia/models/eun_template_product.py create mode 100644 zscaler/zia/models/eun_user_confirmation_product.py create mode 100644 zscaler/zia/models/ips_categories.py create mode 100644 zscaler/zia/models/nss_collectors.py create mode 100644 zscaler/zia/models/outbound_email_dlp_rules.py create mode 100644 zscaler/zia/models/web_dlp_global_options.py create mode 100644 zscaler/zia/nss_collectors.py create mode 100644 zscaler/zia/outbound_email_dlp_rules.py create mode 100644 zscaler/zia/web_dlp_global_options.py create mode 100644 zscaler/zpa/models/policy_group.py create mode 100644 zscaler/zpa/models/policy_group_set.py create mode 100644 zscaler/zpa/models/policy_group_set_summary.py create mode 100644 zscaler/zpa/models/policy_group_set_summary_stat.py create mode 100644 zscaler/zpa/models/policy_rule.py create mode 100644 zscaler/zpa/policy_group.py create mode 100644 zscaler/zpa/policy_group_rule.py create mode 100644 zscaler/zpa/policy_group_set.py diff --git a/.gitignore b/.gitignore index ee289177..fd9b8daf 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ ENV/ # Configtree diagram generated by sphinx docs/_diagrams local_dev +python_model_generation .vscode .claude diff --git a/CHANGELOG.md b/CHANGELOG.md index 8919a7cf..584ce0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,181 @@ # Zscaler Python SDK Changelog +## 1.9.39 (July 27, 2026) + +### Notes + +- Python Versions: **v3.9, v3.10, v3.11, v3.12** + +### Enhancements + +### New ZIA Endpoints + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoints + - `GET /webDlpGlobalOptions` Retrieves the DLP Advanced Settings information + - `PUT /webDlpGlobalOptions` Updates the existing DLP Advanced Settings. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Applications endpoints + - `GET /endPointApplications` Retrieves the list of endpoint applications. + - `GET /endPointApplications/lite` Retrieves a lightweight list of endpoint applications. + - `GET /endPointApplications/count` Retrieves the count of all endpoint applications. + - `GET /endPointApplications/cloudApps/count` Retrieves the count of well-known and discovered endpoint applications. + - `GET /endPointApplications/policies` Retrieves the list of policy rules associated with the specified endpoint applications. + - `GET /endPointApplications/getCategoriesWithNonEmptyApps` Retrieves the categories that currently have endpoint applications grouped within them. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Custom Applications endpoints + - `GET /endPointApplications/customApps` Retrieves the list of custom endpoint applications. + - `GET /endPointApplications/customApp/{id}` Retrieves the custom endpoint application based on the specified ID. + - `POST /endPointApplications/customApp` Adds a new custom endpoint application. + - `PUT /endPointApplications/customApp/{id}` Updates the custom endpoint application based on the specified ID. + - `DELETE /endPointApplications/customApp/{id}` Deletes the custom endpoint application based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Application Groups endpoints + - `GET /endPointApplicationGroups` Retrieves the list of application tag groups. + - `GET /endPointApplicationGroups/policies` Retrieves the list of policy rules associated with the specified application tag groups. + - `POST /endPointApplicationGroups` Adds a new application tag group. + - `PUT /endPointApplicationGroups/{id}` Updates the application tag group based on the specified ID. + - `PUT /endPointApplicationGroups/{id}/resources` Updates the applications associated with the specified tag group. + - `DELETE /endPointApplicationGroups/{id}` Deletes the application tag group based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Resources endpoints + - `GET /dlpEndpointResource/{channel}` Retrieves the list of DLP resources configured for the specified channel. + - `GET /dlpEndpointResource/{channel}/{id}` Retrieves the DLP resource based on the specified channel and ID. + - `GET /dlpEndpointResource/{id}/groups` Retrieves the list of tags to which the specified DLP resource is associated. + - `POST /dlpEndpointResource` Adds a new DLP endpoint resource. + - `PUT /dlpEndpointResource/{id}` Updates the DLP endpoint resource based on the specified ID. + - `DELETE /dlpEndpointResource/{id}` Deletes the DLP endpoint resource based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Resource Groups endpoints + - `GET /endPointDlpResourceGroups/{channel}` Retrieves the list of DLP resource tags added for the specified channel. + - `GET /endPointDlpResourceGroups/{id}/resources` Retrieves the DLP resources associated with the specified tag group. + - `PUT /endPointDlpResourceGroups/{id}/resources` Updates the DLP resources associated with the specified tag group. + - `POST /endPointDlpResourceGroups` Adds a new DLP resource tag group. + - `PUT /endPointDlpResourceGroups/{id}` Updates the DLP resource tag group based on the specified ID. + - `DELETE /endPointDlpResourceGroups/{id}` Deletes the DLP resource tag group based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Rules endpoints + - `GET /endPointDlpRules` Retrieves a list of Endpoint DLP rules. + - `GET /endPointDlpRules/{id}` Retrieves the Endpoint DLP rule based on the specified ID. + - `GET /endPointDlpRules/fileTypeCategories` Retrieves the file type categories supported by Endpoint DLP rules. + - `POST /endPointDlpRules` Adds a new Endpoint DLP rule. + - `PUT /endPointDlpRules/{id}` Updates the Endpoint DLP rule based on the specified ID. + - `DELETE /endPointDlpRules/{id}` Deletes the Endpoint DLP rule based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Endpoint DLP Exception (Sub) Rules endpoints + - `POST /endPointDlpRules/{id}/subRule` Adds a new exception (sub) rule to an existing Endpoint DLP rule. + - `PUT /endPointDlpRules/{id}/subRule/{subRuleId}` Updates the Endpoint DLP exception (sub) rule based on the specified IDs. + - `DELETE /endPointDlpRules/{id}/subRule/{subRuleId}` Deletes the Endpoint DLP exception (sub) rule based on the specified IDs. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA Outbound Email DLP endpoint + - `GET /emailDlpRules/actions` Retrieves the supported Outbound Email DLP rule actions for the specified email tenants as a CSV file. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA DNS Application Groups endpoints + - `GET /dnsApplicationGroups` Retrieves a list of DNS application groups. + - `GET /dnsApplicationGroups/{id}` Retrieves the DNS application group based on the specified ID. + - `POST /dnsApplicationGroups` Adds a new DNS application group. + - `PUT /dnsApplicationGroups/{id}` Updates the DNS application group based on the specified ID. + - `DELETE /dnsApplicationGroups/{id}` Deletes the DNS application group based on the specified ID. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZIA End User Notification endpoints + - `GET /eunTemplate/{templateType}/product/{product}` Retrieves the browser-based/ZCC end user notification template for the specified template type and product. + - `GET /eunTemplate/{templateType}/featureEnablementStatus` Retrieves the feature enablement status for the specified end user notification template type. + - `GET /userConfirmation/product/{product}` Retrieves the user confirmation template by policy for the specified product. + - `GET /userConfirmation/globalDefaultTemplates` Retrieves the global default user confirmation templates. + - `GET /userConfirmation/{templateType}/featureEnablementStatus` Retrieves the notification enablement feature status for the specified template type. + +### New ZPA Endpoints + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZPA Policy Group Controller Endpoints + - `GET /policyGroupSet/{groupSetId}/group/{groupId}` Get a specific Policy Group by ID within a Policy Group Set + - `PUT /policyGroupSet/{groupSetId}/group/{groupId}` Update an existing Policy Group. + - `DELETE /policyGroupSet/{groupSetId}/group/{groupId}` Delete an existing Policy Group. + - `POST /policyGroupSet/{groupSetId}/group/search` Get All Policy Groups within a Policy Group Set with advanced search and pagination. + - `POST /policyGroupSet/{groupSetId}/group/{groupId}/reorder/{newOrder}` Update an existing Policy Group Order. + - `GET /policyGroupSet/{groupSetId}/group/all` Get All Policy Groups within a Policy Group Set. + - `GET /policyGroupSet/{groupSetId}/group` Add a new Policy Group to a Policy Group Set. + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZPA Policy Group Rule Controller Endpoints + - `GET /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}` Get a policy rule within a policy group + - `DELETE /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}` Delete a policy rule within a policy group + - `GET /policyGroupSet/{groupSetId}/group/{groupId}/rule` Get All Policy Groups Rules within a Policy Group with advanced search and pagination. + - `POST /policyGroupSet/{groupSetId}/group/{groupId}/rule` Add a new policy rule for a given policy group. + - `PUT /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}/reorder/{newOrder}` Update rule order of a rule within policy group + +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added the following new ZPA Policy Group Set Controller Endpoints + - `GET /policyGroupSet/{groupSetId}` Get a specific Policy Group Set by ID. + - `GET /policyGroupSet` Get all Policy Group Sets for a customer. + - `GET /policyGroupSet/policyType/{policyType}/rules` Get paginated rules across groups within a Policy Group Set. + - `GET /policyGroupSet/policyType/{policyType}/summary` Get Policy Group Set Summary fo a customer for policy type. + - `GET /policyGroupSet/policyType/{policyType}` Get Policy Group Set fo a customer for policy type. + - `GET /policyGroupSet/policyType/{policyType}/summaryStats` Get summary stats for groups and rules within a Policy Group Set. + +#### Zscaler AI Guard (AIGuard) New Service and Endpoints +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - Added full OneAPI support for the Zscaler AI Guard (`aiguard`) service. AI Guard resources are now accessible via `client.aiguard`. All AI Guard configuration resources are supported through the OneAPI client (Zidentity); the policy detection endpoints (`/v1/detection/*`) are **not** exposed through OneAPI and remain available via `LegacyAIGuardClient` (see below). The following new AI Guard API endpoints were added: + - Added `GET /detections/policies` to retrieve the list of detection policies. + - Added `GET /detections/policies/{policyId}` to retrieve a detection policy by ID. + - Added `GET /detections/policies/name/{name}` to retrieve a detection policy by name. + - Added `POST /detections/policies` to create a new detection policy. + - Added `PUT /detections/policies/{policyId}` to update a detection policy. + - Added `DELETE /detections/policies/{policyId}` to delete a detection policy. + - Added `GET /detections/policy-match-rules` to retrieve the list of policy match rules. + - Added `GET /detections/policy-match-rules/{ruleId}` to retrieve a policy match rule by ID. + - Added `GET /detections/policy-match-rules/name/{name}` to retrieve a policy match rule by name. + - Added `POST /detections/policy-match-rules` to create a new policy match rule. + - Added `PUT /detections/policy-match-rules/{ruleId}` to update a policy match rule. + - Added `DELETE /detections/policy-match-rules/{ruleId}` to delete a policy match rule. + - Added `GET /llm-providers` to retrieve the list of LLM providers. + - Added `GET /llm-providers/{providerId}` to retrieve an LLM provider by ID. + - Added `GET /llm-providers/name/{name}` to retrieve an LLM provider by name. + - Added `GET /llm-providers/{providerId}/referential-check` to retrieve resources referencing an LLM provider. + - Added `GET /llm-provider-types` to retrieve the list of supported LLM provider types. + - Added `GET /llm-provider-types/{type}` to retrieve a specific LLM provider type. + - Added `POST /llm-providers` to create a new LLM provider. + - Added `PUT /llm-providers/{providerId}` to update an LLM provider. + - Added `DELETE /llm-providers/{providerId}` to delete an LLM provider. + - Added `GET /llm-provider-credentials` to retrieve the list of LLM provider credentials. + - Added `GET /llm-provider-credentials/{credentialId}` to retrieve an LLM provider credential by ID. + - Added `GET /llm-provider-credentials/name/{name}` to retrieve an LLM provider credential by name. + - Added `GET /llm-provider-credentials/{credentialId}/referential-check` to retrieve resources referencing an LLM provider credential. + - Added `POST /llm-provider-credentials` to create a new LLM provider credential. + - Added `PUT /llm-provider-credentials/{credentialId}` to update an LLM provider credential. + - Added `DELETE /llm-provider-credentials/{credentialId}` to delete an LLM provider credential. + - Added `GET /llm-applications` to retrieve the list of LLM applications. + - Added `GET /llm-applications/{applicationId}` to retrieve an LLM application by ID. + - Added `GET /llm-applications/name/{name}` to retrieve an LLM application by name. + - Added `GET /llm-applications/{applicationId}/referential-check` to retrieve resources referencing an LLM application. + - Added `POST /llm-applications` to create a new LLM application. + - Added `PUT /llm-applications/{applicationId}` to update an LLM application. + - Added `DELETE /llm-applications/{applicationId}` to delete an LLM application. + - Added `GET /llm-application-credentials` to retrieve the list of LLM application credentials. + - Added `GET /llm-application-credentials/{credentialId}` to retrieve an LLM application credential by ID. + - Added `GET /llm-application-credentials/name/{name}` to retrieve an LLM application credential by name. + - Added `GET /llm-application-credentials/{credentialId}/referential-check` to retrieve resources referencing an LLM application credential. + - Added `POST /llm-application-credentials` to create a new LLM application credential. + - Added `POST /llm-application-credentials/{credentialId}/regenerate` to regenerate an LLM application credential. + - Added `PUT /llm-application-credentials/{credentialId}` to update an LLM application credential. + - Added `DELETE /llm-application-credentials/{credentialId}` to delete an LLM application credential. + +#### Zscaler AI Guard (AIGuard) Policy Detection via Legacy Client +[PR #554](https://github.com/zscaler/zscaler-sdk-python/pull/554) - The AI Guard policy detection endpoints are **not** exposed through OneAPI and are therefore served by the AI Guard legacy client, `LegacyAIGuardClient`: + - `POST /v1/detection/execute-policy` - executes a specific detection policy against content. + - `POST /v1/detection/resolve-and-execute-policy` - resolves the applicable detection policy and executes it against content. + + These are reached via `client.aiguard.policy_detection` on a `LegacyAIGuardClient`, which authenticates with an AI Guard API key (`api_key` / `AIGUARD_API_KEY`) against `https://api..zseclipse.net`. All other AI Guard resources remain OneAPI only via `ZscalerClient`. + + ```py + from zscaler.oneapi_client import LegacyAIGuardClient + + with LegacyAIGuardClient({"api_key": "", "cloud": "us1"}) as client: + result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + content="User prompt or AI response to scan", + direction="IN", + ) + ``` + +### Deprecations + +* [PR #549](https://github.com/zscaler/zscaler-sdk-python/pull/549) - AI Guard configuration resources are supported via the OneAPI client. The AI Guard legacy client is retained as `LegacyAIGuardClient` **solely** for policy detection (`client.aiguard.policy_detection`), because `/v1/detection/execute-policy` and `/v1/detection/resolve-and-execute-policy` are not exposed through OneAPI. The `client.zguard` property remains as a deprecated alias for `client.aiguard`. + ## 1.9.38 (July 14, 2026) ### Notes diff --git a/CLAUDE.md b/CLAUDE.md index d4d2b28c..37dedcc6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,6 +97,60 @@ These shadow `transform_common_id_fields` for those fields (the helper finds not `add_id_groups` (also in `zscaler/utils.py`) is the original ZPA helper and never coerces. It still exists for backwards compatibility and is kept in `application_segment.py`'s `add_segment_provision` flow and `pra_credential_pool.py`. New code in ZPA should prefer `transform_common_id_fields(..., coerce_ids=False)` so the service converges on a single helper. +## AI Guard-Specific Architecture + +AI Guard is the only product **split across two authentication paths**. Getting this wrong sends requests to a gateway that returns `404` with an empty body. + +| Resources | Client | Base URL | +|---|---|---| +| `policies`, `policy_match_rules`, `llm_providers`, `llm_provider_credentials`, `llm_applications`, `llm_application_credentials` | `ZscalerClient` (OneAPI) | `api.zsapi.net/aiguard/v1/*` | +| `policy_detection` **only** | `LegacyAIGuardClient` | `api..zseclipse.net/v1/detection/*` | + +The policy detection endpoints (`/v1/detection/execute-policy`, `/v1/detection/resolve-and-execute-policy`) were never onboarded to OneAPI. `AIGuardService` deliberately does **not** expose `policy_detection` — advertising it there would route users to a 404. Both paths are reached through `client.aiguard`; which one you get depends on the client you constructed. + +### Service-type routing order matters + +In `request_executor.py`'s `get_service_type`, the `/aiguard` check **must** come before the `/v1/detection/` check: + +```python +elif "/aiguard" in url: # OneAPI - checked FIRST + return "aiguard" +elif "/v1/detection/" in url: # legacy policy detection + return "aiguard_legacy" +``` + +`/aiguard/v1/detections/policies` contains the substring `/v1/detection`, so the reverse order silently misroutes every OneAPI policy call to the legacy client. Note the trailing slash on `/v1/detection/` — it prevents matching `/detections`. + +### Legacy client wiring + +`LegacyZGuardClientHelper` lives in `zscaler/aiguard/legacy.py` (repo convention: `/legacy.py`; there is no separate `zaiguard` package). The `aiguard_legacy_client` kwarg is threaded through `Client.__init__` → `RequestExecutor` → `HTTPClient`; all three must accept it. Auth is `Authorization: Bearer `, config keys `api_key` / `cloud` / `timeout`, env vars `AIGUARD_API_KEY` / `AIGUARD_CLOUD` / `AIGUARD_OVERRIDE_URL`. + +`Client.__exit__` early-returns when `aiguard_legacy_client` is set: AI Guard authenticates per request and has no session to close or `deauthenticate` endpoint, unlike ZIA/ZTW. + +### Pagination + +AI Guard list endpoints return `{"items": [...]}` rather than a bare array. `oneapi_response.py` has a dedicated `aiguard` branch that unwraps it. + +### API constraints worth knowing before writing tests + +These are enforced server-side and each one produced a real test failure: + +- **Public providers are not editable** (`"A public provider is not editable."`), and private ones require a `servers` payload (`"'servers' is required for a private (public=false) provider."`). A create→update→delete lifecycle cannot be written against `llm_providers` without a `servers` body. +- **`encryptEventContents: true`** on an LLM application requires a customer-managed key (CMK) in tenant settings. +- **`apiCredentials`** is required when creating a provider credential, and is **write-only** — never returned in responses, so it cannot be asserted on. +- **Policy match rules 409** (`already_exists`) when the referenced application/credential is already claimed by another rule. Create the full chain (policy → application → application credential) inside the test rather than reusing shared tenant fixtures. +- **`LlmProviderCredentials` has no `id` attribute** in the model; read it from the raw response body (`response.get_body()["id"]`). +- **`referential_check`** (all four resources) and **`regenerate_credential`** return 404 against the live API. `referential_check` is commented out in the clients and removed from the manifests so codegen does not reintroduce it. + +### Cassette scrubbing + +`tests/conftest.py` scrubs two AI Guard credential paths — both are required, and neither is covered by `filter_post_data_parameters` (that only handles form-encoded bodies, and AI Guard sends JSON): + +- **request**: `apiCredentials.key`, scoped to the `apiCredentials` object so the generic `"key"` field is not redacted globally +- **response**: the generated `key` returned by `POST /llm-application-credentials`, scoped to bodies containing `providerCredentialsId` (provider-type payloads legitimately carry `"key": "publicApi"`) + +Recording: `make test:vcr:record:aiguard` deletes cassettes first, because `vcr_config` pins `record_mode` to `new_episodes` when `MOCK_TESTS=false`, which overrides `--record-mode=rewrite`. With `match_on=[method, path, query]` the body is not matched, so a stale cassette replays the old response even after the request payload changes. + ## ZCell-Specific Architecture Every ZCell endpoint is scoped to a customer (`/customers/{id}`). Rather than forcing callers to repeat that id on every call, the SDK resolves it once and auto-injects it — analogous to, but **completely independent from**, ZPA's `customerId`. Never conflate `zcellCustomerId` with ZPA's `customerId`. diff --git a/Makefile b/Makefile index 7a917c08..08ba29f2 100644 --- a/Makefile +++ b/Makefile @@ -42,10 +42,18 @@ help: @echo "$(COLOR_OK) lint:zins Check style with ruff for zins packages$(COLOR_NONE)" @echo "$(COLOR_OK) lint:zms Check style with ruff for zms packages$(COLOR_NONE)" @echo "$(COLOR_OK) lint:zbi Check style with ruff for zbi packages$(COLOR_NONE)" - @echo "$(COLOR_OK) lint:zaiguard Check style with ruff for zaiguard packages$(COLOR_NONE)" + @echo "$(COLOR_OK) lint:aiguard Check style with ruff for aiguard packages$(COLOR_NONE)" @echo "$(COLOR_OK) lint:ztb Check style with ruff for ztb packages$(COLOR_NONE)" @echo "$(COLOR_OK) lint:zwa Check style with ruff for zwa packages$(COLOR_NONE)" @echo "$(COLOR_OK) coverage Check code coverage quickly with the default Python$(COLOR_NONE)" + @echo "$(COLOR_WARNING)codegen (python_model_generation/)$(COLOR_NONE)" + @echo "$(COLOR_OK) manifests PRODUCT=zpa List a spec's controllers (add SECTION=\"...\" to generate manifests+payloads)$(COLOR_NONE)" + @echo "$(COLOR_OK) generate PRODUCT=zia Generate models + API clients for a product (into generated_*/)$(COLOR_NONE)" + @echo "$(COLOR_OK) generate:models PRODUCT=zia Generate only models from json_payloads/$(COLOR_NONE)" + @echo "$(COLOR_OK) generate:clients PRODUCT=zia Generate only API clients from api_manifests/$(COLOR_NONE)" + @echo "$(COLOR_OK) generate:tests PRODUCT=zia Generate only VCR integration tests$(COLOR_NONE)" + @echo "$(COLOR_OK) promote PRODUCT=zia Promote generated files into zscaler/ + wire *_service.py$(COLOR_NONE)" + @echo "$(COLOR_OK) promote:dry PRODUCT=zia Preview promotion without writing anything$(COLOR_NONE)" @echo "$(COLOR_WARNING)test$(COLOR_NONE)" @echo "$(COLOR_OK) test:all Run all tests$(COLOR_NONE)" @echo "$(COLOR_OK) test:unit Run only unit tests$(COLOR_NONE)" @@ -58,8 +66,12 @@ help: @echo "$(COLOR_OK) test:integration:zins Run only zins integration tests$(COLOR_NONE)" @echo "$(COLOR_OK) test:integration:zms Run only zms integration tests$(COLOR_NONE)" @echo "$(COLOR_OK) test:integration:zbi Run only zbi integration tests$(COLOR_NONE)" - @echo "$(COLOR_OK) test:integration:zaiguard Run only zaiguard integration tests$(COLOR_NONE)" @echo "$(COLOR_OK) test:integration:ztb Run only ztb integration tests$(COLOR_NONE)" + @echo "$(COLOR_OK) test:integration:aiguard Run only aiguard integration tests$(COLOR_NONE)" + @echo "$(COLOR_OK) test:vcr:record:aiguard Record aiguard VCR cassettes against a live tenant$(COLOR_NONE)" + @echo "$(COLOR_OK) test:vcr:playback:aiguard Replay aiguard VCR cassettes (no credentials needed)$(COLOR_NONE)" + @echo "$(COLOR_OK) coverage:aiguard Coverage report for the aiguard package$(COLOR_NONE)" + @echo "$(COLOR_OK) sweep:aiguard Delete leftover aiguard tests- resources$(COLOR_NONE)" @echo "$(COLOR_WARNING)security$(COLOR_NONE)" @echo "$(COLOR_OK) security-scan Run Trivy (vuln + secret scan, excludes local_dev/openapi)$(COLOR_NONE)" @echo "$(COLOR_WARNING)build$(COLOR_NONE)" @@ -145,9 +157,9 @@ lint\:zeasm: poetry run ruff check zscaler/zeasm --select I poetry run ruff check zscaler/zeasm -lint\:zaiguard: - poetry run ruff check zscaler/zaiguard --select I - poetry run ruff check zscaler/zaiguard +lint\:aiguard: + poetry run ruff check zscaler/aiguard --select I + poetry run ruff check zscaler/aiguard format: poetry run black . @@ -155,6 +167,50 @@ format: check-format: poetry run black --check --diff . +# --------------------------------------------------------------------------- +# Code generation (python_model_generation/) +# generate -> json_payloads/ + api_manifests/ into generated_*/ (review) +# promote -> generated_*/ into zscaler// (+ *_service.py wiring) +# All require PRODUCT, e.g. make generate PRODUCT=zia +# --------------------------------------------------------------------------- +GEN_DIR=python_model_generation + +_require-product: + @if [ -z "$(PRODUCT)" ]; then \ + echo "$(COLOR_ERROR)PRODUCT is required, e.g. make generate PRODUCT=zia$(COLOR_NONE)"; \ + exit 1; \ + fi + +# Derive manifests + payloads from a product's OpenAPI spec (ZPA today). +# make manifests PRODUCT=zpa -> list controllers +# make manifests PRODUCT=zpa SECTION="Policy Group" +manifests: _require-product + @if [ -z "$(SECTION)" ]; then \ + poetry run python $(GEN_DIR)/generate_manifests.py --product $(PRODUCT) --list; \ + else \ + poetry run python $(GEN_DIR)/generate_manifests.py --product $(PRODUCT) --section "$(SECTION)"; \ + fi + +generate\:models: _require-product + poetry run python $(GEN_DIR)/generate_models.py --product $(PRODUCT) + +generate\:clients: _require-product + poetry run python $(GEN_DIR)/generate_api_client.py --product $(PRODUCT) + +generate\:tests: _require-product + poetry run python $(GEN_DIR)/generate_tests.py --product $(PRODUCT) + +generate: _require-product + poetry run python $(GEN_DIR)/generate_models.py --product $(PRODUCT) + poetry run python $(GEN_DIR)/generate_api_client.py --product $(PRODUCT) + poetry run python $(GEN_DIR)/generate_tests.py --product $(PRODUCT) + +promote: _require-product + poetry run python $(GEN_DIR)/promote.py --product $(PRODUCT) --all + +promote\:dry: _require-product + poetry run python $(GEN_DIR)/promote.py --product $(PRODUCT) --all --dry-run + test\:unit: @echo "$(COLOR_ZSCALER)Running unit tests...$(COLOR_NONE)" poetry run pytest tests/unit --disable-warnings -v @@ -191,6 +247,10 @@ test\:integration\:zins: @echo "$(COLOR_ZSCALER)Running zins integration tests...$(COLOR_NONE)" poetry run pytest tests/integration/zins --disable-warnings +test\:integration\:aiguard: + @echo "$(COLOR_ZSCALER)Running aiguard integration tests...$(COLOR_NONE)" + poetry run pytest tests/integration/aiguard --disable-warnings + test\:integration\:zms: @echo "$(COLOR_ZSCALER)Running zms integration tests...$(COLOR_NONE)" poetry run pytest tests/integration/zms --disable-warnings @@ -246,6 +306,9 @@ coverage\:zbi: coverage\:zeasm: poetry run pytest tests/integration/zeasm --cov=zscaler/zeasm --cov-report xml --cov-report term + +coverage\:aiguard: + poetry run pytest tests/integration/aiguard --cov=zscaler/aiguard --cov-report xml --cov-report term # ========================================== # VCR Testing Commands # ========================================== @@ -323,11 +386,26 @@ test\:vcr\:record\:ztw: MOCK_TESTS=false poetry run pytest tests/integration/ztw --record-mode=rewrite -v --disable-warnings # Record VCR cassettes for ZEASM +# Record VCR cassettes for AI Guard +# tests/conftest.py's vcr_config pins record_mode to "new_episodes" when MOCK_TESTS=false, +# which overrides --record-mode=rewrite. Combined with match_on=[method, path, query] that +# replays a previously recorded response for the same endpoint even when the request body +# has changed, so stale cassettes are removed first to force a genuine re-record. +test\:vcr\:record\:aiguard: + @echo "$(COLOR_ZSCALER)Recording AI Guard VCR cassettes...$(COLOR_NONE)" + find tests/integration/aiguard/cassettes -name '*.yaml' -delete + MOCK_TESTS=false poetry run pytest tests/integration/aiguard --record-mode=rewrite -v --disable-warnings + test\:vcr\:record\:zeasm: @echo "$(COLOR_ZSCALER)Recording ZEASM VCR cassettes...$(COLOR_NONE)" MOCK_TESTS=false poetry run pytest tests/integration/zeasm --record-mode=rewrite -v --disable-warnings # Playback VCR cassettes for ZIA (no credentials needed) +# Playback VCR cassettes for AI Guard (no credentials needed) +test\:vcr\:playback\:aiguard: + @echo "$(COLOR_ZSCALER)Playing back AI Guard VCR cassettes...$(COLOR_NONE)" + MOCK_TESTS=true poetry run pytest tests/integration/aiguard -v --disable-warnings + test\:vcr\:playback\:zia: @echo "$(COLOR_ZSCALER)Playing back ZIA VCR cassettes...$(COLOR_NONE)" MOCK_TESTS=true poetry run pytest tests/integration/zia -v --disable-warnings @@ -402,6 +480,10 @@ sweep\:zins: @echo "$(COLOR_WARNING)WARNING: This will destroy infrastructure. Use only in development accounts.$(COLOR_NONE)" ZINS_SDK_TEST_SWEEP=true poetry run python tests/integration/zins/sweep/run_sweep.py --sweep +sweep\:aiguard: + @echo "$(COLOR_WARNING)WARNING: This will destroy infrastructure. Use only in development accounts.$(COLOR_NONE)" + AIGUARD_SDK_TEST_SWEEP=true poetry run python tests/integration/aiguard/sweep/run_sweep.py --sweep + build\:dist: rm -rf dist build @@ -453,4 +535,4 @@ security\:install: @echo "$(COLOR_ZSCALER)Installing secret detection tools...$(COLOR_NONE)" ./scripts/check-secrets.sh --install -.PHONY: clean-pyc clean-build docs clean +.PHONY: clean-pyc clean-build docs clean _require-product manifests generate generate\:models generate\:clients generate\:tests promote promote\:dry diff --git a/README.md b/README.md index 70e9ed06..bab09072 100644 --- a/README.md +++ b/README.md @@ -1404,6 +1404,83 @@ if __name__ == "__main__": **Note:** `client.zmicroseg` is available as an alias for `client.zms`. +### Zscaler AI Guard (aiguard) + +AI Guard provides configuration and detection APIs to secure the use of generative AI — detection policies, policy match rules, LLM providers, LLM applications, and their credentials. + +AI Guard is split across **two** authentication paths: + +| Resource | Client | Endpoint | +|---|---|---| +| Detection policies, policy match rules, LLM providers/applications and their credentials | `ZscalerClient` (OneAPI) | `/aiguard/v1/*` | +| `policy_detection` — `execute_policy`, `resolve_and_execute_policy` | `LegacyAIGuardClient` | `/v1/detection/*` | + +**Important:** the policy detection endpoints are **not** exposed through OneAPI. They must be +called with `LegacyAIGuardClient`, which authenticates with an AI Guard API key against +`https://api..zseclipse.net`. Every other AI Guard resource is OneAPI only. + +```py +from zscaler import ZscalerClient + +config = { + "clientId": '{yourClientId}', + "clientSecret": '{yourClientSecret}', + "vanityDomain": '{yourvanityDomain}', + "cloud": "beta", +} + +def main(): + with ZscalerClient(config) as client: + policies, _, err = client.aiguard.policies.list_policies() + if err: + print(f"Error: {err}") + return + for policy in policies: + print(policy.as_dict()) + +if __name__ == "__main__": + main() +``` + +**Available Resources (via `client.aiguard.`):** + +- `policies` — List, get (by ID or name), create, update, and delete detection policies +- `policy_match_rules` — List, get (by ID or name), create, update, and delete policy match rules +- `llm_providers` — Manage LLM providers, list provider types, and run referential checks +- `llm_provider_credentials` — Manage LLM provider credentials and run referential checks +- `llm_applications` — Manage LLM applications and run referential checks +- `llm_application_credentials` — Manage LLM application credentials + +**Policy detection (legacy client only):** + +```py +from zscaler.oneapi_client import LegacyAIGuardClient + +config = { + "api_key": '{yourAIGuardApiKey}', # or the AIGUARD_API_KEY environment variable + "cloud": "us1", # or AIGUARD_CLOUD +} + +def main(): + with LegacyAIGuardClient(config) as client: + result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + content="User prompt or AI response to scan", + direction="IN", + ) + if err: + print(f"Error: {err}") + return + print(result.as_dict()) + +if __name__ == "__main__": + main() +``` + +- `policy_detection` — Execute a detection policy (`execute_policy`) or resolve-and-execute + (`resolve_and_execute_policy`) against content. **Requires `LegacyAIGuardClient`.** + +**Note:** `client.zguard` is available as a deprecated alias for `client.aiguard`. + ## Zscaler Legacy API Framework The legacy Zscaler API is still utilized by several customers, and will remain in place for the foreseeable future with no specific announced deprecation date. diff --git a/docsrc/conf.py b/docsrc/conf.py index 3f02b66b..1ad53974 100644 --- a/docsrc/conf.py +++ b/docsrc/conf.py @@ -28,9 +28,9 @@ html_title = "" # The short X.Y version -version = "1.9.38" +version = "1.9.39" # The full version, including alpha/beta/rc tags -release = "1.9.38" +release = "1.9.39" # -- General configuration --------------------------------------------------- diff --git a/docsrc/index.rst b/docsrc/index.rst index 9abbe1a4..3ea784ab 100644 --- a/docsrc/index.rst +++ b/docsrc/index.rst @@ -19,7 +19,7 @@ zs/zms/index zs/zbi/index zs/zeasm/index - zs/zaiguard/index + zs/aiguard/index zs/guides/index Official Python SDK for the Zscaler Products @@ -223,6 +223,15 @@ must use the respective Legacy API client described in the following section: `Zscaler Legacy API Framework <#zscaler-legacy-api-framework>`__ +.. note:: + + **AI Guard is an exception.** Its policy detection endpoints + (``/v1/detection/execute-policy`` and + ``/v1/detection/resolve-and-execute-policy``) are not exposed through OneAPI, + so they require ``LegacyAIGuardClient`` regardless of whether your tenant has + been migrated to Zidentity. Every other AI Guard resource is OneAPI only. See + :doc:`zs/aiguard/index`. + **Caution**: Zscaler does not recommend hard-coding credentials into arguments, as they can be exposed in plain text in version control systems. Use environment variables instead. diff --git a/docsrc/zs/aiguard/index.rst b/docsrc/zs/aiguard/index.rst new file mode 100644 index 00000000..0661351f --- /dev/null +++ b/docsrc/zs/aiguard/index.rst @@ -0,0 +1,22 @@ +Zscaler AI Guard +================= +This package covers the Zscaler AI Guard interface. + +AI Guard is split across two authentication paths: + +* **OneAPI** (``ZscalerClient``) -- detection policies, policy match rules, LLM providers, + LLM applications and their credentials. +* **Legacy** (``LegacyAIGuardClient``) -- ``policy_detection`` only. The + ``/v1/detection/*`` endpoints are not available through OneAPI. + +.. toctree:: + :maxdepth: 1 + :glob: + :hidden: + + * + +.. automodule:: zscaler.aiguard + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/llm_application_credentials.rst b/docsrc/zs/aiguard/llm_application_credentials.rst new file mode 100644 index 00000000..487e7927 --- /dev/null +++ b/docsrc/zs/aiguard/llm_application_credentials.rst @@ -0,0 +1,14 @@ +llm_application_credentials +=========================== + +The following methods allow for interaction with the Zscaler AI Guard LLM Application Credentials API endpoints. +Includes listing, creating, updating, deleting, and regenerating LLM application credentials, retrieving a credential by ID or name, and running referential checks. + +Methods are accessible via ``aiguard.llm_application_credentials`` + +.. _aiguard-llm_application_credentials: + +.. automodule:: zscaler.aiguard.llm_application_credentials + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/llm_applications.rst b/docsrc/zs/aiguard/llm_applications.rst new file mode 100644 index 00000000..24d7ddb8 --- /dev/null +++ b/docsrc/zs/aiguard/llm_applications.rst @@ -0,0 +1,14 @@ +llm_applications +================ + +The following methods allow for interaction with the Zscaler AI Guard LLM Applications API endpoints. +Includes listing, creating, updating, and deleting LLM applications, retrieving an application by ID or name, and running referential checks. + +Methods are accessible via ``aiguard.llm_applications`` + +.. _aiguard-llm_applications: + +.. automodule:: zscaler.aiguard.llm_applications + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/llm_provider_credentials.rst b/docsrc/zs/aiguard/llm_provider_credentials.rst new file mode 100644 index 00000000..f0d0a1b8 --- /dev/null +++ b/docsrc/zs/aiguard/llm_provider_credentials.rst @@ -0,0 +1,14 @@ +llm_provider_credentials +======================== + +The following methods allow for interaction with the Zscaler AI Guard LLM Provider Credentials API endpoints. +Includes listing, creating, updating, and deleting LLM provider credentials, retrieving a credential by ID or name, and running referential checks. + +Methods are accessible via ``aiguard.llm_provider_credentials`` + +.. _aiguard-llm_provider_credentials: + +.. automodule:: zscaler.aiguard.llm_provider_credentials + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/llm_providers.rst b/docsrc/zs/aiguard/llm_providers.rst new file mode 100644 index 00000000..4cc058c2 --- /dev/null +++ b/docsrc/zs/aiguard/llm_providers.rst @@ -0,0 +1,14 @@ +llm_providers +============= + +The following methods allow for interaction with the Zscaler AI Guard LLM Providers API endpoints. +Includes listing, creating, updating, and deleting LLM providers, retrieving a provider by ID or name, listing provider types, and running referential checks. + +Methods are accessible via ``aiguard.llm_providers`` + +.. _aiguard-llm_providers: + +.. automodule:: zscaler.aiguard.llm_providers + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/policies.rst b/docsrc/zs/aiguard/policies.rst new file mode 100644 index 00000000..705c40a6 --- /dev/null +++ b/docsrc/zs/aiguard/policies.rst @@ -0,0 +1,14 @@ +policies +======== + +The following methods allow for interaction with the Zscaler AI Guard Detection Policies API endpoints. +Includes listing, creating, updating, and deleting detection policies, and retrieving a policy by ID or name. + +Methods are accessible via ``aiguard.policies`` + +.. _aiguard-policies: + +.. automodule:: zscaler.aiguard.policies + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/policy_detection.rst b/docsrc/zs/aiguard/policy_detection.rst new file mode 100644 index 00000000..dbbbb558 --- /dev/null +++ b/docsrc/zs/aiguard/policy_detection.rst @@ -0,0 +1,40 @@ +policy_detection +================ + +The following methods allow for interaction with the Zscaler AI Guard Policy Detection API endpoints. +Includes executing a detection policy and resolving-and-executing a detection policy against content. + +.. warning:: + + **These endpoints are only available through the legacy AI Guard client.** + + The policy detection endpoints (``/v1/detection/execute-policy`` and + ``/v1/detection/resolve-and-execute-policy``) are **not** exposed through OneAPI. + They must be called with :class:`~zscaler.oneapi_client.LegacyAIGuardClient`, which + authenticates with an AI Guard API key against ``https://api..zseclipse.net``. + + Every other AI Guard resource -- detection policies, policy match rules, LLM + providers, LLM applications and their credentials -- is **OneAPI only** and is + reached with ``ZscalerClient``. + +Methods are accessible via ``aiguard.policy_detection`` on a ``LegacyAIGuardClient``: + +.. code-block:: python + + from zscaler.oneapi_client import LegacyAIGuardClient + + with LegacyAIGuardClient({"api_key": "", "cloud": "us1"}) as client: + result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + content="User prompt or AI response to scan", + direction="IN", + ) + +The API key may also be supplied through the ``AIGUARD_API_KEY`` environment variable +(``AIGUARD_CLOUD`` for the cloud, ``AIGUARD_OVERRIDE_URL`` to override the base URL). + +.. _aiguard-policy_detection: + +.. automodule:: zscaler.aiguard.policy_detection + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/aiguard/policy_match_rules.rst b/docsrc/zs/aiguard/policy_match_rules.rst new file mode 100644 index 00000000..c4dcf260 --- /dev/null +++ b/docsrc/zs/aiguard/policy_match_rules.rst @@ -0,0 +1,14 @@ +policy_match_rules +================== + +The following methods allow for interaction with the Zscaler AI Guard Policy Match Rules API endpoints. +Includes listing, creating, updating, and deleting policy match rules, and retrieving a rule by ID or name. + +Methods are accessible via ``aiguard.policy_match_rules`` + +.. _aiguard-policy_match_rules: + +.. automodule:: zscaler.aiguard.policy_match_rules + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/guides/release_notes.rst b/docsrc/zs/guides/release_notes.rst index cf740bf9..313fe7a1 100644 --- a/docsrc/zs/guides/release_notes.rst +++ b/docsrc/zs/guides/release_notes.rst @@ -6,6 +6,203 @@ Release Notes Zscaler Python SDK Changelog ---------------------------- +1.9.39 (July 27, 2026) +--------------------------- + +Notes +------- + +- Python Versions: **v3.9, v3.10, v3.11, v3.12** + +Enhancements +------------- + +Zscaler Internet Access (ZIA) New Endpoints + +(`#554 `_) - Added the following new ZIA Endpoints: + +- Added ``GET /webDlpGlobalOptions`` Retrieves the DLP Advanced Settings information +- Added ``PUT /webDlpGlobalOptions`` Updates the existing DLP Advanced Settings. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Applications endpoints: + +- Added ``GET /endPointApplications`` Retrieves the list of endpoint applications. +- Added ``GET /endPointApplications/lite`` Retrieves a lightweight list of endpoint applications. +- Added ``GET /endPointApplications/count`` Retrieves the count of all endpoint applications. +- Added ``GET /endPointApplications/cloudApps/count`` Retrieves the count of well-known and discovered endpoint applications. +- Added ``GET /endPointApplications/policies`` Retrieves the list of policy rules associated with the specified endpoint applications. +- Added ``GET /endPointApplications/getCategoriesWithNonEmptyApps`` Retrieves the categories that currently have endpoint applications grouped within them. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Custom Applications endpoints: + +- Added ``GET /endPointApplications/customApps`` Retrieves the list of custom endpoint applications. +- Added ``GET /endPointApplications/customApp/{id}`` Retrieves the custom endpoint application based on the specified ID. +- Added ``POST /endPointApplications/customApp`` Adds a new custom endpoint application. +- Added ``PUT /endPointApplications/customApp/{id}`` Updates the custom endpoint application based on the specified ID. +- Added ``DELETE /endPointApplications/customApp/{id}`` Deletes the custom endpoint application based on the specified ID. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Application Groups endpoints: + +- Added ``GET /endPointApplicationGroups`` Retrieves the list of application tag groups. +- Added ``GET /endPointApplicationGroups/policies`` Retrieves the list of policy rules associated with the specified application tag groups. +- Added ``POST /endPointApplicationGroups`` Adds a new application tag group. +- Added ``PUT /endPointApplicationGroups/{id}`` Updates the application tag group based on the specified ID. +- Added ``PUT /endPointApplicationGroups/{id}/resources`` Updates the applications associated with the specified tag group. +- Added ``DELETE /endPointApplicationGroups/{id}`` Deletes the application tag group based on the specified ID. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Resources endpoints: + +- Added ``GET /dlpEndpointResource/{channel}`` Retrieves the list of DLP resources configured for the specified channel. +- Added ``GET /dlpEndpointResource/{channel}/{id}`` Retrieves the DLP resource based on the specified channel and ID. +- Added ``GET /dlpEndpointResource/{id}/groups`` Retrieves the list of tags to which the specified DLP resource is associated. +- Added ``POST /dlpEndpointResource`` Adds a new DLP endpoint resource. +- Added ``PUT /dlpEndpointResource/{id}`` Updates the DLP endpoint resource based on the specified ID. +- Added ``DELETE /dlpEndpointResource/{id}`` Deletes the DLP endpoint resource based on the specified ID. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Resource Groups endpoints: + +- Added ``GET /endPointDlpResourceGroups/{channel}`` Retrieves the list of DLP resource tags added for the specified channel. +- Added ``GET /endPointDlpResourceGroups/{id}/resources`` Retrieves the DLP resources associated with the specified tag group. +- Added ``PUT /endPointDlpResourceGroups/{id}/resources`` Updates the DLP resources associated with the specified tag group. +- Added ``POST /endPointDlpResourceGroups`` Adds a new DLP resource tag group. +- Added ``PUT /endPointDlpResourceGroups/{id}`` Updates the DLP resource tag group based on the specified ID. +- Added ``DELETE /endPointDlpResourceGroups/{id}`` Deletes the DLP resource tag group based on the specified ID. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Rules endpoints: + +- Added ``GET /endPointDlpRules`` Retrieves a list of Endpoint DLP rules. +- Added ``GET /endPointDlpRules/{id}`` Retrieves the Endpoint DLP rule based on the specified ID. +- Added ``GET /endPointDlpRules/fileTypeCategories`` Retrieves the file type categories supported by Endpoint DLP rules. +- Added ``POST /endPointDlpRules`` Adds a new Endpoint DLP rule. +- Added ``PUT /endPointDlpRules/{id}`` Updates the Endpoint DLP rule based on the specified ID. +- Added ``DELETE /endPointDlpRules/{id}`` Deletes the Endpoint DLP rule based on the specified ID. + +(`#554 `_) - Added the following new ZIA Endpoint DLP Exception (Sub) Rules endpoints: + +- Added ``POST /endPointDlpRules/{id}/subRule`` Adds a new exception (sub) rule to an existing Endpoint DLP rule. +- Added ``PUT /endPointDlpRules/{id}/subRule/{subRuleId}`` Updates the Endpoint DLP exception (sub) rule based on the specified IDs. +- Added ``DELETE /endPointDlpRules/{id}/subRule/{subRuleId}`` Deletes the Endpoint DLP exception (sub) rule based on the specified IDs. + +(`#554 `_) - Added the following new ZIA Outbound Email DLP endpoint: + +- Added ``GET /emailDlpRules/actions`` Retrieves the supported Outbound Email DLP rule actions for the specified email tenants as a CSV file. + +(`#554 `_) - Added the following new ZIA DNS Application Groups endpoints: + +- Added ``GET /dnsApplicationGroups`` Retrieves a list of DNS application groups. +- Added ``GET /dnsApplicationGroups/{id}`` Retrieves the DNS application group based on the specified ID. +- Added ``POST /dnsApplicationGroups`` Adds a new DNS application group. +- Added ``PUT /dnsApplicationGroups/{id}`` Updates the DNS application group based on the specified ID. +- Added ``DELETE /dnsApplicationGroups/{id}`` Deletes the DNS application group based on the specified ID. + +(`#554 `_) - Added the following new ZIA End User Notification endpoints: + +- Added ``GET /eunTemplate/{templateType}/product/{product}`` Retrieves the browser-based/ZCC end user notification template for the specified template type and product. +- Added ``GET /eunTemplate/{templateType}/featureEnablementStatus`` Retrieves the feature enablement status for the specified end user notification template type. +- Added ``GET /userConfirmation/product/{product}`` Retrieves the user confirmation template by policy for the specified product. +- Added ``GET /userConfirmation/globalDefaultTemplates`` Retrieves the global default user confirmation templates. +- Added ``GET /userConfirmation/{templateType}/featureEnablementStatus`` Retrieves the notification enablement feature status for the specified template type. + +Zscaler Private Access (ZPA) New Endpoints + +(`#554 `_) - Added the following new ZPA Policy Group Controller Endpoints: + +- Added ``GET /policyGroupSet/{groupSetId}/group/{groupId}`` Get a specific Policy Group by ID within a Policy Group Set +- Added ``PUT /policyGroupSet/{groupSetId}/group/{groupId}`` Update an existing Policy Group. +- Added ``DELETE /policyGroupSet/{groupSetId}/group/{groupId}`` Delete an existing Policy Group. +- Added ``POST /policyGroupSet/{groupSetId}/group/search`` Get All Policy Groups within a Policy Group Set with advanced search and pagination. +- Added ``POST /policyGroupSet/{groupSetId}/group/{groupId}/reorder/{newOrder}`` Update an existing Policy Group Order. +- Added ``GET /policyGroupSet/{groupSetId}/group/all`` Get All Policy Groups within a Policy Group Set. +- Added ``GET /policyGroupSet/{groupSetId}/group`` Add a new Policy Group to a Policy Group Set. + +(`#554 `_) - Added the following new ZPA Policy Group Rule Controller Endpoints: + +- Added ``GET /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}`` Get a policy rule within a policy group +- Added ``DELETE /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}`` Delete a policy rule within a policy group +- Added ``GET /policyGroupSet/{groupSetId}/group/{groupId}/rule`` Get All Policy Groups Rules within a Policy Group with advanced search and pagination. +- Added ``POST /policyGroupSet/{groupSetId}/group/{groupId}/rule`` Add a new policy rule for a given policy group. +- Added ``PUT /policyGroupSet/{groupSetId}/group/{groupId}/rule/{ruleId}/reorder/{newOrder}`` Update rule order of a rule within policy group + +(`#554 `_) - Added the following new ZPA Policy Group Set Controller Endpoints: + +- Added ``GET /policyGroupSet/{groupSetId}`` Get a specific Policy Group Set by ID. +- Added ``GET /policyGroupSet`` Get all Policy Group Sets for a customer. +- Added ``GET /policyGroupSet/policyType/{policyType}/rules`` Get paginated rules across groups within a Policy Group Set. +- Added ``GET /policyGroupSet/policyType/{policyType}/summary`` Get Policy Group Set Summary fo a customer for policy type. +- Added ``GET /policyGroupSet/policyType/{policyType}`` Get Policy Group Set fo a customer for policy type. +- Added ``GET /policyGroupSet/policyType/{policyType}/summaryStats`` Get summary stats for groups and rules within a Policy Group Set. + +Zscaler AI Guard (AIGuard) New Service and Endpoints +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(`#549 `_) - Added full OneAPI support for the Zscaler AI Guard (`aiguard`) service. AI Guard resources are now accessible via ``client.aiguard``. All AI Guard configuration resources are supported through the OneAPI client (Zidentity); the policy detection endpoints (``/v1/detection/*``) are not exposed through OneAPI and remain available via ``LegacyAIGuardClient``. New resources: Detection Policies, Policy Match Rules, LLM Providers (including provider types), LLM Provider Credentials, LLM Applications, and LLM Application Credentials (including credential regeneration and referential checks). + +(`#554 `_) - The following new AI Guard API endpoints were added: + +- Added ``GET /detections/policies`` to retrieve the list of detection policies. +- Added ``GET /detections/policies/{policyId}`` to retrieve a detection policy by ID. +- Added ``GET /detections/policies/name/{name}`` to retrieve a detection policy by name. +- Added ``POST /detections/policies`` to create a new detection policy. +- Added ``PUT /detections/policies/{policyId}`` to update a detection policy. +- Added ``DELETE /detections/policies/{policyId}`` to delete a detection policy. +- Added ``GET /detections/policy-match-rules`` to retrieve the list of policy match rules. +- Added ``GET /detections/policy-match-rules/{ruleId}`` to retrieve a policy match rule by ID. +- Added ``GET /detections/policy-match-rules/name/{name}`` to retrieve a policy match rule by name. +- Added ``POST /detections/policy-match-rules`` to create a new policy match rule. +- Added ``PUT /detections/policy-match-rules/{ruleId}`` to update a policy match rule. +- Added ``DELETE /detections/policy-match-rules/{ruleId}`` to delete a policy match rule. +- Added ``GET /llm-providers`` to retrieve the list of LLM providers. +- Added ``GET /llm-providers/{providerId}`` to retrieve an LLM provider by ID. +- Added ``GET /llm-providers/name/{name}`` to retrieve an LLM provider by name. +- Added ``GET /llm-providers/{providerId}/referential-check`` to retrieve resources referencing an LLM provider. +- Added ``GET /llm-provider-types`` to retrieve the list of supported LLM provider types. +- Added ``GET /llm-provider-types/{type}`` to retrieve a specific LLM provider type. +- Added ``POST /llm-providers`` to create a new LLM provider. +- Added ``PUT /llm-providers/{providerId}`` to update an LLM provider. +- Added ``DELETE /llm-providers/{providerId}`` to delete an LLM provider. +- Added ``GET /llm-provider-credentials`` to retrieve the list of LLM provider credentials. +- Added ``GET /llm-provider-credentials/{credentialId}`` to retrieve an LLM provider credential by ID. +- Added ``GET /llm-provider-credentials/name/{name}`` to retrieve an LLM provider credential by name. +- Added ``GET /llm-provider-credentials/{credentialId}/referential-check`` to retrieve resources referencing an LLM provider credential. +- Added ``POST /llm-provider-credentials`` to create a new LLM provider credential. +- Added ``PUT /llm-provider-credentials/{credentialId}`` to update an LLM provider credential. +- Added ``DELETE /llm-provider-credentials/{credentialId}`` to delete an LLM provider credential. +- Added ``GET /llm-applications`` to retrieve the list of LLM applications. +- Added ``GET /llm-applications/{applicationId}`` to retrieve an LLM application by ID. +- Added ``GET /llm-applications/name/{name}`` to retrieve an LLM application by name. +- Added ``GET /llm-applications/{applicationId}/referential-check`` to retrieve resources referencing an LLM application. +- Added ``POST /llm-applications`` to create a new LLM application. +- Added ``PUT /llm-applications/{applicationId}`` to update an LLM application. +- Added ``DELETE /llm-applications/{applicationId}`` to delete an LLM application. +- Added ``GET /llm-application-credentials`` to retrieve the list of LLM application credentials. +- Added ``GET /llm-application-credentials/{credentialId}`` to retrieve an LLM application credential by ID. +- Added ``GET /llm-application-credentials/name/{name}`` to retrieve an LLM application credential by name. +- Added ``GET /llm-application-credentials/{credentialId}/referential-check`` to retrieve resources referencing an LLM application credential. +- Added ``POST /llm-application-credentials`` to create a new LLM application credential. +- Added ``POST /llm-application-credentials/{credentialId}/regenerate`` to regenerate an LLM application credential. +- Added ``PUT /llm-application-credentials/{credentialId}`` to update an LLM application credential. +- Added ``DELETE /llm-application-credentials/{credentialId}`` to delete an LLM application credential. + +Zscaler AI Guard (AIGuard) Policy Detection via Legacy Client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(`#549 `_) - The AI Guard policy detection endpoints (``POST /v1/detection/execute-policy`` and ``POST /v1/detection/resolve-and-execute-policy``) are not exposed through OneAPI and are served by the AI Guard legacy client, ``LegacyAIGuardClient``. They are reached via ``client.aiguard.policy_detection`` and authenticate with an AI Guard API key (``api_key`` / ``AIGUARD_API_KEY``) against ``https://api..zseclipse.net``. All other AI Guard resources remain OneAPI only via ``ZscalerClient``. + +.. code-block:: python + + from zscaler.oneapi_client import LegacyAIGuardClient + + with LegacyAIGuardClient({"api_key": "", "cloud": "us1"}) as client: + result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + content="User prompt or AI response to scan", + direction="IN", + ) + +Deprecations +------------- + +(`#549 `_) - AI Guard configuration resources are supported via the OneAPI client. The AI Guard legacy client is retained as ``LegacyAIGuardClient`` solely for policy detection (``client.aiguard.policy_detection``), because ``/v1/detection/execute-policy`` and ``/v1/detection/resolve-and-execute-policy`` are not exposed through OneAPI. The ``client.zguard`` property remains as a deprecated alias for ``client.aiguard``. + 1.9.38 (July 14, 2026) --------------------------- diff --git a/docsrc/zs/zaiguard/index.rst b/docsrc/zs/zaiguard/index.rst deleted file mode 100644 index d85a3a46..00000000 --- a/docsrc/zs/zaiguard/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -Zscaler AI Guard -================= -This package covers the Zscaler AI Guard interface. - -.. toctree:: - :glob: - :hidden: - - * - -.. automodule:: zscaler.zaiguard - :members: - :undoc-members: - :show-inheritance: diff --git a/docsrc/zs/zaiguard/policy_detection.rst b/docsrc/zs/zaiguard/policy_detection.rst deleted file mode 100644 index 31b1efc1..00000000 --- a/docsrc/zs/zaiguard/policy_detection.rst +++ /dev/null @@ -1,12 +0,0 @@ -Policy Detection ------------------ - -The following methods allow for interaction with the Zscaler AI Guard Policy Detection API endpoints. - -Methods are accessible via ``zaiguard.policy_detection`` -.. _zaiguard-policy_detection: - -.. automodule:: zscaler.zaiguard.policy_detection - :members: - :undoc-members: - :show-inheritance: diff --git a/docsrc/zs/zpa/policy_group.rst b/docsrc/zs/zpa/policy_group.rst new file mode 100644 index 00000000..5b8a7906 --- /dev/null +++ b/docsrc/zs/zpa/policy_group.rst @@ -0,0 +1,14 @@ +policy_group +------------ + +The following methods allow for interaction with the ZPA +Policy Group API endpoints. + +Methods are accessible via ``zpa.policy_group`` + +.. _zpa-policy_group: + +.. automodule:: zscaler.zpa.policy_group + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/zpa/policy_group_rule.rst b/docsrc/zs/zpa/policy_group_rule.rst new file mode 100644 index 00000000..a9caaa55 --- /dev/null +++ b/docsrc/zs/zpa/policy_group_rule.rst @@ -0,0 +1,14 @@ +policy_group_rule +----------------- + +The following methods allow for interaction with the ZPA +Policy Group Rule API endpoints. + +Methods are accessible via ``zpa.policy_group_rule`` + +.. _zpa-policy_group_rule: + +.. automodule:: zscaler.zpa.policy_group_rule + :members: + :undoc-members: + :show-inheritance: diff --git a/docsrc/zs/zpa/policy_group_set.rst b/docsrc/zs/zpa/policy_group_set.rst new file mode 100644 index 00000000..a2fb49da --- /dev/null +++ b/docsrc/zs/zpa/policy_group_set.rst @@ -0,0 +1,14 @@ +policy_group_set +---------------- + +The following methods allow for interaction with the ZPA +Policy Group Set API endpoints. + +Methods are accessible via ``zpa.policy_group_set`` + +.. _zpa-policy_group_set: + +.. automodule:: zscaler.zpa.policy_group_set + :members: + :undoc-members: + :show-inheritance: diff --git a/poetry.lock b/poetry.lock index d659b200..7ac0cc53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -148,14 +148,14 @@ uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db"}, - {file = "certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432"}, + {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, + {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, ] [[package]] @@ -404,103 +404,103 @@ files = [ [[package]] name = "coverage" -version = "7.15.1" +version = "7.15.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:05d87c2a43373ad6b976d0a99ad58c48633633bcdeb896dc645a006472cc4a71"}, - {file = "coverage-7.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2afce82f2cf8f4c9002746a42755e1dc61baff33d9f7ab5569b4c9101f8f4d1d"}, - {file = "coverage-7.15.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a545ef5384d787d0fcac6c349afc2c5f99dcc39e13ed3c191b2c06305f64c04"}, - {file = "coverage-7.15.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:afaa144b8f5b3bc69fe0ce50d401c46b01ab264782553bfd05a3f98804524ecb"}, - {file = "coverage-7.15.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b6099490e5f88569c46b18605f556c3b30acc9a0a219cf7ef8fab8f7161ec4"}, - {file = "coverage-7.15.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb5f6c2cf1ffd0bf2bd925c7cdcae9b4f208e9696d453ed51eb1f5fa0cc5b45b"}, - {file = "coverage-7.15.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81572011fc1fc271317da35da593944daef7bfd507085e35751abbe702b74f69"}, - {file = "coverage-7.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f765d13c08497687d0780cca66115c6aa4ba6703ad43b61e94fab9db689e3a3"}, - {file = "coverage-7.15.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:771caba880fee96493d18dfc465c318e08ab74e3bc2a3e4089e52514be6a6e54"}, - {file = "coverage-7.15.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:433a73200848e80f27712fc113b6ff5311f29b479a7d3bd4b1106138a77f9674"}, - {file = "coverage-7.15.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5d6fa45079db9fbeba0a69e3d91189f05301d6ac918162a53179d32fc9ed4910"}, - {file = "coverage-7.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:00b6703c6640075cdce5124e9335dfdf9167272475301828acfdd09c0e5ee731"}, - {file = "coverage-7.15.1-cp310-cp310-win32.whl", hash = "sha256:3ad9a0eac4728327fd870d52f74d2e631d176c5f178eaea2d9983ab5b9755a55"}, - {file = "coverage-7.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:eb5fa75dc3d30e3a1b75da97973479b20ffa9b0641ff56d6e94b5f3e210daa54"}, - {file = "coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0"}, - {file = "coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9"}, - {file = "coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea"}, - {file = "coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2"}, - {file = "coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29"}, - {file = "coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89"}, - {file = "coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d"}, - {file = "coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c"}, - {file = "coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab"}, - {file = "coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358"}, - {file = "coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404"}, - {file = "coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34"}, - {file = "coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7"}, - {file = "coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b"}, - {file = "coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c"}, - {file = "coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80"}, - {file = "coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189"}, - {file = "coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615"}, - {file = "coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3"}, - {file = "coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304"}, - {file = "coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c"}, - {file = "coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b"}, - {file = "coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61"}, - {file = "coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e"}, - {file = "coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5"}, - {file = "coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d"}, - {file = "coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2"}, - {file = "coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76"}, - {file = "coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374"}, - {file = "coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a"}, - {file = "coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a"}, - {file = "coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b"}, - {file = "coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e"}, - {file = "coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a"}, - {file = "coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c"}, - {file = "coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90"}, - {file = "coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f"}, - {file = "coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8"}, - {file = "coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a"}, - {file = "coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31"}, - {file = "coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad"}, - {file = "coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a"}, - {file = "coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e"}, - {file = "coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7"}, - {file = "coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49"}, - {file = "coverage-7.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8"}, - {file = "coverage-7.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4"}, - {file = "coverage-7.15.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81"}, - {file = "coverage-7.15.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca"}, - {file = "coverage-7.15.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74"}, - {file = "coverage-7.15.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047"}, - {file = "coverage-7.15.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9"}, - {file = "coverage-7.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652"}, - {file = "coverage-7.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4"}, - {file = "coverage-7.15.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c"}, - {file = "coverage-7.15.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633"}, - {file = "coverage-7.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41"}, - {file = "coverage-7.15.1-cp314-cp314-win32.whl", hash = "sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b"}, - {file = "coverage-7.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a"}, - {file = "coverage-7.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74"}, - {file = "coverage-7.15.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b"}, - {file = "coverage-7.15.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49"}, - {file = "coverage-7.15.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864"}, - {file = "coverage-7.15.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c"}, - {file = "coverage-7.15.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07"}, - {file = "coverage-7.15.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e"}, - {file = "coverage-7.15.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e"}, - {file = "coverage-7.15.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf"}, - {file = "coverage-7.15.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82"}, - {file = "coverage-7.15.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82"}, - {file = "coverage-7.15.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7"}, - {file = "coverage-7.15.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594"}, - {file = "coverage-7.15.1-cp314-cp314t-win32.whl", hash = "sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070"}, - {file = "coverage-7.15.1-cp314-cp314t-win_amd64.whl", hash = "sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f"}, - {file = "coverage-7.15.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b"}, - {file = "coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c"}, - {file = "coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67"}, + {file = "coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d"}, + {file = "coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846"}, + {file = "coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf"}, + {file = "coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376"}, + {file = "coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd"}, + {file = "coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb"}, + {file = "coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1"}, + {file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9"}, + {file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a"}, + {file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287"}, + {file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89"}, + {file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88"}, + {file = "coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443"}, + {file = "coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629"}, + {file = "coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036"}, + {file = "coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660"}, + {file = "coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589"}, + {file = "coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee"}, + {file = "coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0"}, + {file = "coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487"}, + {file = "coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f"}, + {file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1"}, + {file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5"}, + {file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0"}, + {file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad"}, + {file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db"}, + {file = "coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9"}, + {file = "coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688"}, + {file = "coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934"}, + {file = "coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9"}, + {file = "coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73"}, + {file = "coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d"}, + {file = "coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b"}, + {file = "coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296"}, + {file = "coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6"}, + {file = "coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098"}, + {file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a"}, + {file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b"}, + {file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2"}, + {file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440"}, + {file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e"}, + {file = "coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd"}, + {file = "coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40"}, + {file = "coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3"}, + {file = "coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8"}, + {file = "coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1"}, + {file = "coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578"}, + {file = "coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1"}, + {file = "coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6"}, + {file = "coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7"}, + {file = "coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d"}, + {file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026"}, + {file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa"}, + {file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d"}, + {file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b"}, + {file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188"}, + {file = "coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050"}, + {file = "coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c"}, + {file = "coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b"}, + {file = "coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a"}, + {file = "coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2"}, + {file = "coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138"}, + {file = "coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f"}, + {file = "coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984"}, + {file = "coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7"}, + {file = "coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3"}, + {file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee"}, + {file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1"}, + {file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a"}, + {file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145"}, + {file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446"}, + {file = "coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243"}, + {file = "coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc"}, + {file = "coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635"}, + {file = "coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be"}, + {file = "coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072"}, + {file = "coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328"}, + {file = "coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a"}, + {file = "coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0"}, + {file = "coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5"}, + {file = "coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743"}, + {file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c"}, + {file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071"}, + {file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a"}, + {file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d"}, + {file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c"}, + {file = "coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688"}, + {file = "coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199"}, + {file = "coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658"}, + {file = "coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c"}, + {file = "coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d"}, ] [package.dependencies] @@ -842,14 +842,14 @@ re2 = ["google-re2 (>=1.1)"] [[package]] name = "platformdirs" -version = "4.10.0" +version = "4.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a"}, - {file = "platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7"}, + {file = "platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74"}, + {file = "platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0"}, ] [[package]] @@ -1172,14 +1172,14 @@ dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "t [[package]] name = "pytz" -version = "2026.2" +version = "2026.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126"}, - {file = "pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a"}, + {file = "pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815"}, + {file = "pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d"}, ] [[package]] @@ -1309,30 +1309,30 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "ruff" -version = "0.15.21" +version = "0.16.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d"}, - {file = "ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd"}, - {file = "ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8"}, - {file = "ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075"}, - {file = "ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341"}, - {file = "ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d"}, - {file = "ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233"}, - {file = "ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f"}, - {file = "ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd"}, - {file = "ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3"}, - {file = "ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8"}, - {file = "ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500"}, + {file = "ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e"}, + {file = "ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522"}, + {file = "ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed"}, + {file = "ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb"}, + {file = "ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472"}, + {file = "ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d"}, + {file = "ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982"}, ] [[package]] @@ -1835,121 +1835,121 @@ anyio = ">=3.0.0" [[package]] name = "websockets" -version = "16.1" +version = "16.1.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "websockets-16.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de72a9c611178b15557d98eabd3101c9663c4d68938510478a6d162f99afd213"}, - {file = "websockets-16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:37b0e4d726ffea3776670092d3d13e1cb605076f036a695fd1259de0d9b9fe02"}, - {file = "websockets-16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:00d50c0a27098fcb7ab47b3d99a1b1159b534dbcd959fbf05113ebc37e5f927b"}, - {file = "websockets-16.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1acb698bff1da1782b31aebd8d7a24d7d05453964abcd7d03dbf6e25893908e8"}, - {file = "websockets-16.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc2c453f3b5f99c56b16e233aad5299860558487d26adb2ed27a00c14ca24b8c"}, - {file = "websockets-16.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1a9f08a0728b0835f1c6abe1d9b746ab3de49b7336a0e1919cf96be1e76273eb"}, - {file = "websockets-16.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a089979d6173b27af18026c8d8b0077f83669a9169174482c4651e9f5739a5b6"}, - {file = "websockets-16.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a3c18dba232ec2b92a68579c9fed8ff5a18f853d1e09fc0b6ca3159e94f689fe"}, - {file = "websockets-16.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c1eb7df4170d5068892a8834fb5c07b9552353deb0dbeb0bff3820481ae4792"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c522bd48e625b6d557aa228967258d6d3da031c4cc21d3352fb302479aa9ba0a"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d106396927a7f00b0f3a69215c3357f87bf0bca6844247121f7e8291e826a3b1"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d71bed12909b8039955536e192867d02d76cd3797cedfd0facf822e7668636c3"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:9c1cf6f9a936b030b5bed0e800c5ee32069338129084546baf5ff5014dc62fa9"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3fd3e6a7af2c8fcdcf4ffbeaf7f54a567b91a83267204187797f31faaa2a4efa"}, - {file = "websockets-16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dddd27175bf640acae5561fa79b77e8ec71fc445816200523e5c19b6a556fb72"}, - {file = "websockets-16.1-cp310-cp310-win32.whl", hash = "sha256:cce36c80b3f2fede7942f1756d3d885fa6fa086766c8c1bcf00695ab80f0d51a"}, - {file = "websockets-16.1-cp310-cp310-win_amd64.whl", hash = "sha256:115fc4695b94bb855995b23fb1abcb66099a5995575d3d5bc5605a616c58d0eb"}, - {file = "websockets-16.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9b1d7a63cba8e6b9b77e499a81eab29d31100298d090ad4507d1048c0b9cae0"}, - {file = "websockets-16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bedbc5efeb96621aa2921d2d92608246691399418cac22acba427eb11877ea1f"}, - {file = "websockets-16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd847ab82133015afe65d778e7966ab42dba16bd7ad2e5b8a7918db6539f3f94"}, - {file = "websockets-16.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2fb33ccb16ee40a95cc676d7b0ff451a9a2632f11a0dbc2e666326892b2e1de"}, - {file = "websockets-16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f15b6d9ea9c2eaf6ccab964a082b09bfa6634a495bb0c2e9e7ee6943f58976"}, - {file = "websockets-16.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:638cf57c48b4ad8ac1ff1e453f4f97db2426b690ddc111e6da96b27b4a340bc3"}, - {file = "websockets-16.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c1c85f61bc9d5eac57ce705d848dc2d2ce3680638300bf4e1da7d749e2cf4ce"}, - {file = "websockets-16.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:eeab6d27f51c7e579023c971f5e6dff200deadf01faf6831beaecd32052dfaef"}, - {file = "websockets-16.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2ed64e5a97b0b97a0b66e18bfe281317a75fbbd5afe692f939ea8d14a4292f2c"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b3b021d0ed4bc16eea9775f62c9fa71acdacba0fc790b38581754dedf29ca60"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6eb604a4167f0a0d53c2243dfc667a29f0b43c3436057184e070bb82a1000fa2"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9a3f125e44c3e34d61d111652e608e0f5b85ce08c225c8d56ad0eb822fa40030"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8fdf0b00d0d1f30d1f06a92cab46fe542eec3eb302a7aee7163f142d0780f216"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:67b56828712f5fa7852de4c0265c28827311a657a4d275b7312ed0d1a918bee4"}, - {file = "websockets-16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39c7e7730be33b8f0cd6f0aa8e8c82f9cdd1813f159765e073b2ece65f4824b5"}, - {file = "websockets-16.1-cp311-cp311-win32.whl", hash = "sha256:c54fe94fb2f11e11b48920c5f971e298cec73ac35db56efe57a49db63dfc95d4"}, - {file = "websockets-16.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9f4fb9ae8b802e55609685db98382d48fd3feb1397804e1e774968dea0f28c7"}, - {file = "websockets-16.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b6aa3f7ad345cf3862c21f4fbf2ef5e14d911348476c2845e137c091fe3a3f0b"}, - {file = "websockets-16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b43fcfb521ac2f34ba80b7b8ea16303e4ad82dd8af667bf40839ad3a5d37b164"}, - {file = "websockets-16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bd3e12cd9afbe2baedae0b1eeade8ba64329b60fe2f9abdc966bd10fd2c2ef5"}, - {file = "websockets-16.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f41979c8623df9bd30d949d82010a8fda5c56ff12cd8508a5b7272b6d4b53a"}, - {file = "websockets-16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a24d1f35aef07d794a16c853c688e74956c50239bec37b4f2de080056046419b"}, - {file = "websockets-16.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c64c024ddf7a35331b21fcddb562a039c275d2c82e8c2d12939e7da23997270"}, - {file = "websockets-16.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e99757f5baafe20fc598e202ea6f5b0b265186ad38d0a17bd8beca16296955"}, - {file = "websockets-16.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:353f3bc6e058ac1ccab4b3588e8598837a8c04cfc8351233e6d523be675d844c"}, - {file = "websockets-16.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0352f5b38b40e857b6428d468fa21dbb4dd4a567d933c26d9831b4efe1b92f43"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70bd789afab579602968c39f21cb925466505f3edff22f0ae852bca54978a4f9"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d0fb4b46f121eccd539353baebd1083a8767a9a351109453d1d1caecd1ba40c2"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c14b6634af01541e4efe2954fd8f263386f7aa6d37c01e55dd8109fd17661452"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a58532c49a851bcb481e58c1be23b315c17fe2fbbed509d75aeea12f543d2c15"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4e969170c3b08e1d8dabd990fef1fa702c4233aeaabec33f871806e444f6a0e4"}, - {file = "websockets-16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff9b000064b88787ba9f7a3cb2af2b68a658ca5aad76458a46469e7124b678a0"}, - {file = "websockets-16.1-cp312-cp312-win32.whl", hash = "sha256:b9f5d83f80f4d7c4bba6d97f3755ac05850c784dce0fd2ab371c4e41172f53ff"}, - {file = "websockets-16.1-cp312-cp312-win_amd64.whl", hash = "sha256:6852c9f653966c16109d3b6f31181fd734f7914927e3f0fa1117af7a18c9aa21"}, - {file = "websockets-16.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b0232ed141cec3df2af5a3959a071c51f40036336b0d37e17faf9ef52fc73e47"}, - {file = "websockets-16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a71b73d143991714144e159f767b698f03c4a70b8a65ae1733b650cff488045b"}, - {file = "websockets-16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:187323204c3b2fc465e8fc2609e60437c521790cb9c1acb49c4c452a33e57f37"}, - {file = "websockets-16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dba74233c8c3ce368850818c98354dad2570f57231b3fd3bd00d7aa57628881"}, - {file = "websockets-16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63339bc8c63c86a463177775cb7c677691f5bcfac7b3b2f01b286d42acd41600"}, - {file = "websockets-16.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23e545ea8ae4263e37cdfd4e22a217f519e48e432728bc461185bbf585f38a83"}, - {file = "websockets-16.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2237081454846fb40403a80ba86d82e2038b9c45865ab96af0abe7d002a91045"}, - {file = "websockets-16.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5f5218de1ed047385ca53744caba9435d65f75d008364970a3fae95a05812cf9"}, - {file = "websockets-16.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75c98e3920039d0edff03b74478ada504b7ce3a1bc406db2cabfca84320f7baf"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1facd189d8190af30487a55b4c3688484dd50801628a3b5b2ccd26db08e67057"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cc0c6a6eef613c7da32d4fb068f82ef834b58134f6a16b54e6c1e5bf9529ab3d"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ad9411eded8988b879be6038206698bf7106c85a78f642c004485bcb95be17eb"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cd68f0914f3b64694895bc5e9b14e8b447e41d7bf5ffaf989bb8dcb5e2dfdce7"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fef2debfe7f7ebdda12176f26166f95b7af17af05ba06150fcf889032e0213e9"}, - {file = "websockets-16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3cd6c9b798218798f4bb7b2e71c38f0e744bb94ca537b13376f88019d46384d"}, - {file = "websockets-16.1-cp313-cp313-win32.whl", hash = "sha256:84c170c6869633536921e4474b1cce7254c0c9b0053ef5725f966cee47e718e4"}, - {file = "websockets-16.1-cp313-cp313-win_amd64.whl", hash = "sha256:bef52d327d70fa75dad93ee61ea2cb1d1489aca9f35c188833563f5a3b4df0a5"}, - {file = "websockets-16.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f881fca0a45dd6789939bd6637cd98169b92f1c3fdc78262f2cb9ec2cb1f324e"}, - {file = "websockets-16.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c379d5b207d3a7f0ba4c2e4602a895b0bcc63fb5f5371a4ae7fbddb03b672b"}, - {file = "websockets-16.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:98ab58a4faa72b46da0127ccc1931dcbfc0985b0778892300a092185910c4cbe"}, - {file = "websockets-16.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e9c4e369fc181b2d41a99e01477215cecdc8546a39f7d41a59cc0a7065a0b09"}, - {file = "websockets-16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0704df094b2d5fa7f6f410925a594c2a5c9a09167731a76292e5410934208209"}, - {file = "websockets-16.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b22b1f4950f6ab7126623329c3b47b3b90a14c05db517f2db2a026ad6c928352"}, - {file = "websockets-16.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1ae4a686a662964a6671069f84f7f908cc3475e782227726b0c622c715962105"}, - {file = "websockets-16.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:856bdd638f8277f86465057bfdd4da097c73058fb0f9d2bd5baea29e2bf2d367"}, - {file = "websockets-16.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9003a1fde1c21a322a3ca3fa0c4bda8c639da81dbc925162766086643b05ba87"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39e947b1f5fdab045174306e3916785bf3ed537648acc1549827c08c33b10953"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5dd0e666b5931c0509cf65714686a1c5126771e663a79ac5d40da4f58b1f9502"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a0285df7925657ad65a65fb8dc330808bce082827538fd50ef45fa12d1fc5bca"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:82d1c2cab3c133e9d059b3a5420bed9376bd30e21c185c63dda4ddadf6ddda47"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c39907f1eaf11f6277def65aa02d68f30576b693d0c1ca332aafa3caa723ac6d"}, - {file = "websockets-16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:45c5ea55446171949eb99fd34b771ceddd511ca21958d40d0197ced33159e5ee"}, - {file = "websockets-16.1-cp314-cp314-win32.whl", hash = "sha256:b8ef8b1c8d6bd029a475ac432e730fba2dfd456715d26c473e2a82291024b99c"}, - {file = "websockets-16.1-cp314-cp314-win_amd64.whl", hash = "sha256:7358ff21632b5d062707f73e859c824f1c3807e73d8ca25e71caca7c4cdcf145"}, - {file = "websockets-16.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d0f38f4c3e9b359e257c339c2cc1967ccaeedb102e57c1c986bdce4bf4f32268"}, - {file = "websockets-16.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3c3d2cbd1602593bad49bd86fa3fbb25407d87a3b4bf8857c0ac5ac4914e1901"}, - {file = "websockets-16.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:36069b74671e7e667f48a7484249f84c45a825a134c8b1bdc01875d0daa10d79"}, - {file = "websockets-16.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:587f83c2ce8a5d628e166384d77fa7f0ac69b9007d515ab442123e6615aa8da3"}, - {file = "websockets-16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6db7972d52bc1b66cefe2246902e256cbaebc9ba8a45eac09343d7eb6671b2"}, - {file = "websockets-16.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e7d6014888a0632e1ed7a4095248bb3095232999447f2d83bfb1900987dd9ed9"}, - {file = "websockets-16.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cb074d150e4ad2a77aa8a332c2be85f3f64f2681519d2570c1225c12c9821ff"}, - {file = "websockets-16.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d19c9067e1fe9490f974bffbc0e443b80a7674c5efb4980c429cc00771f07c5a"}, - {file = "websockets-16.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d440ff0c6c7469ad59c0a412c383c235935b43635e89425e3f6a0c36de90c31b"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8613129a2533f08de24505e69a3e403cedaadae49abdb043c4d170ca71b7e4bd"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a5bf9c23f197b4ec88290fd5463f33db67362a1bb10f85fc2e8e7627f0ddab97"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:520b0fd0395f075febb283c76755af724ab9fd19dffa4f3bfd18cb4e622790a3"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7143aa09a67e1c013be44e81a88dfe90fc6244198ab86c7edd064152cf619805"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:7acb811fad08e611755800d1560e395c67e11a6bd563598ea6abb319afb86938"}, - {file = "websockets-16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c5cf88e3faa2f7931bc6baeee7599c97656a3f6ac7f831f4fccba233e141783a"}, - {file = "websockets-16.1-cp314-cp314t-win32.whl", hash = "sha256:589f8842521c8307684ce0b40ce4ad70c5e0aa46484c6f1225a94ef4b8970341"}, - {file = "websockets-16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c0e0857c30bbbc2bb5c30687508f0b7ec19aa026cd9f2ff8424d0fee42dcc07"}, - {file = "websockets-16.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7289d899c79e763e6221c8dcb8959361cb43274418538d7c7ad16a43b01d12f9"}, - {file = "websockets-16.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e22e9e3719f5131bd62da4db63c8da63eb8c91cc99e16c1cbd122f130e1ae07a"}, - {file = "websockets-16.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:83bdabafef431247e6b11a9aab8a0893fd8e82e1ed95b32e0373625b03ffce4a"}, - {file = "websockets-16.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b8d13ceabc5c60995f201b5211d76876e17e68706ebf5d3bc666b32eefff1a6"}, - {file = "websockets-16.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81495f9c0085361c582efbc3207fb877174cfe03370f17d9cd70624404aa526f"}, - {file = "websockets-16.1-py3-none-any.whl", hash = "sha256:c5149dfe490ec7e5ee5dbf624c642fb725f93a5575c7f00ab594ca9eddb8dd81"}, - {file = "websockets-16.1.tar.gz", hash = "sha256:299468cbe42e2b9981134c7c51d99387d8a7bf562b00183b3eec53f882846dad"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9246a0d063cfcbcc85f2359dd6876d681213f4790832272aa16641b4ed5d64d4"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1214e673c404684b9bf7154f5cf43b45025b1a6160fac3a9e438e9c1a97e22cb"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90001d893bc368e302ef168d82130b4e4fdd27b85fa094682df9b667c2d48838"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:130937b167a52af203c8d58e78d67705874e82759862e3b9671a452fec4abc87"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c9f23004a3d40e89c01a7955d186a6cc83418d93b749701944ce2de3e95a1f3"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f55f0b01956a094c8587146d9558c91937e78789c333860ffaf35931a6e5dbc4"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6aaface73b9c71974c6497366d8b9628357f6c9749e09c4ea3610176c63f2ae3"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc0fad4933f427acd5b1cec210f3ea6dce7089e1724e4b9ec6ef47c6c04d1b3b"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2769a0344a09e9ccf5b3cce538bc75a51b53eff3275d3896310c8552049195d"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f70541f3104339f59f830522d94ebadb1bf47426287381623443d8bb1cdbf33d"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:dc385593a42e31cd6fb60c19f0ecb015b386603818fc2c6c274fb42bd2bb4165"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:387e8e4aa5df2f90b198fa3cad3478822a89cf905b6a6d6c97dc3664689640cc"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fd46fff7eb62c24804d234f0051c7a8ea81285ad63e0337d3dcf33ca82aee58a"}, + {file = "websockets-16.1.1-cp310-cp310-win32.whl", hash = "sha256:7883388947767080f094950b342b30d35a2a06b849cd967c422fa0db72b40ea9"}, + {file = "websockets-16.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d57685547e0060cc6fd90ee6a28405d6bd395e525545f13c8d7cd99c78afd79f"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d0fcf657e9f13ff4b177960ab2200237b12994232dfb6df16f1cfe1d4339f93c"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b852788aa51764e2d8e4cf5493d559326bcae5e38d16ba25ffa322b034df272a"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1427fb4cf0d72f66333e2cacc3ff5f575bf2d7008166ce991a4a470b21d51a22"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:da4ca1a9d72f9030b3146b8d7022719a9f3d478f61efe6f7dd51d243f61c51b2"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86d7f0f8bdb25d2c632b72527325e4776430fd5bc61b9118de4e2b8ddb5f5b01"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dfcad78ea1492ee3a9ec765cb7f51bbc17d477107aaf6b22abf7b2558d1c5a0"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb9a0a6dc3d1b3986cb88091b6899f0396651e0f74e2c9766ab8d6ffc3842e29"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29dfa8114c4a620c69591c5973860f768eac29d3fd6904f37f34266cb219c512"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff9417c0ada4d0f7d212f928303e5579bdf3ace4c802fa4afabb30995da58c3"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fe0b50da2d84535fb4f7b4bfa951280f97ce3d558a0443b541166d609e67b57"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34420aaa64440ebd51ac72ca8a45ef4626429438c9b02e633ae412ed43f925d3"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a6a61aff018180c9c50b7b0da33bfd29d378af3497429c95006c589a23a11648"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:04fd29a0e2fe9414a95b00e92c67ae51bf900c50c0f8a4b2dafdad621f49ea1d"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5c31aa7e39ee3e8a358573257f1c0bb5c52430d1b637030dd9c8cc2c282926be"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d14bfb217eb4701e850f1525c9d29d79c44794cdf1c299ead25f39f8c78dea81"}, + {file = "websockets-16.1.1-cp311-cp311-win32.whl", hash = "sha256:2e28e602bb13da44fbe518c1781a88e3b9d4c3d48d02c9bad83e546164336f57"}, + {file = "websockets-16.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7421fad442de870a8cbf2287d1cad7e706ece0dbfeba5e911df132cbdc1cb56a"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc97814dfb786a83b6e2dc2e79351e1b83e6d715647d6887fcabd83026417a00"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e047dc87ef7ca50f4d309bf775ad4a71711c58556d75d7bd0604b2317f43e94b"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01fbdcbac298efe19360b94bc0039c8f746f0220ba570f327577bfee81059175"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f62863e8a00a6d33c3d6566ec0b89f23787b747ffe0c3bc71ec0e76b82c94b1"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8087e82f842609734c9b5a1330464f8e94e346ba0e18c832c08bafa4b0d63c15"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2bb5d041a8307d2e18782e7ce777f6fdb1e8c2f5d09291484b18c294b789d9aa"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1db4de4a0e95673f7545d393c49eeb0c2f18ac1ef93073218c79d5cdb2ee75ab"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f17dbe07eb3ea7f99e4df9b7e0efefe80fbf30d37a8cc4d561a0aed310bc8847"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b57693728576d84ede0a77987ab16881b783d2cd9f1dc180a8fbbc3f79c4428"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a636ff1e7a5c4edf71ef0e79adae7f25dba93b4fcbe3dc958733477ffeb0eaf"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d6bec75c290fe484a8ba4cacdf838501e17c06ecfbbf31eede81a9e431bd7751"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:54509b8e92fee4453e152b7558ddef37ce9705a044922f2095a6105e3f80c96f"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f0aa4aad3b1b69ad3fd85a0fd0952ec64331c762bd77ec51cc814170873890b2"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:42290eb6db4ccaca7012656738214f8514082fb6fa40cdeb61bb9a471b52e383"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:53260c8930da5771cec89439bff99c20c8cb03ddb9588b980697355a83cd4bd3"}, + {file = "websockets-16.1.1-cp312-cp312-win32.whl", hash = "sha256:1d27fa8462ad6a1cb36206a3d0640b2333340def181fae11ed7f9adeaa5c0747"}, + {file = "websockets-16.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b436f6ec4fc3a6b4237c84d3f83170ed2b40bb584222f0ac47a0c8a5921980c7"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87"}, + {file = "websockets-16.1.1-cp313-cp313-win32.whl", hash = "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea"}, + {file = "websockets-16.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9"}, + {file = "websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328"}, + {file = "websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737"}, + {file = "websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7"}, + {file = "websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:820fb8450edddae3812fd58cbc08e2bf22812cb248ecb5f06dbb82119a56e869"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:125f22dbefaf1554fea66fc83851490edb284ce4f501d37ffed2752f418332d9"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30bbe120437b5648a77d3519b7024ea09530e0b5b18d3698c5a0ae536fe0cc2e"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6b9dadbef0cccd9f4c4ee96b08898afa73e26803bbe0f6aeb5bb12b0074206d"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56cd5fc4f10a9ea8aa0804bddb7b42506cf9e136046f3b4c27de8fec9e2ecba5"}, + {file = "websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3"}, + {file = "websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 35556ca4..e1d27beb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zscaler-sdk-python" -version = "1.9.38" +version = "1.9.39" description = "Official Python SDK for the Zscaler Products" authors = ["Zscaler, Inc. "] license = "MIT" diff --git a/requirements.txt b/requirements.txt index 71b32c6e..2144a18b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ aenum==3.1.17 ; python_version >= "3.10" and python_version < "4.0" arrow==1.4.0 ; python_version >= "3.10" and python_version < "4.0" -certifi==2026.6.17 ; python_version >= "3.10" and python_version < "4.0" +certifi==2026.7.22 ; python_version >= "3.10" and python_version < "4.0" cffi==2.1.0 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" charset-normalizer==3.4.9 ; python_version >= "3.10" and python_version < "4.0" cryptography==49.0.0 ; python_version >= "3.10" and python_version < "4.0" @@ -12,7 +12,7 @@ pydash==8.0.6 ; python_version >= "3.10" and python_version < "4.0" pyjwt==2.13.0 ; python_version >= "3.10" and python_version < "4.0" python-box==7.4.1 ; python_version >= "3.10" and python_version < "4.0" python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" -pytz==2026.2 ; python_version >= "3.10" and python_version < "4.0" +pytz==2026.3.post1 ; python_version >= "3.10" and python_version < "4.0" pyyaml==6.0.3 ; python_version >= "3.10" and python_version < "4.0" requests==2.34.2 ; python_version >= "3.10" and python_version < "4.0" six==1.17.0 ; python_version >= "3.10" and python_version < "4.0" diff --git a/tests/conftest.py b/tests/conftest.py index 087f1a98..0a15b2ec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -171,6 +171,14 @@ def before_record_request(request): b'"zccFailCloseSettingsExitUninstallPassword":"REDACTED"', body, ) + # AI Guard: the provider/application credential secret lives at + # apiCredentials.key. "key" is too generic to redact globally, so it is + # scoped to the apiCredentials object. + body = re.sub( + rb'("apiCredentials"\s*:\s*\{[^}]*?"key"\s*:\s*)"[^"]*"', + rb'\1"REDACTED"', + body, + ) else: body = re.sub(r'client_id=[^&"]*', "client_id=REDACTED", body) body = re.sub(r'client_secret=[^&"]*', "client_secret=REDACTED", body) @@ -196,6 +204,12 @@ def before_record_request(request): '"zccFailCloseSettingsExitUninstallPassword":"REDACTED"', body, ) + # AI Guard: see the note in the bytes branch above. + body = re.sub( + r'("apiCredentials"\s*:\s*\{[^}]*?"key"\s*:\s*)"[^"]*"', + r'\1"REDACTED"', + body, + ) request.body = body return request @@ -325,6 +339,13 @@ def before_record_response(response): # Redact any email-like values (anything with @ in a quoted string) body = re.sub(rb'"[^"]*@[^"]*"', b'"REDACTED"', body) + # AI Guard: the llm-application-credentials response returns a live generated + # secret in a top-level "key". "key" is too generic to redact globally (e.g. + # provider-type payloads use "key":"publicApi"), so it is scoped to bodies that + # carry providerCredentialsId -- i.e. credential responses. + if b"providerCredentialsId" in body: + body = re.sub(rb'"key"\s*:\s*"[^"]*"', b'"key":"REDACTED"', body) + for service, pattern in URL_PATTERNS_BYTES.items(): test_url = pattern_to_test_url_bytes.get(service, TEST_URLS_BYTES["base"]) body = re.sub(pattern, test_url, body) @@ -406,6 +427,10 @@ def before_record_response(response): # Redact any email-like values (anything with @ in a quoted string) body = re.sub(r'"[^"]*@[^"]*"', '"REDACTED"', body) + # AI Guard: see the note in the bytes branch above. + if "providerCredentialsId" in body: + body = re.sub(r'"key"\s*:\s*"[^"]*"', '"key":"REDACTED"', body) + pattern_to_test_url = { "zia": TEST_URLS["zia"], "zia_alt": TEST_URLS["zia"], diff --git a/tests/integration/zaiguard/cassettes/.gitkeep b/tests/integration/aiguard/__init__.py similarity index 100% rename from tests/integration/zaiguard/cassettes/.gitkeep rename to tests/integration/aiguard/__init__.py diff --git a/tests/integration/aiguard/cassettes/.gitkeep b/tests/integration/aiguard/cassettes/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/aiguard/cassettes/TestLlmApplicationCredentials.yaml b/tests/integration/aiguard/cassettes/TestLlmApplicationCredentials.yaml new file mode 100644 index 00000000..bdd34fa0 --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestLlmApplicationCredentials.yaml @@ -0,0 +1,722 @@ +interactions: +- request: + body: grant_type=client_credentials&client_id=REDACTED&client_secret=REDACTED&audience=REDACTED + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '171' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://identity.test.zscaler.com/oauth2/v1/token + response: + body: + string: '{"access_token":"REDACTED_TOKEN","token_type":"Bearer","expires_in":83999}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-security-policy: + - 'default-src ''none'' ;connect-src https://securitygeekio.zslogin.net/;font-src + https://fonts.gstatic.com ;img-src data: https://www.zscaler.com https://info.zscaler.com + ;script-src ''unsafe-inline'' ''unsafe-eval'' https://www.zscaler.com ;style-src + ''unsafe-inline'' https://fonts.googleapis.com ;frame-src https://securitygeekio.zslogin.net/ + https://securitygeekio-admin.zslogin.net/;frame-ancestors ;upgrade-insecure-requests' + content-type: + - application/json;charset=UTF-8 + date: + - Wed, 22 Jul 2026 20:03:32 GMT + expires: + - '0' + pragma: + - no-cache + server: + - ingress-gateway + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + - max-age=31536000; includeSubDomains; + - max-age=31536000; includeSubDomains; + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + - nosniff + - nosniff + x-frame-options: + - DENY + - SAMEORIGIN + - SAMEORIGIN + x-ratelimit-limit: + - 240, 500;w=1, 240;w=1, 735;w=1 + x-ratelimit-remaining: + - '239' + x-ratelimit-reset: + - '1' + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/name/Default%20Anthropic%20Provider + response: + body: + string: '{"id":6099,"name":"Default Anthropic Provider","type":"anthropic","createTimeMillis":1775790150684,"updateTimeMillis":1775790150684,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:32 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '23' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 0e079b7c-f0eb-9ef5-a9dc-e8548dfac767 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/name/App01 + response: + body: + string: '{"id":647,"name":"App01","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1769657108803,"updateTimeMillis":1774752266885}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:32 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '15' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - befc6fbd-388f-96f1-9d3c-65531e80c808 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials + response: + body: + string: '{"items":[{"id":744,"providerId":6099,"name":"Anthropic_API01","createTimeMillis":1784745192169,"updateTimeMillis":1784745192169,"expireTimeMillis":1785481200000},{"id":747,"providerId":6099,"name":"Anthropic_API02","createTimeMillis":1784745463518,"updateTimeMillis":1784745463518},{"id":739,"providerId":6099,"name":"Anthropic_API_Key","createTimeMillis":1784654432715,"updateTimeMillis":1784743942727,"expireTimeMillis":1785481200000}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '21' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - fc4f7372-33e7-9f54-abd1-5c9d63f95d2a + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"applicationId": 647, "providerId": 6099, "providerCredentialsId": 744, + "name": "tests-lac-vcr0001", "mode": "PROXY"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '118' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials + response: + body: + string: '{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001","mode":"PROXY","key":"REDACTED","createTimeMillis":1784750613180,"updateTimeMillis":1784750613180}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '33' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 61d0024d-ce3d-992f-89fe-d707d2d3968a + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/2154 + response: + body: + string: '{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001","mode":"PROXY","createTimeMillis":1784750613180,"updateTimeMillis":1784750613180}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '17' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 372a3940-9204-977b-8c5d-76c0fa7238e0 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/name/tests-lac-vcr0001 + response: + body: + string: '{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001","mode":"PROXY","createTimeMillis":1784750613180,"updateTimeMillis":1784750613180}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '19' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - fe8b85b1-7f6a-917d-b956-53d36d19ed21 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"applicationId": 647, "providerId": 6099, "providerCredentialsId": 744, + "name": "tests-lac-vcr0001Updated", "mode": "PROXY"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '125' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: PUT + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/2154 + response: + body: + string: '{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001Updated","mode":"PROXY","createTimeMillis":1784750613180,"updateTimeMillis":1784750613870}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '28' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 3015fc61-48b4-902d-8a33-87d1dc545df5 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/2154 + response: + body: + string: '{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001Updated","mode":"PROXY","createTimeMillis":1784750613180,"updateTimeMillis":1784750613870}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:33 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '17' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 1d09e8af-23fd-9570-b2af-537bb45ba07d + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials + response: + body: + string: '{"items":[{"id":2131,"applicationId":647,"providerId":6099,"providerCredentialsId":739,"name":"IDB01","mode":"PROXY","createTimeMillis":1784654807901,"updateTimeMillis":1784743502447,"expireTimeMillis":1785481200000},{"id":2154,"applicationId":647,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0001Updated","mode":"PROXY","createTimeMillis":1784750613180,"updateTimeMillis":1784750613870}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '18' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - d2fc7faf-fe30-934d-a95d-c070125b2d82 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/2154 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '28' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 16ec8937-346f-9db5-a04c-404042932a8a + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/cassettes/TestLlmApplications.yaml b/tests/integration/aiguard/cassettes/TestLlmApplications.yaml new file mode 100644 index 00000000..63dfa10b --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestLlmApplications.yaml @@ -0,0 +1,462 @@ +interactions: +- request: + body: '{"name": "tests-la-vcr0001", "ownerEmail": "tests-la-vcr0002@acme.com", + "applicationSettings": {"includeEventContents": true, "encryptEventContents": + false}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '157' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications + response: + body: + string: '{"id":1582,"name":"tests-la-vcr0001","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614352}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '24' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - e62ca1c0-4488-9c7c-afc1-41f8b5bc3111 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/1582 + response: + body: + string: '{"id":1582,"name":"tests-la-vcr0001","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614352}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '14' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 49244e3b-5337-9c14-bf9a-284ce11ca303 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/name/tests-la-vcr0001 + response: + body: + string: '{"id":1582,"name":"tests-la-vcr0001","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614352}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '5' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - e9c80309-4389-9d6f-8a03-9e5adac80622 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"name": "tests-la-vcr0001Updated", "ownerEmail": "tests-la-vcr0002@acme.com", + "applicationSettings": {"includeEventContents": true, "encryptEventContents": + false}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '164' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: PUT + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/1582 + response: + body: + string: '{"id":1582,"name":"tests-la-vcr0001Updated","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614680}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '23' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 14539d6f-ba4d-9aee-8f44-9cd4d91c9587 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/1582 + response: + body: + string: '{"id":1582,"name":"tests-la-vcr0001Updated","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614680}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '16' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - ea436cd1-06c2-9cb3-89ff-77b0ef9fa763 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '993' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications + response: + body: + string: '{"items":[{"id":647,"name":"App01","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1769657108803,"updateTimeMillis":1774752266885},{"id":1575,"name":"App10","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784749349821,"updateTimeMillis":1784749349821},{"id":1582,"name":"tests-la-vcr0001Updated","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750614352,"updateTimeMillis":1784750614680}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:34 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '21' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 589e2b22-3e83-9fdc-91fd-8095a432cdfa + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '992' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/1582 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '12' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 2d552275-525b-94f9-a780-0741f122e26b + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '991' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/cassettes/TestLlmProviderCredentials.yaml b/tests/integration/aiguard/cassettes/TestLlmProviderCredentials.yaml new file mode 100644 index 00000000..d891cc47 --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestLlmProviderCredentials.yaml @@ -0,0 +1,526 @@ +interactions: +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/name/Default%20Anthropic%20Provider + response: + body: + string: '{"id":6099,"name":"Default Anthropic Provider","type":"anthropic","createTimeMillis":1775790150684,"updateTimeMillis":1775790150684,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - a616d807-3103-90f1-a747-c42a918b3375 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"providerId": 6099, "name": "tests-lpc-vcr0001", "apiCredentials": {"type": + "API_KEY", "key": "REDACTED"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '116' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials + response: + body: + string: '{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001","createTimeMillis":1784750615254,"updateTimeMillis":1784750615254}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '50' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 230b0a70-0577-95cb-87b8-ead379e3aad7 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials/753 + response: + body: + string: '{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001","createTimeMillis":1784750615254,"updateTimeMillis":1784750615254}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '20' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - edc3299e-7e89-9c1e-96ed-a6bb9a63a91c + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials/name/tests-lpc-vcr0001 + response: + body: + string: '{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001","createTimeMillis":1784750615254,"updateTimeMillis":1784750615254}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '17' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 8b379cdf-1cf4-9235-be76-097330467580 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"providerId": 6099, "name": "tests-lpc-vcr0001Updated", "apiCredentials": + {"type": "API_KEY", "key": "REDACTED"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '123' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: PUT + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials/753 + response: + body: + string: '{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001Updated","createTimeMillis":1784750615254,"updateTimeMillis":1784750615591}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '34' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - d3d97229-61ba-9df2-80b0-f18ab6d427a3 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials/753 + response: + body: + string: '{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001Updated","createTimeMillis":1784750615254,"updateTimeMillis":1784750615591}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - f6f645f4-2963-9501-8d89-9d25ecf78e9b + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials + response: + body: + string: '{"items":[{"id":744,"providerId":6099,"name":"Anthropic_API01","createTimeMillis":1784745192169,"updateTimeMillis":1784745192169,"expireTimeMillis":1785481200000},{"id":747,"providerId":6099,"name":"Anthropic_API02","createTimeMillis":1784745463518,"updateTimeMillis":1784745463518},{"id":739,"providerId":6099,"name":"Anthropic_API_Key","createTimeMillis":1784654432715,"updateTimeMillis":1784743942727,"expireTimeMillis":1785481200000},{"id":753,"providerId":6099,"name":"tests-lpc-vcr0001Updated","createTimeMillis":1784750615254,"updateTimeMillis":1784750615591}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 11ee10f7-99d5-95a0-b8d0-bdd7186fa1c6 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '993' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials/753 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:35 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '26' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - dde2d1d1-2c9c-9a61-a4ce-1f89e9ba7464 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '992' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/cassettes/TestLlmProviders.yaml b/tests/integration/aiguard/cassettes/TestLlmProviders.yaml new file mode 100644 index 00000000..f3ce4bab --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestLlmProviders.yaml @@ -0,0 +1,513 @@ +interactions: +- request: + body: '{"name": "tests-lp-vcr0001", "type": "xai", "public": true}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '59' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers + response: + body: + string: '{"id":29109,"name":"tests-lp-vcr0001","type":"xai","createTimeMillis":1784750616073,"updateTimeMillis":1784750616073,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '19' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - a31db233-fcc1-9960-bfd9-8d195258af51 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/29109 + response: + body: + string: '{"id":29109,"name":"tests-lp-vcr0001","type":"xai","createTimeMillis":1784750616073,"updateTimeMillis":1784750616073,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '5' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 1465dbb9-3a65-9ace-9a11-5b6bddf017ed + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/name/tests-lp-vcr0001 + response: + body: + string: '{"id":29109,"name":"tests-lp-vcr0001","type":"xai","createTimeMillis":1784750616073,"updateTimeMillis":1784750616073,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '17' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 75e85abe-8172-9cf7-9e2a-343c4cca15fa + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-types + response: + body: + string: '{"items":[{"type":"openai","name":"OpenAI","description":"Metadata + for OpenAI APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"anthropic","name":"Anthropic","description":"Metadata + for Anthropic APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"azure","name":"Azure","description":"Metadata + for Azure APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"bedrock-agent","name":"Bedrock-Agent","description":"Metadata + for Bedrock Invoke Agent","servers":{"public":{"accepted":true,"key":"publicApi","allowedValues":["bedrock-agent-runtime.us-east-1.amazonaws.com","bedrock-agent-runtime.us-east-2.amazonaws.com","bedrock-agent-runtime.us-west-2.amazonaws.com"]},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"bedrock-anthropic","name":"Bedrock-Anthropic","description":"Metadata + for Bedrock APIs for Anthropic Models","servers":{"public":{"accepted":true,"key":"publicApi","allowedValues":["bedrock-runtime.ap-northeast-1.amazonaws.com","bedrock-runtime.ap-northeast-2.amazonaws.com","bedrock-runtime.ap-south-1.amazonaws.com","bedrock-runtime.ap-southeast-1.amazonaws.com","bedrock-runtime.ap-southeast-2.amazonaws.com","bedrock-runtime.ca-central-1.amazonaws.com","bedrock-runtime.eu-central-1.amazonaws.com","bedrock-runtime.eu-west-1.amazonaws.com","bedrock-runtime.eu-west-2.amazonaws.com","bedrock-runtime.eu-west-3.amazonaws.com","bedrock-runtime.sa-east-1.amazonaws.com","bedrock-runtime.us-east-1.amazonaws.com","bedrock-runtime.us-east-2.amazonaws.com","bedrock-runtime.us-gov-east-1.amazonaws.com","bedrock-runtime.us-west-2.amazonaws.com","bedrock-runtime.ap-northeast-3.amazonaws.com"]},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"bedrock-unified","name":"Bedrock-Unified","description":"Metadata + for Bedrock Converse APIs","servers":{"public":{"accepted":true,"key":"publicApi","allowedValues":["bedrock-runtime.us-east-1.amazonaws.com","bedrock-runtime.us-east-2.amazonaws.com","bedrock-runtime.us-west-2.amazonaws.com"]},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"bolt","name":"Bolt","description":"Bolt + API signature for prompt and response extraction.","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"builder","name":"Builder","description":"Metadata + for Builder.io","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"deepai","name":"DeepAI","description":"Metadata + for DeepAI","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"elevenlabs","name":"Elevenlabs","description":"Metadata + for Elevenlabs.io","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"gamma","name":"Gamma","description":"Metadata + for Gamma.app","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"github-copilot","name":"GitHub + Copilot","description":"Metadata for GitHub Copilot APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"glean","name":"Glean","description":"Metadata + for Glean","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"google-gemini","name":"Google + Gemini","description":"Metadata for Google Gemini APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"google-gemini-code","name":"Google + Gemini Code","description":"Metadata for Google Gemini Code Assist APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"google-gemini-enterprise","name":"Google + Gemini Enterprise","description":"Metadata for Google Gemini Enterprise APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"google-gemini-workspace","name":"Google + Gemini Workspace","description":"Metadata for Google Gemini Workspace APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"google-vertex-studio","name":"Google + Vertex Studio","description":"Google Cloud Vertex AI Studio","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"lovable","name":"Lovable","description":"Lovable + Dev API signature for prompt and response extraction.","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"maxai","name":"MaxAI","description":"Metadata + for MaxAI","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"microsoft-365-copilot","name":"Microsoft + 365 Copilot","description":"Metadata for Microsoft 365 Copilot APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"microsoft-copilot","name":"Microsoft + Copilot","description":"Metadata for Microsoft Copilot APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"mistral","name":"Mistral + AI","description":"API signature for the Mistral AI Le Chat web application.","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"napkin","name":"Napkin","description":"Napkin + API signature for prompt and response extraction.","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"notebooklm","name":"NotebookLM","description":"Metadata + for NotebookLM APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"opencode","name":"OpenCode","description":"Metadata + for OpenCode APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"perplexity","name":"PerplexityAI","description":"Metadata + for Perplexity APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"quillbot","name":"Quillbot","description":"Quillbot + AI powered writing assitant","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"replit","name":"Replit","description":"Replit + AI agent platform API","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"vertex","name":"Vertex","description":"Metadata + for Vertex APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":true,"key":"publicApi","allowedValues":null}}},{"type":"windsurf","name":"Windsurf","description":"Metadata + for Windsurf","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}},{"type":"xai","name":"xAI","description":"Metadata + for Grok APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '52' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - fbab5f2c-b097-9b79-a083-e259e49f1f38 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-types/xai + response: + body: + string: '{"type":"xai","name":"xAI","description":"Metadata for Grok APIs","servers":{"public":{"accepted":false,"key":null,"allowedValues":null},"private":{"accepted":false,"key":null,"allowedValues":null}}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '3' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 75d4046d-2ebb-910a-aa65-58777ab8ee85 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers + response: + body: + string: '{"items":[{"id":1480,"name":"Anthropic","type":"xai","createTimeMillis":1775163852257,"updateTimeMillis":1775163852257,"public":true},{"id":6104,"name":"Default + Bolt Provider","type":"bolt","createTimeMillis":1775790150705,"updateTimeMillis":1775790150705,"public":true},{"id":29103,"name":"BDAnthropic","type":"xai","createTimeMillis":1784744445921,"updateTimeMillis":1784744445921,"public":true},{"id":29109,"name":"tests-lp-vcr0001","type":"xai","createTimeMillis":1784750616073,"updateTimeMillis":1784750616073,"public":true},{"id":6099,"name":"Default + Anthropic Provider","type":"anthropic","createTimeMillis":1775790150684,"updateTimeMillis":1775790150684,"public":true},{"id":6100,"name":"Default + Azure Provider","type":"azure","createTimeMillis":1775790150688,"updateTimeMillis":1775790150688,"public":true},{"id":6101,"name":"Default + Bedrock-Agent Provider","type":"bedrock-agent","servers":{"publicApi":["bedrock-agent-runtime.us-east-1.amazonaws.com"]},"createTimeMillis":1775790150692,"updateTimeMillis":1775790150692,"public":true},{"id":6102,"name":"Default + Bedrock-Anthropic Provider","type":"bedrock-anthropic","servers":{"publicApi":["bedrock-runtime.us-east-1.amazonaws.com"]},"createTimeMillis":1775790150697,"updateTimeMillis":1775790150697,"public":true},{"id":6103,"name":"Default + Bedrock-Unified Provider","type":"bedrock-unified","servers":{"publicApi":["bedrock-runtime.us-east-1.amazonaws.com"]},"createTimeMillis":1775790150701,"updateTimeMillis":1775790150701,"public":true},{"id":6105,"name":"Default + Microsoft Copilot Provider","type":"microsoft-copilot","createTimeMillis":1775790150709,"updateTimeMillis":1775790150709,"public":true},{"id":6106,"name":"Default + DeepAI Provider","type":"deepai","createTimeMillis":1775790150714,"updateTimeMillis":1775790150714,"public":true},{"id":6107,"name":"Default + Google Gemini Code Provider","type":"google-gemini-code","createTimeMillis":1775790150718,"updateTimeMillis":1775790150718,"public":true},{"id":6116,"name":"Default + Napkin Provider","type":"napkin","createTimeMillis":1775790150755,"updateTimeMillis":1775790150755,"public":true},{"id":6117,"name":"Default + OpenAI Provider","type":"openai","servers":{"publicApi":["api.openai.com"]},"createTimeMillis":1775790150759,"updateTimeMillis":1775790150759,"public":true},{"id":6118,"name":"Default + OpenCode Provider","type":"opencode","createTimeMillis":1775790150763,"updateTimeMillis":1775790150763,"public":true},{"id":6119,"name":"Default + PerplexityAI Provider","type":"perplexity","createTimeMillis":1775790150767,"updateTimeMillis":1775790150767,"public":true},{"id":6120,"name":"Default + Google Vertex Studio Provider","type":"google-vertex-studio","createTimeMillis":1775790150771,"updateTimeMillis":1775790150771,"public":true},{"id":6121,"name":"Default + Vertex Provider","type":"vertex","servers":{"publicApi":["aiplatform.googleapis.com"]},"createTimeMillis":1775790150775,"updateTimeMillis":1775790150775,"public":true},{"id":6108,"name":"Default + Google Gemini Enterprise Provider","type":"google-gemini-enterprise","createTimeMillis":1775790150722,"updateTimeMillis":1775790150722,"public":true},{"id":6109,"name":"Default + Google Gemini Provider","type":"google-gemini","servers":{"publicApi":["generativelanguage.googleapis.com"]},"createTimeMillis":1775790150726,"updateTimeMillis":1775790150726,"public":true},{"id":6110,"name":"Default + Google Gemini Workspace Provider","type":"google-gemini-workspace","createTimeMillis":1775790150730,"updateTimeMillis":1775790150730,"public":true},{"id":6111,"name":"Default + Glean Provider","type":"glean","createTimeMillis":1775790150734,"updateTimeMillis":1775790150734,"public":true},{"id":6114,"name":"Default + Microsoft 365 Copilot Provider","type":"microsoft-365-copilot","createTimeMillis":1775790150747,"updateTimeMillis":1775790150747,"public":true},{"id":6115,"name":"Default + MaxAI Provider","type":"maxai","createTimeMillis":1775790150751,"updateTimeMillis":1775790150751,"public":true},{"id":6112,"name":"Default + xAI Provider","type":"xai","createTimeMillis":1775790150738,"updateTimeMillis":1775790150738,"public":true},{"id":6113,"name":"Default + Lovable Provider","type":"lovable","createTimeMillis":1775790150742,"updateTimeMillis":1775790150742,"public":true}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '16' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - b7509575-060a-9a30-8300-7ab39ea3b0d7 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/29109 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '18' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - c8bba3cc-9d7f-92b0-9a33-a2a5490d3e1d + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '993' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/cassettes/TestPolicies.yaml b/tests/integration/aiguard/cassettes/TestPolicies.yaml new file mode 100644 index 00000000..a92f45dd --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestPolicies.yaml @@ -0,0 +1,501 @@ +interactions: +- request: + body: '{"name": "tests-pol-vcr0001", "inputDetectorPolicies": [{"detector": "toxicity", + "enabled": true, "severity": "HIGH", "configuration": {"action": "BLOCK", "threshold": + 0.87}}, {"detector": "prompt_injection", "enabled": true, "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.75}}], "outputDetectorPolicies": + [{"detector": "toxicity", "enabled": true, "severity": "CRITICAL", "configuration": + {"action": "BLOCK", "threshold": 0.87}}, {"detector": "pii", "enabled": false, + "severity": "CRITICAL", "configuration": {"entities": [{"action": "DETECT", + "entityType": "CRYPTO"}, {"action": "DETECT", "entityType": "EMAIL_ADDRESS"}, + {"action": "DETECT", "entityType": "IP_ADDRESS"}, {"action": "DETECT", "entityType": + "LOCATION"}, {"action": "DETECT", "entityType": "US_ITIN"}, {"action": "DETECT", + "entityType": "DATE_TIME"}, {"action": "DETECT", "entityType": "URL"}, {"action": + "DETECT", "entityType": "MEDICAL_LICENSE"}, {"action": "DETECT", "entityType": + "STREET_ADDRESS"}, {"action": "DETECT", "entityType": "DATE_OF_BIRTH"}, {"action": + "DETECT", "entityType": "US_DEA_NUMBER"}, {"action": "BLOCK", "entityType": + "CREDIT_CARD"}, {"action": "BLOCK", "entityType": "US_SSN"}, {"action": "DETECT", + "entityType": "PERSON"}, {"action": "BLOCK", "entityType": "US_PASSPORT"}, {"action": + "BLOCK", "entityType": "US_BANK_NUMBER"}, {"action": "BLOCK", "entityType": + "US_DRIVER_LICENSE"}, {"action": "BLOCK", "entityType": "IBAN_CODE"}, {"action": + "BLOCK", "entityType": "SWIFT_CODE"}, {"action": "ALLOW", "entityType": "PHONE_NUMBER"}], + "threshold": 0.5, "anonymization": "NONE", "defaultAction": "BLOCK", "replaceWithMaskedContent": + false}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1659' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies + response: + body: + string: '{"id":2933,"name":"tests-pol-vcr0001","version":1,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750616893}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '14' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - f3da8c14-968d-92aa-a6a8-c4db20aca147 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '992' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/2933 + response: + body: + string: '{"id":2933,"name":"tests-pol-vcr0001","version":1,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750616893}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:36 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - f3bb0121-5ff7-9962-aacb-3f27f8cbccf9 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '991' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/name/tests-pol-vcr0001 + response: + body: + string: '{"id":2933,"name":"tests-pol-vcr0001","version":1,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750616893}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '53' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 882e61ea-0d0e-9fc4-b156-bff8a48fa5b4 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"name": "tests-pol-vcr0001Updated", "inputDetectorPolicies": [{"detector": + "toxicity", "enabled": true, "severity": "HIGH", "configuration": {"action": + "BLOCK", "threshold": 0.87}}, {"detector": "prompt_injection", "enabled": true, + "severity": "CRITICAL", "configuration": {"action": "BLOCK", "threshold": 0.75}}], + "outputDetectorPolicies": [{"detector": "toxicity", "enabled": true, "severity": + "CRITICAL", "configuration": {"action": "BLOCK", "threshold": 0.87}}, {"detector": + "pii", "enabled": false, "severity": "CRITICAL", "configuration": {"entities": + [{"action": "DETECT", "entityType": "CRYPTO"}, {"action": "DETECT", "entityType": + "EMAIL_ADDRESS"}, {"action": "DETECT", "entityType": "IP_ADDRESS"}, {"action": + "DETECT", "entityType": "LOCATION"}, {"action": "DETECT", "entityType": "US_ITIN"}, + {"action": "DETECT", "entityType": "DATE_TIME"}, {"action": "DETECT", "entityType": + "URL"}, {"action": "DETECT", "entityType": "MEDICAL_LICENSE"}, {"action": "DETECT", + "entityType": "STREET_ADDRESS"}, {"action": "DETECT", "entityType": "DATE_OF_BIRTH"}, + {"action": "DETECT", "entityType": "US_DEA_NUMBER"}, {"action": "BLOCK", "entityType": + "CREDIT_CARD"}, {"action": "BLOCK", "entityType": "US_SSN"}, {"action": "DETECT", + "entityType": "PERSON"}, {"action": "BLOCK", "entityType": "US_PASSPORT"}, {"action": + "BLOCK", "entityType": "US_BANK_NUMBER"}, {"action": "BLOCK", "entityType": + "US_DRIVER_LICENSE"}, {"action": "BLOCK", "entityType": "IBAN_CODE"}, {"action": + "BLOCK", "entityType": "SWIFT_CODE"}, {"action": "ALLOW", "entityType": "PHONE_NUMBER"}], + "threshold": 0.5, "anonymization": "NONE", "defaultAction": "BLOCK", "replaceWithMaskedContent": + false}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1666' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: PUT + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/2933 + response: + body: + string: '{"id":2933,"name":"tests-pol-vcr0001Updated","version":2,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750617280}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '14' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 10ffa1c7-d190-95e5-9fc1-3b153d4c9e89 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/2933 + response: + body: + string: '{"id":2933,"name":"tests-pol-vcr0001Updated","version":2,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750617280}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '19' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - c9d37ad5-1407-97e3-a984-ab9a59863c39 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies + response: + body: + string: '{"items":[{"id":2933,"name":"tests-pol-vcr0001Updated","version":2,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}}],"createTimeMillis":1784750616893,"updateTimeMillis":1784750617280},{"id":1152,"name":"PolicyRule01","version":17,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}},{"detector":"pii","enabled":true,"severity":"CRITICAL","configuration":{"entities":[{"action":"ALLOW","entityType":"CRYPTO"},{"action":"ALLOW","entityType":"EMAIL_ADDRESS"},{"action":"ALLOW","entityType":"IP_ADDRESS"},{"action":"ALLOW","entityType":"LOCATION"},{"action":"ALLOW","entityType":"PERSON"},{"action":"ALLOW","entityType":"PHONE_NUMBER"},{"action":"ALLOW","entityType":"US_DRIVER_LICENSE"},{"action":"ALLOW","entityType":"US_ITIN"},{"action":"ALLOW","entityType":"US_PASSPORT"},{"action":"ALLOW","entityType":"DATE_TIME"},{"action":"ALLOW","entityType":"URL"},{"action":"ALLOW","entityType":"MEDICAL_LICENSE"},{"action":"ALLOW","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"STREET_ADDRESS"},{"action":"ALLOW","entityType":"DATE_OF_BIRTH"},{"action":"ALLOW","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"}],"threshold":0.85,"anonymization":"REPLACE","defaultAction":"BLOCK","replaceWithMaskedContent":true}},{"detector":"secrets","enabled":true,"severity":"CRITICAL","configuration":{"secretTypes":[{"action":"DETECT","secretType":"GOOGLE_API_KEY"},{"action":"DETECT","secretType":"GOOGLE_OAUTH_ACCESS_TOKEN"},{"action":"DETECT","secretType":"PAYPAL_BRAINTREE_ACCESS_TOKEN"},{"action":"DETECT","secretType":"PICATIC_API_KEY"},{"action":"DETECT","secretType":"PAN_BASED_CARDS"},{"action":"DETECT","secretType":"SENDGRID_API_KEY"},{"action":"DETECT","secretType":"SLACK_ACCESS_TOKEN"},{"action":"DETECT","secretType":"SLACK_WEBHOOK"},{"action":"DETECT","secretType":"SQUARE_ACCESS_TOKEN"},{"action":"DETECT","secretType":"SQUARE_OAUTH_SECRET"},{"action":"DETECT","secretType":"STRIPE_API_KEY"},{"action":"DETECT","secretType":"TWO_FACTOR_TOKEN"},{"action":"BLOCK","secretType":"GITHUB_TOKEN"},{"action":"BLOCK","secretType":"JWT_TOKEN"},{"action":"BLOCK","secretType":"PRIVATE_KEY"},{"action":"BLOCK","secretType":"US_GOVERNMENT_IDS"},{"action":"BLOCK","secretType":"AMAZON_MWS_AUTH_TOKEN"}],"anonymization":"NONE","defaultAction":"DETECT"}},{"detector":"personal_data","enabled":true,"severity":"HIGH","configuration":{"action":"DETECT","threshold":0.85}},{"detector":"gibberish","enabled":true,"severity":"MEDIUM","configuration":{"action":"DETECT","threshold":0.9}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"pii","enabled":false,"severity":"CRITICAL","configuration":{"entities":[{"action":"DETECT","entityType":"CRYPTO"},{"action":"DETECT","entityType":"EMAIL_ADDRESS"},{"action":"DETECT","entityType":"IP_ADDRESS"},{"action":"DETECT","entityType":"LOCATION"},{"action":"DETECT","entityType":"US_ITIN"},{"action":"DETECT","entityType":"DATE_TIME"},{"action":"DETECT","entityType":"URL"},{"action":"DETECT","entityType":"MEDICAL_LICENSE"},{"action":"DETECT","entityType":"STREET_ADDRESS"},{"action":"DETECT","entityType":"DATE_OF_BIRTH"},{"action":"DETECT","entityType":"US_DEA_NUMBER"},{"action":"BLOCK","entityType":"CREDIT_CARD"},{"action":"BLOCK","entityType":"US_SSN"},{"action":"DETECT","entityType":"PERSON"},{"action":"BLOCK","entityType":"US_PASSPORT"},{"action":"BLOCK","entityType":"US_BANK_NUMBER"},{"action":"BLOCK","entityType":"US_DRIVER_LICENSE"},{"action":"BLOCK","entityType":"IBAN_CODE"},{"action":"BLOCK","entityType":"SWIFT_CODE"},{"action":"ALLOW","entityType":"PHONE_NUMBER"}],"threshold":0.5,"anonymization":"NONE","defaultAction":"BLOCK","replaceWithMaskedContent":false}},{"detector":"secrets","enabled":true,"severity":"CRITICAL","configuration":{"secretTypes":[{"action":"DETECT","secretType":"PAYPAL_BRAINTREE_ACCESS_TOKEN"},{"action":"DETECT","secretType":"PICATIC_API_KEY"},{"action":"DETECT","secretType":"PAN_BASED_CARDS"},{"action":"DETECT","secretType":"SENDGRID_API_KEY"},{"action":"DETECT","secretType":"SLACK_ACCESS_TOKEN"},{"action":"DETECT","secretType":"SLACK_WEBHOOK"},{"action":"DETECT","secretType":"SQUARE_ACCESS_TOKEN"},{"action":"DETECT","secretType":"SQUARE_OAUTH_SECRET"},{"action":"DETECT","secretType":"STRIPE_API_KEY"},{"action":"DETECT","secretType":"TWO_FACTOR_TOKEN"},{"action":"DETECT","secretType":"US_GOVERNMENT_IDS"},{"action":"BLOCK","secretType":"GITHUB_TOKEN"},{"action":"BLOCK","secretType":"AMAZON_MWS_AUTH_TOKEN"},{"action":"BLOCK","secretType":"PRIVATE_KEY"},{"action":"BLOCK","secretType":"GOOGLE_API_KEY"},{"action":"BLOCK","secretType":"JWT_TOKEN"},{"action":"BLOCK","secretType":"GOOGLE_OAUTH_ACCESS_TOKEN"}],"anonymization":"NONE","defaultAction":"DETECT"}},{"detector":"gibberish","enabled":true,"severity":"MEDIUM","configuration":{"action":"DETECT","threshold":0.9}},{"detector":"malicious_url","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK"}},{"detector":"invisible_text","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK"}},{"detector":"personal_data","enabled":true,"severity":"LOW","configuration":{"action":"DETECT","threshold":0.8}},{"detector":"refusal","enabled":true,"severity":"LOW","configuration":{"action":"DETECT"}},{"detector":"pii_deepscan","enabled":true,"severity":"LOW","configuration":{"action":"DETECT","threshold":0.87}},{"detector":"url_reachability","enabled":true,"severity":"LOW","configuration":{"action":"DETECT"}},{"detector":"code","enabled":true,"severity":"LOW","configuration":{"languages":[{"action":"DETECT","language":"C"},{"action":"DETECT","language":"C#"},{"action":"DETECT","language":"C++"},{"action":"DETECT","language":"CLI + Command"},{"action":"DETECT","language":"COBOL"},{"action":"DETECT","language":"Erlang"},{"action":"DETECT","language":"Fortran"},{"action":"DETECT","language":"Go"},{"action":"DETECT","language":"Java"},{"action":"DETECT","language":"JavaScript"},{"action":"DETECT","language":"Kotlin"},{"action":"DETECT","language":"Lua"},{"action":"DETECT","language":"Mathematica/Wolfram + Language"},{"action":"DETECT","language":"PHP"},{"action":"DETECT","language":"Pascal"},{"action":"DETECT","language":"Perl"},{"action":"DETECT","language":"R"},{"action":"DETECT","language":"Ruby"},{"action":"DETECT","language":"Rust"},{"action":"DETECT","language":"Scala"},{"action":"DETECT","language":"SQL"},{"action":"DETECT","language":"Swift"},{"action":"DETECT","language":"Visual + Basic .NET"},{"action":"DETECT","language":"jq"},{"action":"DETECT","language":"Python"}],"threshold":0.35,"defaultAction":"DETECT"}},{"detector":"brand_and_reputation_risk","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.85}}],"description":"PolicyRule01","createTimeMillis":1774752247197,"updateTimeMillis":1776749485316}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '8' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 774a08ab-6c39-975a-9116-6effd997882e + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/2933 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '12' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - be97312f-bfc5-95ef-8dba-e4d5cdce4f29 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/cassettes/TestPolicyMatchRules.yaml b/tests/integration/aiguard/cassettes/TestPolicyMatchRules.yaml new file mode 100644 index 00000000..ec2b5daa --- /dev/null +++ b/tests/integration/aiguard/cassettes/TestPolicyMatchRules.yaml @@ -0,0 +1,980 @@ +interactions: +- request: + body: '{"name": "tests-pol-vcr0001", "inputDetectorPolicies": [{"detector": "toxicity", + "enabled": true, "severity": "HIGH", "configuration": {"action": "BLOCK", "threshold": + 0.87}}, {"detector": "prompt_injection", "enabled": true, "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.75}}], "outputDetectorPolicies": + [{"detector": "toxicity", "enabled": true, "severity": "CRITICAL", "configuration": + {"action": "BLOCK", "threshold": 0.87}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '460' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies + response: + body: + string: '{"id":2934,"name":"tests-pol-vcr0001","version":1,"inputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"HIGH","configuration":{"action":"BLOCK","threshold":0.87}},{"detector":"prompt_injection","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.75}}],"outputDetectorPolicies":[{"detector":"toxicity","enabled":true,"severity":"CRITICAL","configuration":{"action":"BLOCK","threshold":0.87}}],"createTimeMillis":1784750617724,"updateTimeMillis":1784750617724}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '13' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - f9badc43-9a64-97f5-a3ca-cf9ea256c94e + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{"name": "tests-la-vcr0002", "ownerEmail": "tests-la-vcr0002@acme.com", + "applicationSettings": {"includeEventContents": true, "encryptEventContents": + false}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '157' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications + response: + body: + string: '{"id":1583,"name":"tests-la-vcr0002","ownerEmail":"REDACTED","applicationSettings":{"includeEventContents":true,"encryptEventContents":false},"createTimeMillis":1784750617834,"updateTimeMillis":1784750617834}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '11' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - da712e4b-c25d-9d6f-b0cb-b3615a456336 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '993' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-providers/name/Default%20Anthropic%20Provider + response: + body: + string: '{"id":6099,"name":"Default Anthropic Provider","type":"anthropic","createTimeMillis":1775790150684,"updateTimeMillis":1775790150684,"public":true}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:37 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '4' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 14d581c0-0ff3-9947-85e1-c85e6d5bac75 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '992' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/llm-provider-credentials + response: + body: + string: '{"items":[{"id":744,"providerId":6099,"name":"Anthropic_API01","createTimeMillis":1784745192169,"updateTimeMillis":1784745192169,"expireTimeMillis":1785481200000},{"id":747,"providerId":6099,"name":"Anthropic_API02","createTimeMillis":1784745463518,"updateTimeMillis":1784745463518},{"id":739,"providerId":6099,"name":"Anthropic_API_Key","createTimeMillis":1784654432715,"updateTimeMillis":1784743942727,"expireTimeMillis":1785481200000}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 4b06d6e8-f7ed-970a-bf85-23e6a6a418fb + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"applicationId": 1583, "providerId": 6099, "providerCredentialsId": 744, + "name": "tests-lac-vcr0003", "mode": "PROXY"}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '119' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials + response: + body: + string: '{"id":2155,"applicationId":1583,"providerId":6099,"providerCredentialsId":744,"name":"tests-lac-vcr0003","mode":"PROXY","key":"REDACTED","createTimeMillis":1784750618138,"updateTimeMillis":1784750618138}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '17' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 6f876cd6-6694-9e2c-8924-7b16a8a201ba + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{"policyId": 2934, "name": "tests-pmr-vcr0004", "enabled": true, "ruleOrder": + 2, "matchCriteria": {"llmApplications": [{"applicationId": 1583, "applicationCredentialsIds": + [2155]}], "type": "DAS_APPLICATION"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '209' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: POST + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules + response: + body: + string: '{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004","enabled":true,"ruleOrder":2,"version":1,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '23' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - f866031a-48ca-9f5f-bedc-4870c54a6fa8 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules/2758 + response: + body: + string: '{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004","enabled":true,"ruleOrder":2,"version":1,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '6' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 696b68e9-3f4e-97d3-bd2e-efe5227ffaf5 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '996' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules/name/tests-pmr-vcr0004 + response: + body: + string: '{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004","enabled":true,"ruleOrder":2,"version":1,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '7' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 873b1d57-df32-9774-9e87-0a27a0bd9d62 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '995' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"policyId": 2934, "name": "tests-pmr-vcr0004Updated", "enabled": true, + "ruleOrder": 2, "matchCriteria": {"llmApplications": [{"applicationId": 1583, + "applicationCredentialsIds": [2155]}], "type": "DAS_APPLICATION"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '216' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: PUT + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules/2758 + response: + body: + string: '{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004Updated","enabled":true,"ruleOrder":2,"version":2,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '24' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 814affff-1614-9b00-aaac-c5a5893108ba + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '994' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules/2758 + response: + body: + string: '{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004Updated","enabled":true,"ruleOrder":2,"version":2,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '7' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 31b9f3fd-b76f-94cc-ae9d-6fd3757de83d + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '993' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: GET + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules + response: + body: + string: '{"items":[{"id":978,"policyId":1152,"name":"PolicyRule01","enabled":true,"ruleOrder":1,"version":1,"matchCriteria":{"llmApplications":[{"applicationId":647,"applicationCredentialsIds":[1075]}],"sourceIpAddresses":[],"applicationGroups":[],"customRequestHeaders":[],"type":"DAS_APPLICATION"}},{"id":2758,"policyId":2934,"name":"tests-pmr-vcr0004Updated","enabled":true,"ruleOrder":2,"version":2,"matchCriteria":{"llmApplications":[{"applicationId":1583,"applicationCredentialsIds":[2155]}],"type":"DAS_APPLICATION"}}]}' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '7' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 4b398422-8e59-9d2c-be96-f0166eeb8c53 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '992' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policy-match-rules/2758 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:38 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '10' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 1cc5881c-931c-94ec-bedd-abe4ed81cba5 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '991' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-application-credentials/2155 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:39 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '13' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - adef3a9a-c33c-9495-92a2-7e5c827cf026 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '999' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/llm-applications/1583 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:39 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '9' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 072fd609-d5b0-9e65-8afe-b775ffddd3f0 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '998' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +- request: + body: '{}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + User-Agent: + - zscaler-sdk-python/1.9.39 python/3.11.8 Darwin/25.5.0 + method: DELETE + uri: https://easm.test.zscaler.com/aiguard/v1/detections/policies/2934 + response: + body: + string: '' + headers: + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + date: + - Wed, 22 Jul 2026 20:03:39 GMT + expires: + - '0' + pragma: + - no-cache + server: + - Zscaler + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-envoy-upstream-service-time: + - '12' + x-frame-options: + - DENY + x-oneapi-host: + - https://apiusw2.zsapi.net + x-oneapi-request-id: + - 305ae334-d640-9677-852b-eb0e61b7cf20 + x-oneapi-version: + - 111.1.54 + x-ratelimit-limit: + - 1000, 1000;w=1 + x-ratelimit-remaining: + - '997' + x-ratelimit-reset: + - '1' + x-transaction-id: + - c8078f6a-077d-42df-8d96-d31429769b5f + x-xss-protection: + - '0' + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/integration/aiguard/conftest.py b/tests/integration/aiguard/conftest.py new file mode 100644 index 00000000..b0bfa2f5 --- /dev/null +++ b/tests/integration/aiguard/conftest.py @@ -0,0 +1,94 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import os + +import pytest + +from tests.test_utils import reset_vcr_counters +from zscaler import ZscalerClient + +PYTEST_MOCK_CLIENT = "pytest_mock_client" + + +@pytest.fixture(autouse=True, scope="function") +def reset_counters_per_test(): + """ + Reset VCR counters before each test function. + + This ensures that generate_random_string() and generate_random_ip() + return the same deterministic values during both recording and playback. + """ + reset_vcr_counters() + yield + + +@pytest.fixture(scope="function") +def aiguard_client(fs): + return MockAIGuardClient(fs) + + +class MockAIGuardClient(ZscalerClient): + """ + Mock client for the Zscaler AI Guard service. + + AI Guard is OneAPI-only (no legacy client), so this mirrors the OneAPI + ``ZscalerClient`` bootstrap used by the other OneAPI services (e.g. ZINS, + ZMS) and does not require a customer id. + """ + + def __init__(self, fs, config=None): + """ + Initialize the MockAIGuardClient with support for environment variables + and optional inline config. + + Args: + fs: Fixture to pause/resume the filesystem mock for pyfakefs. + config: Optional dictionary containing client configuration + (clientId, clientSecret, vanityDomain, cloud). + """ + config = config or {} + + # VCR playback mode (MOCK_TESTS=true means use recorded cassettes). + mock_tests = os.getenv("MOCK_TESTS", "true").strip().lower() != "false" + + clientId = config.get("clientId", os.getenv("ZSCALER_CLIENT_ID")) + clientSecret = config.get("clientSecret", os.getenv("ZSCALER_CLIENT_SECRET")) + vanityDomain = config.get("vanityDomain", os.getenv("ZSCALER_VANITY_DOMAIN")) + cloud = config.get("cloud", os.getenv("ZSCALER_CLOUD", "beta")) + + # In playback mode, fall back to dummy credentials when real ones are absent. + if mock_tests: + clientId = clientId or "dummy_client_id" + clientSecret = clientSecret or "dummy_client_secret" + vanityDomain = vanityDomain or "dummy_vanity_domain" + + logging_config = config.get("logging", {"enabled": False, "verbose": False}) + + client_config = { + "clientId": clientId, + "clientSecret": clientSecret, + "vanityDomain": vanityDomain, + "cloud": cloud, + "logging": {"enabled": logging_config.get("enabled", False), "verbose": logging_config.get("verbose", False)}, + } + + if PYTEST_MOCK_CLIENT in os.environ and fs is not None: + fs.pause() + super().__init__(client_config) + fs.resume() + else: + super().__init__(client_config) diff --git a/tests/integration/aiguard/sweep/run_sweep.py b/tests/integration/aiguard/sweep/run_sweep.py new file mode 100644 index 00000000..8f9998f2 --- /dev/null +++ b/tests/integration/aiguard/sweep/run_sweep.py @@ -0,0 +1,263 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import os +import sys +from pathlib import Path + +# Add project root to path so zscaler is importable when run as script +_project_root = Path(__file__).resolve().parent.parent.parent.parent.parent +if str(_project_root) not in sys.path: + sys.path.insert(0, str(_project_root)) + +import logging # noqa: E402 + +from zscaler import ZscalerClient # noqa: E402 + + +class TestSweepUtility: + def __init__(self, config=None): + """ + Initializes the TestSweepUtility with ZscalerClient configuration. + """ + config = config or {} + + client_id = config.get("clientId", os.getenv("ZSCALER_CLIENT_ID")) + client_secret = config.get("clientSecret", os.getenv("ZSCALER_CLIENT_SECRET")) + vanity_domain = config.get("vanityDomain", os.getenv("ZSCALER_VANITY_DOMAIN")) + cloud = config.get("cloud", os.getenv("ZSCALER_CLOUD", "PRODUCTION")) + + logging_config = config.get("logging", {"enabled": False, "verbose": False}) + + client_config = { + "clientId": client_id, + "clientSecret": client_secret, + "vanityDomain": vanity_domain, + "cloud": cloud, + "logging": {"enabled": logging_config.get("enabled", True), "verbose": logging_config.get("verbose", True)}, + } + + self.client = ZscalerClient(client_config) + + def suppress_warnings(func): + def wrapper(*args, **kwargs): + previous_level = logging.getLogger().level + logging.getLogger().setLevel(logging.ERROR) + result = func(*args, **kwargs) + logging.getLogger().setLevel(previous_level) + return result + + return wrapper + + def run_sweep_functions(self): + # Ordered so that dependent resources are removed before their parents. + sweep_functions = [ + self.sweep_llm_application_credentials, + self.sweep_llm_provider_credentials, + self.sweep_policy_match_rules, + self.sweep_policies, + self.sweep_llm_applications, + self.sweep_llm_providers, + ] + + for func in sweep_functions: + logging.info(f"Executing {func.__name__}") + func() + + @suppress_warnings + def sweep_policies(self): + logging.info("Starting to sweep detection policies") + try: + policies, _, error = self.client.aiguard.policies.list_policies() + if error: + raise Exception(f"Error listing detection policies: {error}") + + test_policies = [p for p in (policies or []) if hasattr(p, "name") and p.name.startswith("tests-")] + logging.info(f"Found {len(test_policies)} detection policies named starting with 'tests-' to delete.") + + for policy in test_policies: + logging.info(f"sweep_policies: Attempting to delete detection policy: Name='{policy.name}', ID='{policy.id}'") + _, _, error = self.client.aiguard.policies.delete_policy(policy.id) + if error: + logging.error(f"Failed to delete detection policy ID={policy.id} — {error}") + else: + logging.info(f"Successfully deleted detection policy ID={policy.id}") + + except Exception as e: + logging.error(f"An error occurred while sweeping detection policies: {str(e)}") + raise + + @suppress_warnings + def sweep_policy_match_rules(self): + logging.info("Starting to sweep policy match rules") + try: + rules, _, error = self.client.aiguard.policy_match_rules.list_rules() + if error: + raise Exception(f"Error listing policy match rules: {error}") + + test_rules = [r for r in (rules or []) if hasattr(r, "name") and r.name.startswith("tests-")] + logging.info(f"Found {len(test_rules)} policy match rules named starting with 'tests-' to delete.") + + for rule in test_rules: + logging.info( + f"sweep_policy_match_rules: Attempting to delete policy match rule: Name='{rule.name}', ID='{rule.id}'" + ) + _, _, error = self.client.aiguard.policy_match_rules.delete_rule(rule.id) + if error: + logging.error(f"Failed to delete policy match rule ID={rule.id} — {error}") + else: + logging.info(f"Successfully deleted policy match rule ID={rule.id}") + + except Exception as e: + logging.error(f"An error occurred while sweeping policy match rules: {str(e)}") + raise + + @suppress_warnings + def sweep_llm_applications(self): + logging.info("Starting to sweep LLM applications") + try: + applications, _, error = self.client.aiguard.llm_applications.list_applications() + if error: + raise Exception(f"Error listing LLM applications: {error}") + + test_applications = [a for a in (applications or []) if hasattr(a, "name") and a.name.startswith("tests-")] + logging.info(f"Found {len(test_applications)} LLM applications named starting with 'tests-' to delete.") + + for application in test_applications: + logging.info( + f"sweep_llm_applications: Attempting to delete LLM application: " + f"Name='{application.name}', ID='{application.id}'" + ) + _, _, error = self.client.aiguard.llm_applications.delete_application(application.id) + if error: + logging.error(f"Failed to delete LLM application ID={application.id} — {error}") + else: + logging.info(f"Successfully deleted LLM application ID={application.id}") + + except Exception as e: + logging.error(f"An error occurred while sweeping LLM applications: {str(e)}") + raise + + @suppress_warnings + def sweep_llm_providers(self): + logging.info("Starting to sweep LLM providers") + try: + providers, _, error = self.client.aiguard.llm_providers.list_providers() + if error: + raise Exception(f"Error listing LLM providers: {error}") + + test_providers = [p for p in (providers or []) if hasattr(p, "name") and p.name.startswith("tests-")] + logging.info(f"Found {len(test_providers)} LLM providers named starting with 'tests-' to delete.") + + for provider in test_providers: + logging.info( + f"sweep_llm_providers: Attempting to delete LLM provider: Name='{provider.name}', ID='{provider.id}'" + ) + _, _, error = self.client.aiguard.llm_providers.delete_provider(provider.id) + if error: + logging.error(f"Failed to delete LLM provider ID={provider.id} — {error}") + else: + logging.info(f"Successfully deleted LLM provider ID={provider.id}") + + except Exception as e: + logging.error(f"An error occurred while sweeping LLM providers: {str(e)}") + raise + + @suppress_warnings + def sweep_llm_provider_credentials(self): + logging.info("Starting to sweep LLM provider credentials") + try: + # The LlmProviderCredentials model does not expose an id attribute, so the raw + # response body is used to pair each credential name with its id. + _, response, error = self.client.aiguard.llm_provider_credentials.list_credentials() + if error: + raise Exception(f"Error listing LLM provider credentials: {error}") + + items = (response.get_body() or {}).get("items", []) if response else [] + test_credentials = [i for i in items if str(i.get("name", "")).startswith("tests-")] + logging.info(f"Found {len(test_credentials)} LLM provider credentials named starting with 'tests-' to delete.") + + for credential in test_credentials: + logging.info( + f"sweep_llm_provider_credentials: Attempting to delete LLM provider credential: " + f"Name='{credential.get('name')}', ID='{credential.get('id')}'" + ) + _, _, error = self.client.aiguard.llm_provider_credentials.delete_credential(credential.get("id")) + if error: + logging.error(f"Failed to delete LLM provider credential ID={credential.get('id')} — {error}") + else: + logging.info(f"Successfully deleted LLM provider credential ID={credential.get('id')}") + + except Exception as e: + logging.error(f"An error occurred while sweeping LLM provider credentials: {str(e)}") + raise + + @suppress_warnings + def sweep_llm_application_credentials(self): + logging.info("Starting to sweep LLM application credentials") + try: + credentials, _, error = self.client.aiguard.llm_application_credentials.list_credentials() + if error: + raise Exception(f"Error listing LLM application credentials: {error}") + + test_credentials = [c for c in (credentials or []) if hasattr(c, "name") and c.name.startswith("tests-")] + logging.info(f"Found {len(test_credentials)} LLM application credentials named starting with 'tests-' to delete.") + + for credential in test_credentials: + logging.info( + f"sweep_llm_application_credentials: Attempting to delete LLM application credential: " + f"Name='{credential.name}', ID='{credential.id}'" + ) + _, _, error = self.client.aiguard.llm_application_credentials.delete_credential(credential.id) + if error: + logging.error(f"Failed to delete LLM application credential ID={credential.id} — {error}") + else: + logging.info(f"Successfully deleted LLM application credential ID={credential.id}") + + except Exception as e: + logging.error(f"An error occurred while sweeping LLM application credentials: {str(e)}") + raise + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + + # Ensure the environment variable is set + if not os.getenv("AIGUARD_SDK_TEST_SWEEP"): + os.environ["AIGUARD_SDK_TEST_SWEEP"] = "true" + logging.info("Environment variable AIGUARD_SDK_TEST_SWEEP was not set. Setting it to true.") + + env_var = os.getenv("AIGUARD_SDK_TEST_SWEEP") + flag_present = "--sweep" in sys.argv + logging.info(f"Environment variable AIGUARD_SDK_TEST_SWEEP: {env_var}") + logging.info(f"Sweep flag presence: {flag_present}") + + if env_var == "true" and flag_present: + sweeper = TestSweepUtility() + + # Pre-test sweep + logging.info("Running pre-test sweep.") + sweeper.run_sweep_functions() + + # Placeholder for main test execution + logging.info("Executing main test suite...") + # Insert your test suite execution here + + # Post-test sweep + logging.info("Running post-test sweep.") + sweeper.run_sweep_functions() + else: + logging.info("Sweep flag not set or environment variable AIGUARD_SDK_TEST_SWEEP is not set to true. Skipping sweep.") diff --git a/tests/integration/aiguard/test_llm_application_credentials.py b/tests/integration/aiguard/test_llm_application_credentials.py new file mode 100644 index 00000000..bcb71eed --- /dev/null +++ b/tests/integration/aiguard/test_llm_application_credentials.py @@ -0,0 +1,147 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestLlmApplicationCredentials: + """ + Integration Tests for the AI Guard LLM Application Credentials. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_llm_application_credentials(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + provider_name = "Default Anthropic Provider" + application_name = "App01" + credential_name = "tests-lac-" + generate_random_string() + credential_mode = "PROXY" + provider_id = None # Initialize provider_id + application_id = None # Initialize application_id + provider_credentials_id = None # Initialize provider_credentials_id + credential_id = None # Initialize credential_id + + try: + # Resolve the provider id by name so it can be passed to the credential payload + provider, _, err = client.aiguard.llm_providers.get_provider_by_name(provider_name) + assert err is None, f"Error fetching LLM provider by name: {err}" + assert provider is not None, f"LLM provider '{provider_name}' was not found" + provider_id = provider.id + assert provider_id is not None + + # Resolve the application id by name so it can be passed to the credential payload + application, _, err = client.aiguard.llm_applications.get_application_by_name(application_name) + assert err is None, f"Error fetching LLM application by name: {err}" + assert application is not None, f"LLM application '{application_name}' was not found" + application_id = application.id + assert application_id is not None + + # Resolve the provider credentials id belonging to the resolved provider. The + # provider credential model does not expose an id attribute, so the id is read + # from the raw response body. + _, response, err = client.aiguard.llm_provider_credentials.list_credentials() + assert err is None, f"Error listing LLM provider credentials: {err}" + for item in (response.get_body() or {}).get("items", []): + if item.get("providerId") == provider_id: + provider_credentials_id = item.get("id") + break + assert provider_credentials_id is not None, f"No provider credential found for provider '{provider_name}'" + except Exception as exc: + errors.append(f"Error resolving the LLM application credential dependencies: {exc}") + + try: + if application_id and provider_id and provider_credentials_id: + # Create a new LLM application credential + created_credential, _, err = client.aiguard.llm_application_credentials.add_credential( + applicationId=application_id, + providerId=provider_id, + providerCredentialsId=provider_credentials_id, + name=credential_name, + mode=credential_mode, + ) + assert err is None, f"Error creating LLM application credential: {err}" + assert created_credential is not None + assert created_credential.name == credential_name + assert created_credential.mode == credential_mode + assert created_credential.application_id == application_id + assert created_credential.provider_id == provider_id + assert created_credential.provider_credentials_id == provider_credentials_id + + credential_id = created_credential.id # Capture the credential_id for later use + except Exception as exc: + errors.append(f"Error during LLM application credential creation: {exc}") + + try: + if credential_id: + # Retrieve the created LLM application credential by ID + retrieved_credential, _, err = client.aiguard.llm_application_credentials.get_credential(credential_id) + assert err is None, f"Error fetching LLM application credential: {err}" + assert retrieved_credential.id == credential_id + assert retrieved_credential.name == credential_name + + # Retrieve the created LLM application credential by name + credential_by_name, _, err = client.aiguard.llm_application_credentials.get_credential_by_name(credential_name) + assert err is None, f"Error fetching LLM application credential by name: {err}" + assert credential_by_name.id == credential_id + assert credential_by_name.name == credential_name + + # Update the LLM application credential + updated_credential_name = credential_name + "Updated" + _, _, err = client.aiguard.llm_application_credentials.update_credential( + credential_id, + applicationId=application_id, + providerId=provider_id, + providerCredentialsId=provider_credentials_id, + name=updated_credential_name, + mode=credential_mode, + ) + assert err is None, f"Error updating LLM application credential: {err}" + + updated_credential, _, err = client.aiguard.llm_application_credentials.get_credential(credential_id) + assert err is None, f"Error fetching updated LLM application credential: {err}" + assert updated_credential.name == updated_credential_name + + # List LLM application credentials and ensure the created credential is in the list + credentials_list, _, err = client.aiguard.llm_application_credentials.list_credentials() + assert err is None, f"Error listing LLM application credentials: {err}" + assert any(credential.id == credential_id for credential in credentials_list) + except Exception as exc: + errors.append(f"LLM application credential operation failed: {exc}") + + finally: + # Cleanup: Delete the LLM application credential if it was created + if credential_id: + try: + delete_response, _, err = client.aiguard.llm_application_credentials.delete_credential(credential_id) + assert err is None, f"Error deleting LLM application credential: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM application credential ID {credential_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the LLM application credential lifecycle test: {errors}" diff --git a/tests/integration/aiguard/test_llm_applications.py b/tests/integration/aiguard/test_llm_applications.py new file mode 100644 index 00000000..38d2262b --- /dev/null +++ b/tests/integration/aiguard/test_llm_applications.py @@ -0,0 +1,115 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestLlmApplications: + """ + Integration Tests for the AI Guard LLM Applications. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_llm_applications(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + application_name = "tests-la-" + generate_random_string() + owner_email = "tests-la-" + generate_random_string() + "@acme.com" + application_settings = { + "includeEventContents": True, + "encryptEventContents": False, + } + application_id = None # Initialize application_id + + try: + # Create a new LLM application + created_application, _, err = client.aiguard.llm_applications.add_application( + name=application_name, + ownerEmail=owner_email, + applicationSettings=application_settings, + ) + assert err is None, f"Error creating LLM application: {err}" + assert created_application is not None + assert created_application.name == application_name + # NOTE: tests/conftest.py redacts any quoted value containing "@" from recorded + # responses, so owner_email always reads back as "REDACTED" and is not asserted. + assert created_application.application_settings.include_event_contents is True + assert created_application.application_settings.encrypt_event_contents is False + + application_id = created_application.id # Capture the application_id for later use + except Exception as exc: + errors.append(f"Error during LLM application creation: {exc}") + + try: + if application_id: + # Retrieve the created LLM application by ID + retrieved_application, _, err = client.aiguard.llm_applications.get_application(application_id) + assert err is None, f"Error fetching LLM application: {err}" + assert retrieved_application.id == application_id + assert retrieved_application.name == application_name + + # Retrieve the created LLM application by name + application_by_name, _, err = client.aiguard.llm_applications.get_application_by_name(application_name) + assert err is None, f"Error fetching LLM application by name: {err}" + assert application_by_name.id == application_id + assert application_by_name.name == application_name + + # Update the LLM application. NOTE: encryptEventContents is left False -- + # enabling it requires a customer-managed key (CMK) configured in tenant + # settings, which the API rejects otherwise. + updated_application_name = application_name + "Updated" + _, _, err = client.aiguard.llm_applications.update_application( + application_id, + name=updated_application_name, + ownerEmail=owner_email, + applicationSettings=application_settings, + ) + assert err is None, f"Error updating LLM application: {err}" + + updated_application, _, err = client.aiguard.llm_applications.get_application(application_id) + assert err is None, f"Error fetching updated LLM application: {err}" + assert updated_application.name == updated_application_name + + # List LLM applications and ensure the created application is in the list + applications_list, _, err = client.aiguard.llm_applications.list_applications() + assert err is None, f"Error listing LLM applications: {err}" + assert any(application.id == application_id for application in applications_list) + except Exception as exc: + errors.append(f"LLM application operation failed: {exc}") + + finally: + # Cleanup: Delete the LLM application if it was created + if application_id: + try: + delete_response, _, err = client.aiguard.llm_applications.delete_application(application_id) + assert err is None, f"Error deleting LLM application: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM application ID {application_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the LLM application lifecycle test: {errors}" diff --git a/tests/integration/aiguard/test_llm_provider_credentials.py b/tests/integration/aiguard/test_llm_provider_credentials.py new file mode 100644 index 00000000..41e5d0a3 --- /dev/null +++ b/tests/integration/aiguard/test_llm_provider_credentials.py @@ -0,0 +1,119 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestLlmProviderCredentials: + """ + Integration Tests for the AI Guard LLM Provider Credentials. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_llm_provider_credentials(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + provider_name = "Default Anthropic Provider" + credential_name = "tests-lpc-" + generate_random_string() + api_credentials = {"type": "API_KEY", "key": "REDACTED_TEST_KEY"} + provider_id = None # Initialize provider_id + credential_id = None # Initialize credential_id + + try: + # Resolve the provider id by name so it can be passed to the credential payload + provider, _, err = client.aiguard.llm_providers.get_provider_by_name(provider_name) + assert err is None, f"Error fetching LLM provider by name: {err}" + assert provider is not None, f"LLM provider '{provider_name}' was not found" + provider_id = provider.id + assert provider_id is not None + except Exception as exc: + errors.append(f"Error resolving the LLM provider '{provider_name}': {exc}") + + try: + if provider_id: + # Create a new LLM provider credential + created_credential, response, err = client.aiguard.llm_provider_credentials.add_credential( + providerId=provider_id, + name=credential_name, + apiCredentials=api_credentials, + ) + assert err is None, f"Error creating LLM provider credential: {err}" + assert created_credential is not None + assert created_credential.name == credential_name + assert created_credential.provider_id == provider_id + + # The credential model does not expose an id attribute, so read it from the raw body + credential_id = (response.get_body() or {}).get("id") + except Exception as exc: + errors.append(f"Error during LLM provider credential creation: {exc}") + + try: + if credential_id: + # Retrieve the created LLM provider credential by ID + retrieved_credential, _, err = client.aiguard.llm_provider_credentials.get_credential(credential_id) + assert err is None, f"Error fetching LLM provider credential: {err}" + assert retrieved_credential.name == credential_name + assert retrieved_credential.provider_id == provider_id + + # Retrieve the created LLM provider credential by name + credential_by_name, _, err = client.aiguard.llm_provider_credentials.get_credential_by_name(credential_name) + assert err is None, f"Error fetching LLM provider credential by name: {err}" + assert credential_by_name.name == credential_name + + # Update the LLM provider credential + updated_credential_name = credential_name + "Updated" + _, _, err = client.aiguard.llm_provider_credentials.update_credential( + credential_id, + providerId=provider_id, + name=updated_credential_name, + apiCredentials=api_credentials, + ) + assert err is None, f"Error updating LLM provider credential: {err}" + + updated_credential, _, err = client.aiguard.llm_provider_credentials.get_credential(credential_id) + assert err is None, f"Error fetching updated LLM provider credential: {err}" + assert updated_credential.name == updated_credential_name + + # List LLM provider credentials and ensure the created credential is in the list + credentials_list, _, err = client.aiguard.llm_provider_credentials.list_credentials() + assert err is None, f"Error listing LLM provider credentials: {err}" + assert any(credential.name == updated_credential_name for credential in credentials_list) + except Exception as exc: + errors.append(f"LLM provider credential operation failed: {exc}") + + finally: + # Cleanup: Delete the LLM provider credential if it was created + if credential_id: + try: + delete_response, _, err = client.aiguard.llm_provider_credentials.delete_credential(credential_id) + assert err is None, f"Error deleting LLM provider credential: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM provider credential ID {credential_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the LLM provider credential lifecycle test: {errors}" diff --git a/tests/integration/aiguard/test_llm_providers.py b/tests/integration/aiguard/test_llm_providers.py new file mode 100644 index 00000000..4a085b25 --- /dev/null +++ b/tests/integration/aiguard/test_llm_providers.py @@ -0,0 +1,108 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestLlmProviders: + """ + Integration Tests for the AI Guard LLM Providers. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_llm_providers(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + provider_name = "tests-lp-" + generate_random_string() + provider_type = "xai" + # NOTE: provider create/update is constrained on both sides -- a public provider + # is not editable ("A public provider is not editable"), and a private one requires + # a `servers` payload ("'servers' is required for a private (public=false) + # provider."). The test therefore creates a public provider and skips the update. + provider_public = True + provider_id = None # Initialize provider_id + + try: + # Create a new LLM provider + created_provider, _, err = client.aiguard.llm_providers.add_provider( + name=provider_name, + type=provider_type, + public=provider_public, + ) + assert err is None, f"Error creating LLM provider: {err}" + assert created_provider is not None + assert created_provider.name == provider_name + assert created_provider.type == provider_type + assert created_provider.public is provider_public + + provider_id = created_provider.id # Capture the provider_id for later use + except Exception as exc: + errors.append(f"Error during LLM provider creation: {exc}") + + try: + if provider_id: + # Retrieve the created LLM provider by ID + retrieved_provider, _, err = client.aiguard.llm_providers.get_provider(provider_id) + assert err is None, f"Error fetching LLM provider: {err}" + assert retrieved_provider.id == provider_id + assert retrieved_provider.name == provider_name + + # Retrieve the created LLM provider by name + provider_by_name, _, err = client.aiguard.llm_providers.get_provider_by_name(provider_name) + assert err is None, f"Error fetching LLM provider by name: {err}" + assert provider_by_name.id == provider_id + assert provider_by_name.name == provider_name + + # List the supported LLM provider types + provider_types, _, err = client.aiguard.llm_providers.list_provider_types() + assert err is None, f"Error listing LLM provider types: {err}" + assert provider_types is not None + + # Retrieve the LLM provider type used by the created provider + retrieved_type, _, err = client.aiguard.llm_providers.get_provider_type(provider_type) + assert err is None, f"Error fetching LLM provider type: {err}" + assert retrieved_type is not None + + # List LLM providers and ensure the created provider is in the list + providers_list, _, err = client.aiguard.llm_providers.list_providers() + assert err is None, f"Error listing LLM providers: {err}" + assert any(provider.id == provider_id for provider in providers_list) + except Exception as exc: + errors.append(f"LLM provider operation failed: {exc}") + + finally: + # Cleanup: Delete the LLM provider if it was created + if provider_id: + try: + delete_response, _, err = client.aiguard.llm_providers.delete_provider(provider_id) + assert err is None, f"Error deleting LLM provider: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM provider ID {provider_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the LLM provider lifecycle test: {errors}" diff --git a/tests/integration/aiguard/test_policies.py b/tests/integration/aiguard/test_policies.py new file mode 100644 index 00000000..625298f0 --- /dev/null +++ b/tests/integration/aiguard/test_policies.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestPolicies: + """ + Integration Tests for the AI Guard Detection Policies. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_policies(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + policy_name = "tests-pol-" + generate_random_string() + input_detector_policies = [ + { + "detector": "toxicity", + "enabled": True, + "severity": "HIGH", + "configuration": {"action": "BLOCK", "threshold": 0.87}, + }, + { + "detector": "prompt_injection", + "enabled": True, + "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.75}, + }, + ] + output_detector_policies = [ + { + "detector": "toxicity", + "enabled": True, + "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.87}, + }, + { + "detector": "pii", + "enabled": False, + "severity": "CRITICAL", + "configuration": { + "entities": [ + {"action": "DETECT", "entityType": "CRYPTO"}, + {"action": "DETECT", "entityType": "EMAIL_ADDRESS"}, + {"action": "DETECT", "entityType": "IP_ADDRESS"}, + {"action": "DETECT", "entityType": "LOCATION"}, + {"action": "DETECT", "entityType": "US_ITIN"}, + {"action": "DETECT", "entityType": "DATE_TIME"}, + {"action": "DETECT", "entityType": "URL"}, + {"action": "DETECT", "entityType": "MEDICAL_LICENSE"}, + {"action": "DETECT", "entityType": "STREET_ADDRESS"}, + {"action": "DETECT", "entityType": "DATE_OF_BIRTH"}, + {"action": "DETECT", "entityType": "US_DEA_NUMBER"}, + {"action": "BLOCK", "entityType": "CREDIT_CARD"}, + {"action": "BLOCK", "entityType": "US_SSN"}, + {"action": "DETECT", "entityType": "PERSON"}, + {"action": "BLOCK", "entityType": "US_PASSPORT"}, + {"action": "BLOCK", "entityType": "US_BANK_NUMBER"}, + {"action": "BLOCK", "entityType": "US_DRIVER_LICENSE"}, + {"action": "BLOCK", "entityType": "IBAN_CODE"}, + {"action": "BLOCK", "entityType": "SWIFT_CODE"}, + {"action": "ALLOW", "entityType": "PHONE_NUMBER"}, + ], + "threshold": 0.5, + "anonymization": "NONE", + "defaultAction": "BLOCK", + "replaceWithMaskedContent": False, + }, + }, + ] + policy_id = None # Initialize policy_id + + try: + # Create a new detection policy + created_policy, _, err = client.aiguard.policies.add_policy( + name=policy_name, + inputDetectorPolicies=input_detector_policies, + outputDetectorPolicies=output_detector_policies, + ) + assert err is None, f"Error creating policy: {err}" + assert created_policy is not None + assert created_policy.name == policy_name + assert len(created_policy.input_detector_policies) == 2 + assert len(created_policy.output_detector_policies) == 2 + + policy_id = created_policy.id # Capture the policy_id for later use + except Exception as exc: + errors.append(f"Error during detection policy creation: {exc}") + + try: + if policy_id: + # Retrieve the created detection policy by ID + retrieved_policy, _, err = client.aiguard.policies.get_policy(policy_id) + assert err is None, f"Error fetching policy: {err}" + assert retrieved_policy.id == policy_id + assert retrieved_policy.name == policy_name + + # Retrieve the created detection policy by name + policy_by_name, _, err = client.aiguard.policies.get_policy_by_name(policy_name) + assert err is None, f"Error fetching policy by name: {err}" + assert policy_by_name.name == policy_name + + # Update the detection policy + updated_name = policy_name + "Updated" + _, _, err = client.aiguard.policies.update_policy( + policy_id, + name=updated_name, + inputDetectorPolicies=input_detector_policies, + outputDetectorPolicies=output_detector_policies, + ) + assert err is None, f"Error updating policy: {err}" + + updated_policy, _, err = client.aiguard.policies.get_policy(policy_id) + assert err is None, f"Error fetching updated policy: {err}" + assert updated_policy.name == updated_name + + # List detection policies and ensure the updated policy is in the list + policies_list, _, err = client.aiguard.policies.list_policies() + assert err is None, f"Error listing policies: {err}" + assert any(policy.id == policy_id for policy in policies_list) + except Exception as exc: + errors.append(f"Detection policy operation failed: {exc}") + + finally: + # Cleanup: Delete the detection policy if it was created + if policy_id: + try: + delete_response, _, err = client.aiguard.policies.delete_policy(policy_id) + assert err is None, f"Error deleting policy: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for detection policy ID {policy_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the detection policy lifecycle test: {errors}" diff --git a/tests/integration/aiguard/test_policy_match_rules.py b/tests/integration/aiguard/test_policy_match_rules.py new file mode 100644 index 00000000..18cba1fb --- /dev/null +++ b/tests/integration/aiguard/test_policy_match_rules.py @@ -0,0 +1,245 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +import pytest + +from tests.integration.aiguard.conftest import MockAIGuardClient +from tests.test_utils import generate_random_string + + +@pytest.fixture +def fs(): + yield + + +class TestPolicyMatchRules: + """ + Integration Tests for the AI Guard Policy Match Rules. + + These tests use VCR to record and replay HTTP interactions. + """ + + @pytest.mark.vcr() + def test_policy_match_rules(self, fs): + client = MockAIGuardClient(fs) + errors = [] # Initialize an empty list to collect errors + + provider_name = "Default Anthropic Provider" + policy_name = "tests-pol-" + generate_random_string() + application_name = "tests-la-" + generate_random_string() + app_credential_name = "tests-lac-" + generate_random_string() + rule_name = "tests-pmr-" + generate_random_string() + policy_input_detector_policies = [ + { + "detector": "toxicity", + "enabled": True, + "severity": "HIGH", + "configuration": {"action": "BLOCK", "threshold": 0.87}, + }, + { + "detector": "prompt_injection", + "enabled": True, + "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.75}, + }, + ] + policy_output_detector_policies = [ + { + "detector": "toxicity", + "enabled": True, + "severity": "CRITICAL", + "configuration": {"action": "BLOCK", "threshold": 0.87}, + }, + ] + application_settings = {"includeEventContents": True, "encryptEventContents": False} + policy_id = None # Initialize policy_id + application_id = None # Initialize application_id + provider_id = None # Initialize provider_id + provider_credentials_id = None # Initialize provider_credentials_id + app_credential_id = None # Initialize app_credential_id + rule_id = None # Initialize rule_id + + try: + # A policy match rule references a policy, an application and an application + # credential, so each prerequisite is created (or resolved) first and its id + # captured for the match rule payload. + + # Create the detection policy the match rule attaches to + created_policy, _, err = client.aiguard.policies.add_policy( + name=policy_name, + inputDetectorPolicies=policy_input_detector_policies, + outputDetectorPolicies=policy_output_detector_policies, + ) + assert err is None, f"Error creating detection policy: {err}" + assert created_policy is not None + policy_id = created_policy.id + assert policy_id is not None + + # Create the LLM application the match criteria points at + created_application, _, err = client.aiguard.llm_applications.add_application( + name=application_name, + ownerEmail=application_name + "@acme.com", + applicationSettings=application_settings, + ) + assert err is None, f"Error creating LLM application: {err}" + assert created_application is not None + application_id = created_application.id + assert application_id is not None + + # Resolve the provider id by name + provider, _, err = client.aiguard.llm_providers.get_provider_by_name(provider_name) + assert err is None, f"Error fetching LLM provider by name: {err}" + assert provider is not None, f"LLM provider '{provider_name}' was not found" + provider_id = provider.id + assert provider_id is not None + + # Resolve a provider credential belonging to the resolved provider. The provider + # credential model does not expose an id attribute, so it is read from the raw body. + _, response, err = client.aiguard.llm_provider_credentials.list_credentials() + assert err is None, f"Error listing LLM provider credentials: {err}" + for item in (response.get_body() or {}).get("items", []): + if item.get("providerId") == provider_id: + provider_credentials_id = item.get("id") + break + assert provider_credentials_id is not None, f"No provider credential found for provider '{provider_name}'" + + # Create the application credential referenced by the match criteria + created_app_credential, _, err = client.aiguard.llm_application_credentials.add_credential( + applicationId=application_id, + providerId=provider_id, + providerCredentialsId=provider_credentials_id, + name=app_credential_name, + mode="PROXY", + ) + assert err is None, f"Error creating LLM application credential: {err}" + assert created_app_credential is not None + app_credential_id = created_app_credential.id + assert app_credential_id is not None + except Exception as exc: + errors.append(f"Error resolving the policy match rule dependencies: {exc}") + + try: + if policy_id and application_id and app_credential_id: + # Create a new policy match rule + created_rule, _, err = client.aiguard.policy_match_rules.add_rule( + policyId=policy_id, + name=rule_name, + enabled=True, + ruleOrder=2, + matchCriteria={ + "llmApplications": [ + { + "applicationId": application_id, + "applicationCredentialsIds": [app_credential_id], + } + ], + "type": "DAS_APPLICATION", + }, + ) + assert err is None, f"Error creating policy match rule: {err}" + assert created_rule is not None + assert created_rule.name == rule_name + assert created_rule.policy_id == policy_id + assert created_rule.enabled is True + assert created_rule.rule_order == 2 + assert created_rule.match_criteria.type == "DAS_APPLICATION" + assert len(created_rule.match_criteria.llm_applications) == 1 + assert created_rule.match_criteria.llm_applications[0].application_id == application_id + assert created_rule.match_criteria.llm_applications[0].application_credentials_ids == [app_credential_id] + + rule_id = created_rule.id # Capture the rule_id for later use + except Exception as exc: + errors.append(f"Error during policy match rule creation: {exc}") + + try: + if rule_id: + # Retrieve the created policy match rule by ID + retrieved_rule, _, err = client.aiguard.policy_match_rules.get_rule(rule_id) + assert err is None, f"Error fetching policy match rule: {err}" + assert retrieved_rule.id == rule_id + assert retrieved_rule.name == rule_name + + # Retrieve the created policy match rule by name + rule_by_name, _, err = client.aiguard.policy_match_rules.get_rule_by_name(rule_name) + assert err is None, f"Error fetching policy match rule by name: {err}" + assert rule_by_name.id == rule_id + assert rule_by_name.name == rule_name + + # Update the policy match rule + updated_rule_name = rule_name + "Updated" + _, _, err = client.aiguard.policy_match_rules.update_rule( + rule_id, + policyId=policy_id, + name=updated_rule_name, + enabled=True, + ruleOrder=2, + matchCriteria={ + "llmApplications": [ + { + "applicationId": application_id, + "applicationCredentialsIds": [app_credential_id], + } + ], + "type": "DAS_APPLICATION", + }, + ) + assert err is None, f"Error updating policy match rule: {err}" + + updated_rule, _, err = client.aiguard.policy_match_rules.get_rule(rule_id) + assert err is None, f"Error fetching updated policy match rule: {err}" + assert updated_rule.name == updated_rule_name + + # List policy match rules and ensure the created rule is in the list + rules_list, _, err = client.aiguard.policy_match_rules.list_rules() + assert err is None, f"Error listing policy match rules: {err}" + assert any(rule.id == rule_id for rule in rules_list) + except Exception as exc: + errors.append(f"Policy match rule operation failed: {exc}") + + finally: + # Cleanup runs in reverse dependency order so each resource is removed before + # the resource it references. + if rule_id: + try: + delete_response, _, err = client.aiguard.policy_match_rules.delete_rule(rule_id) + assert err is None, f"Error deleting policy match rule: {err}" + # Since a 204 No Content response returns None, we assert that delete_response is None + assert delete_response is None, f"Expected None for 204 No Content, got {delete_response}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for policy match rule ID {rule_id}: {cleanup_exc}") + + if app_credential_id: + try: + _, _, err = client.aiguard.llm_application_credentials.delete_credential(app_credential_id) + assert err is None, f"Error deleting LLM application credential: {err}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM application credential ID {app_credential_id}: {cleanup_exc}") + + if application_id: + try: + _, _, err = client.aiguard.llm_applications.delete_application(application_id) + assert err is None, f"Error deleting LLM application: {err}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for LLM application ID {application_id}: {cleanup_exc}") + + if policy_id: + try: + _, _, err = client.aiguard.policies.delete_policy(policy_id) + assert err is None, f"Error deleting detection policy: {err}" + except Exception as cleanup_exc: + errors.append(f"Cleanup failed for detection policy ID {policy_id}: {cleanup_exc}") + + assert len(errors) == 0, f"Errors occurred during the policy match rule lifecycle test: {errors}" diff --git a/tests/integration/zaiguard/README.md b/tests/integration/zaiguard/README.md deleted file mode 100644 index 22030cb3..00000000 --- a/tests/integration/zaiguard/README.md +++ /dev/null @@ -1,305 +0,0 @@ -# AIGuard Integration Tests - -## Overview - -This directory contains integration and unit tests for the Zscaler AIGuard SDK client. - -## Test Structure - -``` -tests/integration/zaiguard/ -├── __init__.py # Package initialization -├── conftest.py # Pytest fixtures and mock clients -├── test_policy_detection.py # Integration tests for API endpoints -├── test_zaiguard_unit.py # Unit tests for error handling -├── cassettes/ # VCR cassettes for recorded HTTP responses -│ └── .gitkeep -└── README.md # This file -``` - -## Test Files - -### `conftest.py` - -Provides test fixtures and utilities: - -- **`NameGenerator`** - Generates deterministic test names for VCR playback -- **`MockZGuardClient`** - Mock client for testing with VCR cassettes -- **`zguard_client`** - Pytest fixture providing the mock client -- **`reset_counters_per_test`** - Auto-fixture to reset VCR counters - -### `test_policy_detection.py` (21 integration tests) - -Integration tests for the Policy Detection API: - -1. **Basic Functionality** - - `test_resolve_and_execute_policy_inbound` - Test IN direction scanning - - `test_resolve_and_execute_policy_outbound` - Test OUT direction scanning - - `test_execute_policy_without_policy_id` - Test without policy ID - - `test_execute_policy_with_policy_id` - Test with specific policy ID - -2. **Response Structure** - - `test_response_structure` - Validate response schema - - `test_detector_responses_structure` - Validate detector responses - - `test_throttling_details_structure` - Validate throttling details - -3. **Multiple Requests** - - `test_multiple_requests_sequential` - Test sequential requests - - `test_inbound_and_outbound_directions` - Test both directions - -4. **Edge Cases** - - `test_empty_content` - Test with empty string - - `test_large_content` - Test with large payload (10KB) - - `test_special_characters_in_content` - Test unicode and special chars - - `test_numeric_and_code_content` - Test code snippets - -5. **Validation** - - `test_action_values` - Validate action enum values - - `test_severity_values` - Validate severity enum values - - `test_policy_metadata_in_response` - Validate policy metadata - - `test_invalid_direction` - Test invalid enum value - -6. **Rate Limiting** - - `test_rate_limit_stats` - Test statistics tracking - - `test_reset_rate_limit_stats` - Test stats reset - -7. **Client Behavior** - - `test_client_context_manager` - Test context manager - - `test_transaction_id_format` - Validate UUID format - -### `test_zaiguard_unit.py` (33 unit tests) - -Unit tests for error handling and edge cases: - -1. **PolicyDetectionAPI Error Handling** (6 tests) - - Request creation errors - - Execution errors - - Parsing errors - - For both `execute_policy()` and `resolve_and_execute_policy()` - -2. **Model Creation Tests** (6 tests) - - `test_content_hash_creation` - - `test_detector_response_creation` - - `test_rate_limit_throttling_detail_creation` - - `test_execute_policy_request_creation` - - `test_execute_policy_response_creation` - - `test_resolve_and_execute_response_creation` - -3. **Service Layer Tests** (1 test) - - `test_zguard_service_properties` - Validate service properties - -4. **Legacy Client Tests** (15 tests) - - Initialization tests - - Configuration tests - - Rate limit statistics tests - - Throttling detail handling tests - - Authentication tests - - Custom headers tests - -5. **Rate Limiting Logic Tests** (5 tests) - - Proactive waiting logic - - Thread safety - - Wait time calculations - -## Running Tests - -### Run All AIGuard Tests - -```bash -python -m pytest tests/integration/zaiguard/ -v -``` - -### Run Only Unit Tests - -```bash -python -m pytest tests/integration/zaiguard/test_zaiguard_unit.py -v -``` - -### Run Only Integration Tests - -```bash -python -m pytest tests/integration/zaiguard/test_policy_detection.py -v -``` - -### Run with Coverage - -```bash -python -m pytest tests/integration/zaiguard/ --cov=zscaler.zaiguard --cov-report=html -``` - -### Run Specific Test - -```bash -python -m pytest tests/integration/zaiguard/test_policy_detection.py::TestPolicyDetection::test_resolve_and_execute_policy_inbound -v -``` - -## Test Configuration - -### Environment Variables - -For actual API testing (not using VCR cassettes): - -```bash -export MOCK_TESTS=false # Disable VCR playback -export AIGUARD_API_KEY="your-key" # Your actual API key -export AIGUARD_CLOUD="us1" # Your cloud region -``` - -For VCR playback mode (default): - -```bash -export MOCK_TESTS=true # Use recorded cassettes -# No real API key needed - uses dummy credentials -``` - -## VCR Cassettes - -VCR (Video Cassette Recorder) is used to record and replay HTTP interactions: - -- **Recording**: Run tests with `MOCK_TESTS=false` and real credentials -- **Playback**: Run tests with `MOCK_TESTS=true` (default) using recorded cassettes -- **Cassettes Location**: `tests/integration/zaiguard/cassettes/` - -### Creating New Cassettes - -1. Set environment variables: - ```bash - export MOCK_TESTS=false - export AIGUARD_API_KEY="your-real-api-key" - ``` - -2. Run tests to record: - ```bash - python -m pytest tests/integration/zaiguard/test_policy_detection.py -v - ``` - -3. Cassettes are saved in `cassettes/` directory - -4. Commit cassettes to git for CI/CD - -## Test Coverage - -### API Methods Tested - -- ✅ `resolve_and_execute_policy()` - Fully tested -- ✅ `execute_policy()` - Fully tested - -### Scenarios Covered - -- ✅ Inbound content scanning (IN direction) -- ✅ Outbound content scanning (OUT direction) -- ✅ With and without policy ID -- ✅ Error handling (request, execution, parsing) -- ✅ Response structure validation -- ✅ Detector responses parsing -- ✅ Throttling details parsing -- ✅ Rate limiting logic -- ✅ Multiple requests -- ✅ Edge cases (empty, large, special characters) -- ✅ Enum validation -- ✅ Model creation and serialization -- ✅ Thread safety -- ✅ Context manager behavior - -## Test Statistics - -- **Total Tests**: 54 - - Integration Tests: 21 - - Unit Tests: 33 -- **Test Classes**: 5 -- **Coverage Areas**: - - API Methods: 100% - - Models: 100% - - Rate Limiting: 100% - - Error Handling: 100% - -## Best Practices - -1. **Use VCR for Integration Tests**: Record real API responses for consistent testing -2. **Mock for Unit Tests**: Use mocks to test error handling paths -3. **Deterministic Names**: Use `NameGenerator` for consistent test data -4. **Test Error Paths**: Ensure all error conditions are tested -5. **Validate Schemas**: Check response structures match OpenAPI spec -6. **Thread Safety**: Test concurrent operations where applicable - -## Continuous Integration - -Tests are designed to run in CI/CD pipelines: - -- ✅ **No real API key required** (uses VCR cassettes) -- ✅ **Fast execution** (no real HTTP calls in playback mode) -- ✅ **Deterministic** (same results every run) -- ✅ **No flakiness** (recorded responses are stable) - -## Troubleshooting - -### Tests Failing - -1. **Check VCR mode**: - ```bash - echo $MOCK_TESTS # Should be "true" for cassette playback - ``` - -2. **Re-record cassettes**: - ```bash - rm -rf tests/integration/zaiguard/cassettes/*.yaml - export MOCK_TESTS=false - export AIGUARD_API_KEY="your-key" - python -m pytest tests/integration/zaiguard/test_policy_detection.py -v - ``` - -3. **Check dependencies**: - ```bash - pip install -e ".[dev]" # Install with dev dependencies - ``` - -### Import Errors - -If you get import errors, ensure the SDK is installed in development mode: - -```bash -pip install -e . -``` - -## Adding New Tests - -When adding new API endpoints, follow this pattern: - -1. **Add integration test** in `test_policy_detection.py`: - ```python - @pytest.mark.vcr - def test_new_endpoint(self, zguard_client): - with zguard_client as client: - result, response, error = client.zguard.new_api.method() - assert error is None - assert result is not None - ``` - -2. **Add unit tests** in `test_zaiguard_unit.py`: - ```python - def test_new_endpoint_request_error(self, fs): - mock_executor = Mock() - mock_executor.create_request = Mock(return_value=(None, Exception("Error"))) - api = NewAPI(mock_executor) - result, response, err = api.method() - assert result is None and err is not None - ``` - -3. **Record cassette**: - ```bash - MOCK_TESTS=false AIGUARD_API_KEY="key" python -m pytest tests/integration/zaiguard/test_policy_detection.py::TestPolicyDetection::test_new_endpoint -v - ``` - -## Related Documentation - -- [AIGuard API Documentation](../../../local_dev/AIGuardAPI/README.md) -- [Rate Limiting Guide](../../../local_dev/AIGuardAPI/RATE_LIMITING.md) -- [OpenAPI Specification](../../../local_dev/AIGuardAPI/openapi.yml) - -## Support - -For issues with tests: -1. Check test logs for specific errors -2. Verify VCR cassettes are present -3. Ensure SDK is installed correctly -4. Check environment variables diff --git a/tests/integration/zaiguard/cassettes/TestPolicyDetection.yaml b/tests/integration/zaiguard/cassettes/TestPolicyDetection.yaml deleted file mode 100644 index 4a4818bc..00000000 --- a/tests/integration/zaiguard/cassettes/TestPolicyDetection.yaml +++ /dev/null @@ -1,158 +0,0 @@ -interactions: -- request: - body: '{"content": "What is the capital of France?", "direction": "IN"}' - headers: - Content-Length: - - '64' - Content-Type: - - application/json - User-Agent: - - zscaler-sdk-python/1.9.13 python/3.11.8 Darwin/24.6.0 - method: POST - uri: https://api.us1.zseclipse.net/v1/detection/resolve-and-execute-policy - response: - body: - string: '{"transactionId":"773031f7-77ec-42ce-92d6-925b7ea04a18","statusCode":404,"errorMsg":"Policy - not found","detectorErrorCount":0,"direction":"IN","responseDetectorPolicyIsPresent":false}' - headers: - cache-control: - - no-cache, no-store, max-age=0, must-revalidate - content-type: - - application/json - date: - - Thu, 29 Jan 2026 06:25:10 GMT - expires: - - '0' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000 ; includeSubDomains - - max-age=63072000; includeSubDomains; preload - x-content-type-options: - - nosniff - x-frame-options: - - DENY - x-xss-protection: - - '0' - status: - code: 200 - message: OK -- request: - body: '{"content": "Test content for policy execution", "direction": "IN"}' - headers: - Content-Length: - - '67' - Content-Type: - - application/json - User-Agent: - - zscaler-sdk-python/1.9.13 python/3.11.8 Darwin/24.6.0 - method: POST - uri: https://api.us1.zseclipse.net/v1/detection/execute-policy - response: - body: - string: '{"transactionId":"0b2f989a-cbd2-4337-bb15-da189941d845","statusCode":404,"errorMsg":"Policy - not found","detectorErrorCount":0,"direction":"IN"}' - headers: - cache-control: - - no-cache, no-store, max-age=0, must-revalidate - content-type: - - application/json - date: - - Thu, 29 Jan 2026 06:25:20 GMT - expires: - - '0' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000 ; includeSubDomains - - max-age=63072000; includeSubDomains; preload - transfer-encoding: - - chunked - x-content-type-options: - - nosniff - x-frame-options: - - DENY - x-xss-protection: - - '0' - status: - code: 200 - message: OK -- request: - body: '{"content": "Second test content", "direction": "IN"}' - headers: - Content-Length: - - '53' - Content-Type: - - application/json - User-Agent: - - zscaler-sdk-python/1.9.13 python/3.11.8 Darwin/24.6.0 - method: POST - uri: https://api.us1.zseclipse.net/v1/detection/resolve-and-execute-policy - response: - body: - string: '{"transactionId":"4526eeaa-befd-499f-8c6c-0f57acb2cec9","statusCode":404,"errorMsg":"Policy - not found","detectorErrorCount":0,"direction":"IN","responseDetectorPolicyIsPresent":false}' - headers: - cache-control: - - no-cache, no-store, max-age=0, must-revalidate - content-type: - - application/json - date: - - Thu, 29 Jan 2026 06:25:20 GMT - expires: - - '0' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000 ; includeSubDomains - - max-age=63072000; includeSubDomains; preload - x-content-type-options: - - nosniff - x-frame-options: - - DENY - x-xss-protection: - - '0' - status: - code: 200 - message: OK -- request: - body: '{"content": "Third distinct test content for zaiguard", "direction": "IN"}' - headers: - Content-Length: - - '74' - Content-Type: - - application/json - User-Agent: - - zscaler-sdk-python/1.9.13 python/3.11.8 Darwin/24.6.0 - method: POST - uri: https://api.us1.zseclipse.net/v1/detection/resolve-and-execute-policy - response: - body: - string: '{"transactionId":"e9b465fc-c3d4-4365-a555-1742b0872a24","statusCode":404,"errorMsg":"Policy - not found","detectorErrorCount":0,"direction":"IN","responseDetectorPolicyIsPresent":false}' - headers: - cache-control: - - no-cache, no-store, max-age=0, must-revalidate - content-type: - - application/json - date: - - Thu, 29 Jan 2026 06:29:01 GMT - expires: - - '0' - pragma: - - no-cache - strict-transport-security: - - max-age=31536000 ; includeSubDomains - - max-age=63072000; includeSubDomains; preload - transfer-encoding: - - chunked - x-content-type-options: - - nosniff - x-frame-options: - - DENY - x-xss-protection: - - '0' - status: - code: 200 - message: OK -version: 1 diff --git a/tests/integration/zaiguard/conftest.py b/tests/integration/zaiguard/conftest.py deleted file mode 100644 index e56b999d..00000000 --- a/tests/integration/zaiguard/conftest.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Copyright (c) 2023, Zscaler Inc. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -""" - -import os - -import pytest - -from tests.test_utils import reset_vcr_counters -from zscaler.oneapi_client import LegacyZGuardClient - -PYTEST_MOCK_CLIENT = "pytest_mock_client" - - -@pytest.fixture(autouse=True, scope="function") -def reset_counters_per_test(): - """ - Reset VCR counters before each test function. - - This ensures that generate_random_string() and generate_random_ip() - return the same deterministic values during both recording and playback. - Each test starts with counter at 0, so the same sequence is generated. - """ - reset_vcr_counters() - yield - - -class NameGenerator: - """ - Generates deterministic test names for VCR-based testing. - - Instead of using random names (which break VCR playback), this class - provides consistent, predictable names that work with recorded cassettes. - - Usage: - names = NameGenerator("policy_detection") - name = names.name # "tests-policy-detection" - desc = names.description # "Test Policy Detection" - """ - - def __init__(self, resource_type: str, suffix: str = ""): - """ - Initialize with a resource type identifier. - - Args: - resource_type: A descriptive string for the resource (e.g., "policy_detection") - suffix: Optional suffix for uniqueness (e.g., "1", "alt") - """ - self.resource_type = resource_type.lower().replace("_", "-") - self.suffix = f"-{suffix}" if suffix else "" - - @property - def name(self) -> str: - """Returns the base test name.""" - return f"tests-{self.resource_type}{self.suffix}" - - @property - def description(self) -> str: - """Returns a human-readable description.""" - readable = self.resource_type.replace("-", " ").title() - return f"Test {readable}{self.suffix}" - - @property - def updated_name(self) -> str: - """Returns the name for update operations.""" - return f"tests-{self.resource_type}{self.suffix}-updated" - - @property - def updated_description(self) -> str: - """Returns the description for update operations.""" - readable = self.resource_type.replace("-", " ").title() - return f"Updated Test {readable}{self.suffix}" - - def with_suffix(self, suffix: str) -> "NameGenerator": - """Returns a new generator with an additional suffix.""" - new_suffix = f"{self.suffix.lstrip('-')}-{suffix}" if self.suffix else suffix - return NameGenerator(self.resource_type, new_suffix) - - -@pytest.fixture(scope="function") -def zguard_client(): - return MockZGuardClient() - - -class MockZGuardClient(LegacyZGuardClient): - def __init__(self, config=None): - """ - Initialize the MockZGuardClient with support for environment variables and - optional inline config. - - Args: - config: Optional dictionary containing client configuration (api_key, cloud, etc.). - """ - # If config is not provided, initialize it as an empty dictionary - config = config or {} - - # Check if we're in VCR playback mode (MOCK_TESTS=true means use cassettes) - mock_tests = os.getenv("MOCK_TESTS", "true").strip().lower() != "false" - - # Fetch credentials from environment variables, allowing them to be overridden by the config dictionary - # In playback mode (MOCK_TESTS=true), use dummy credentials if not provided - api_key = config.get("api_key", os.getenv("AIGUARD_API_KEY")) - cloud = config.get("cloud", os.getenv("AIGUARD_CLOUD", "us1")) - - # In VCR playback mode, use dummy credentials if real ones aren't provided - if mock_tests: - api_key = api_key or "dummy_api_key_for_testing" - - # Extract logging configuration or use defaults - logging_config = config.get("logging", {"enabled": False, "verbose": False}) - - # Set up the client config dictionary - client_config = { - "api_key": api_key, - "cloud": cloud, - "timeout": config.get("timeout", 30), - "auto_retry_on_rate_limit": config.get("auto_retry_on_rate_limit", True), - "max_rate_limit_retries": config.get("max_rate_limit_retries", 3), - "logging": {"enabled": logging_config.get("enabled", True), "verbose": logging_config.get("verbose", True)}, - } - - # Initialize the client - super().__init__(client_config) - - def get_rate_limit_stats(self): - """Expose rate limit stats from the legacy client helper.""" - if hasattr(self, "_request_executor") and hasattr(self._request_executor, "zguard_legacy_client"): - return self._request_executor.zguard_legacy_client.get_rate_limit_stats() - return {"total_throttles": 0, "request_count_throttles": 0, "content_size_throttles": 0, "currently_limited": False} - - def reset_rate_limit_stats(self): - """Reset rate limit stats from the legacy client helper.""" - if hasattr(self, "_request_executor") and hasattr(self._request_executor, "zguard_legacy_client"): - self._request_executor.zguard_legacy_client.reset_rate_limit_stats() - - def clear_rate_limits(self): - """Clear rate limits from the legacy client helper.""" - if hasattr(self, "_request_executor") and hasattr(self._request_executor, "zguard_legacy_client"): - self._request_executor.zguard_legacy_client.clear_rate_limits() diff --git a/tests/integration/zaiguard/test_policy_detection.py b/tests/integration/zaiguard/test_policy_detection.py deleted file mode 100644 index b30a76d3..00000000 --- a/tests/integration/zaiguard/test_policy_detection.py +++ /dev/null @@ -1,467 +0,0 @@ -""" -Copyright (c) 2023, Zscaler Inc. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -""" - -import pytest - - -@pytest.mark.vcr -class TestPolicyDetection: - """ - Integration Tests for the AIGuard Policy Detection API. - """ - - def test_resolve_and_execute_policy_inbound(self, zguard_client): - """ - Test resolve_and_execute_policy with inbound direction (IN). - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="What is the capital of France?", direction="IN" - ) - - # Assertions - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - assert result.transaction_id is not None, "Expected transaction_id in response" - assert result.direction == "IN", f"Expected direction 'IN', got: {result.direction}" - - def test_resolve_and_execute_policy_outbound(self, zguard_client): - """ - Test resolve_and_execute_policy with outbound direction (OUT). - Note: API may return direction="IN" even for OUT requests based on current implementation. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="The capital of France is Paris.", direction="OUT" - ) - - # Assertions - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - assert result.transaction_id is not None, "Expected transaction_id in response" - # Note: API returns "IN" for both directions currently - assert result.direction in ["IN", "OUT"], f"Expected valid direction, got: {result.direction}" - - def test_execute_policy_without_policy_id(self, zguard_client): - """ - Test execute_policy without specifying a policy_id. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.execute_policy( - content="Test content for policy execution", direction="IN" - ) - - # Assertions - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - assert result.transaction_id is not None, "Expected transaction_id in response" - assert result.direction == "IN", f"Expected direction 'IN', got: {result.direction}" - - def test_execute_policy_with_policy_id(self, zguard_client): - """ - Test execute_policy with a specific policy_id. - Note: This test may fail if the policy_id doesn't exist. - """ - with zguard_client as client: - # Use a test policy ID - in real tests, this would be a valid policy ID - test_policy_id = 12345 - - result, response, error = client.zguard.policy_detection.execute_policy( - content="Test content with specific policy", direction="IN", policy_id=test_policy_id - ) - - # If policy doesn't exist, we expect an error (403, 404, etc.) - # This is acceptable for this test - if error: - # Check if it's an expected error (policy not found/authorized) - assert ( - "403" in str(error) or "404" in str(error) or "401" in str(error) - ), f"Expected policy-related error, got: {error}" - else: - # If successful, validate response - assert result is not None, "Expected result to be returned" - assert result.transaction_id is not None, "Expected transaction_id in response" - - def test_response_structure(self, zguard_client): - """ - Test that the response structure matches expected schema. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test response structure", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - - # Verify response structure - assert hasattr(result, "transaction_id"), "Missing transaction_id" - assert hasattr(result, "status_code"), "Missing status_code" - assert hasattr(result, "direction"), "Missing direction" - assert hasattr(result, "action"), "Missing action" - assert hasattr(result, "severity"), "Missing severity" - assert hasattr(result, "detector_responses"), "Missing detector_responses" - assert hasattr(result, "throttling_details"), "Missing throttling_details" - - # Verify ResolveAndExecute response includes policy info - assert hasattr(result, "policy_id"), "Missing policy_id" - assert hasattr(result, "policy_name"), "Missing policy_name" - assert hasattr(result, "policy_version"), "Missing policy_version" - - def test_detector_responses_structure(self, zguard_client): - """ - Test that detector_responses are properly parsed. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test detector responses", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - - # detector_responses should be a dictionary - assert isinstance( - result.detector_responses, dict - ), f"Expected detector_responses to be dict, got: {type(result.detector_responses)}" - - # If any detectors responded, validate their structure - for detector_name, detector_response in result.detector_responses.items(): - assert hasattr(detector_response, "triggered"), f"Detector {detector_name} missing 'triggered' field" - assert hasattr(detector_response, "action"), f"Detector {detector_name} missing 'action' field" - assert hasattr(detector_response, "severity"), f"Detector {detector_name} missing 'severity' field" - - def test_throttling_details_structure(self, zguard_client): - """ - Test that throttling_details are properly parsed. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test throttling details", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result to be returned" - - # throttling_details should be a list - assert isinstance( - result.throttling_details, list - ), f"Expected throttling_details to be list, got: {type(result.throttling_details)}" - - # If throttling occurred, validate structure - for throttle in result.throttling_details: - assert hasattr(throttle, "metric"), "Throttle detail missing 'metric' field" - assert hasattr(throttle, "retry_after_millis"), "Throttle detail missing 'retry_after_millis' field" - assert hasattr(throttle, "rlc_id"), "Throttle detail missing 'rlc_id' field" - - # Validate metric values - assert throttle.metric in ["rq", "cs", None], f"Invalid metric value: {throttle.metric}" - - def test_multiple_requests_sequential(self, zguard_client): - """ - Test making multiple sequential requests. - """ - with zguard_client as client: - test_contents = [ - "First test content for zaiguard", - "Second unique test content for zaiguard", - "Third distinct test content for zaiguard", - ] - - transaction_ids = [] - - for content in test_contents: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content=content, direction="IN" - ) - - assert error is None, f"Expected no error for content '{content}', got: {error}" - assert result is not None, f"Expected result for content '{content}'" - assert result.transaction_id is not None, f"Expected transaction_id for content '{content}'" - - transaction_ids.append(result.transaction_id) - - # Verify we got 3 transaction IDs (may not all be unique if API has issues) - assert len(transaction_ids) == 3, f"Expected 3 transaction IDs, got {len(transaction_ids)}" - # If API works correctly, they should be unique - unique_ids = set(transaction_ids) - if len(unique_ids) < 3: - pytest.skip("API returned duplicate transaction IDs - may indicate API issue") - - def test_inbound_and_outbound_directions(self, zguard_client): - """ - Test both IN and OUT directions with different content. - Note: API may return direction="IN" for both currently. - """ - with zguard_client as client: - # Test IN direction (user prompt) - result_in, response_in, error_in = client.zguard.policy_detection.resolve_and_execute_policy( - content="What is machine learning?", direction="IN" - ) - - assert error_in is None, f"Expected no error for IN direction, got: {error_in}" - assert result_in.direction == "IN", f"Expected direction 'IN', got: {result_in.direction}" - - # Test OUT direction (AI response) - result_out, response_out, error_out = client.zguard.policy_detection.resolve_and_execute_policy( - content="Machine learning is a subset of artificial intelligence.", direction="OUT" - ) - - assert error_out is None, f"Expected no error for OUT direction, got: {error_out}" - # API may return "IN" for both directions currently - this is API behavior - assert result_out.direction in ["IN", "OUT"], f"Expected valid direction, got: {result_out.direction}" - - # Verify we got transaction IDs - assert result_in.transaction_id is not None - assert result_out.transaction_id is not None - - def test_empty_content(self, zguard_client): - """ - Test behavior with empty content string. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy(content="", direction="IN") - - # API may handle empty content differently - # Either succeeds or returns validation error - if error: - # If error, it should be a validation error - assert "400" in str(error) or "422" in str(error), f"Expected validation error for empty content, got: {error}" - else: - # If successful, should have transaction ID - assert result.transaction_id is not None, "Expected transaction_id" - - def test_large_content(self, zguard_client): - """ - Test behavior with large content (may trigger content size throttling). - """ - with zguard_client as client: - # Create large content (10KB) - large_content = "A" * 10000 - - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content=large_content, direction="IN" - ) - - if error: - # May get content size error or throttling - assert ( - "400" in str(error) or "413" in str(error) or "429" in str(error) - ), f"Expected size-related error, got: {error}" - else: - assert result is not None, "Expected result" - - # Check if content size throttling occurred - if result.throttling_details: - [t for t in result.throttling_details if t.metric == "cs"] - # It's possible to get cs throttling for large content - # Not asserting this as it depends on API limits - - def test_action_values(self, zguard_client): - """ - Test that action values are valid enum values. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test action values", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result" - - # Action should be one of: ALLOW, BLOCK, DETECT, or None - valid_actions = ["ALLOW", "BLOCK", "DETECT", None] - assert result.action in valid_actions, f"Invalid action value: {result.action}. Expected one of {valid_actions}" - - def test_severity_values(self, zguard_client): - """ - Test that severity values are valid enum values. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test severity values", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result" - - # Severity should be one of: CRITICAL, HIGH, MEDIUM, LOW, INFO, or None - valid_severities = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", None] - assert ( - result.severity in valid_severities - ), f"Invalid severity value: {result.severity}. Expected one of {valid_severities}" - - def test_policy_metadata_in_response(self, zguard_client): - """ - Test that policy metadata is included in resolve_and_execute_policy response. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test policy metadata", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result" - - # ResolveAndExecute should include policy metadata - # Note: These may be None if no policy is configured, which is acceptable - assert hasattr(result, "policy_id"), "Missing policy_id attribute" - assert hasattr(result, "policy_name"), "Missing policy_name attribute" - assert hasattr(result, "policy_version"), "Missing policy_version attribute" - - def test_rate_limit_stats(self, zguard_client): - """ - Test that rate limit statistics are tracked correctly. - """ - with zguard_client as client: - # Get initial stats - initial_stats = client.get_rate_limit_stats() - assert isinstance(initial_stats, dict), "Expected stats to be a dictionary" - assert "total_throttles" in initial_stats, "Missing total_throttles in stats" - assert "request_count_throttles" in initial_stats, "Missing request_count_throttles in stats" - assert "content_size_throttles" in initial_stats, "Missing content_size_throttles in stats" - assert "currently_limited" in initial_stats, "Missing currently_limited in stats" - - # Make a request - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test rate limit stats", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - - # Get stats after request - after_stats = client.get_rate_limit_stats() - - # If throttling occurred, stats should have increased - if result and result.throttling_details and len(result.throttling_details) > 0: - assert ( - after_stats["total_throttles"] > initial_stats["total_throttles"] - ), "Expected total_throttles to increase when throttled" - - def test_reset_rate_limit_stats(self, zguard_client): - """ - Test that rate limit statistics can be reset. - """ - with zguard_client as client: - # Make a request - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test reset stats", direction="IN" - ) - - client.get_rate_limit_stats() - - # Reset stats - client.reset_rate_limit_stats() - - # Get stats after reset - stats_after = client.get_rate_limit_stats() - - # Verify stats were reset - assert stats_after["total_throttles"] == 0, "Expected total_throttles to be 0 after reset" - assert stats_after["request_count_throttles"] == 0, "Expected request_count_throttles to be 0 after reset" - assert stats_after["content_size_throttles"] == 0, "Expected content_size_throttles to be 0 after reset" - - def test_invalid_direction(self, zguard_client): - """ - Test behavior with invalid direction value. - Note: API may accept any string value currently - this tests the behavior. - """ - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test invalid direction", direction="INVALID" - ) - - # API may either reject invalid direction OR accept it and process anyway - # Both behaviors are acceptable - we're just testing the client handles it - if error is not None: - # If error, should be validation error - assert "400" in str(error) or "422" in str(error), f"Expected validation error, got: {error}" - else: - # If no error, API accepted it - just verify we got a result - assert result is not None, "Expected result even with invalid direction" - - def test_special_characters_in_content(self, zguard_client): - """ - Test content scanning with special characters and unicode. - """ - with zguard_client as client: - special_content = "Test with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?`~\n\t éàü 中文 🚀" - - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content=special_content, direction="IN" - ) - - assert error is None, f"Expected no error for special characters, got: {error}" - assert result is not None, "Expected result to be returned" - assert result.transaction_id is not None, "Expected transaction_id in response" - - def test_numeric_and_code_content(self, zguard_client): - """ - Test content scanning with code snippets and numeric data. - """ - with zguard_client as client: - code_content = """ - def example_function(): - api_key = "REDACTED" - return {"result": 42} - """ - - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content=code_content, direction="OUT" - ) - - assert error is None, f"Expected no error for code content, got: {error}" - assert result is not None, "Expected result to be returned" - # API may return "IN" regardless of request direction - assert result.direction in ["IN", "OUT"], f"Expected valid direction, got: {result.direction}" - - def test_client_context_manager(self, zguard_client): - """ - Test that the client works properly with context manager. - """ - # This test validates the __enter__ and __exit__ methods work correctly - with zguard_client as client: - # Make a request inside context - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test context manager", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result" - - # After exiting context, session should be closed - # This is implicitly tested - if __exit__ fails, the test will fail - - def test_transaction_id_format(self, zguard_client): - """ - Test that transaction IDs are in UUID format. - """ - import uuid - - with zguard_client as client: - result, response, error = client.zguard.policy_detection.resolve_and_execute_policy( - content="Test transaction ID format", direction="IN" - ) - - assert error is None, f"Expected no error, got: {error}" - assert result is not None, "Expected result" - assert result.transaction_id is not None, "Expected transaction_id" - - # Verify it's a valid UUID format - try: - uuid.UUID(result.transaction_id) - except (ValueError, AttributeError, TypeError): - pytest.fail(f"transaction_id is not a valid UUID: {result.transaction_id}") diff --git a/tests/integration/zaiguard/test_zaiguard_unit.py b/tests/integration/zaiguard/test_zaiguard_unit.py deleted file mode 100644 index 29bd973e..00000000 --- a/tests/integration/zaiguard/test_zaiguard_unit.py +++ /dev/null @@ -1,613 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2023, Zscaler Inc. -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from unittest.mock import Mock - -import pytest - - -@pytest.fixture -def fs(): - yield - - -class TestPolicyDetectionUnit: - """Unit Tests for the AIGuard Policy Detection API to increase coverage""" - - def test_execute_policy_request_error(self, fs): - """Test execute_policy handles request creation errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_executor.create_request = Mock(return_value=(None, Exception("Request error"))) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - def test_execute_policy_execute_error(self, fs): - """Test execute_policy handles execution errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_request = Mock() - mock_executor.create_request = Mock(return_value=(mock_request, None)) - mock_executor.execute = Mock(return_value=(None, Exception("Execution error"))) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - def test_execute_policy_parsing_error(self, fs): - """Test execute_policy handles parsing errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_request = Mock() - mock_executor.create_request = Mock(return_value=(mock_request, None)) - - mock_response = Mock() - mock_response.get_body = Mock(side_effect=Exception("Parsing error")) - mock_executor.execute = Mock(return_value=(mock_response, None)) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - def test_resolve_and_execute_policy_request_error(self, fs): - """Test resolve_and_execute_policy handles request creation errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_executor.create_request = Mock(return_value=(None, Exception("Request error"))) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.resolve_and_execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - def test_resolve_and_execute_policy_execute_error(self, fs): - """Test resolve_and_execute_policy handles execution errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_request = Mock() - mock_executor.create_request = Mock(return_value=(mock_request, None)) - mock_executor.execute = Mock(return_value=(None, Exception("Execution error"))) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.resolve_and_execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - def test_resolve_and_execute_policy_parsing_error(self, fs): - """Test resolve_and_execute_policy handles parsing errors correctly""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - mock_executor = Mock() - mock_request = Mock() - mock_executor.create_request = Mock(return_value=(mock_request, None)) - - mock_response = Mock() - mock_response.get_body = Mock(side_effect=Exception("Parsing error")) - mock_executor.execute = Mock(return_value=(mock_response, None)) - - policy_api = PolicyDetectionAPI(mock_executor) - result, response, err = policy_api.resolve_and_execute_policy(content="test", direction="IN") - - assert result is None - assert err is not None - - -class TestModelsUnit: - """Unit Tests for AIGuard models""" - - def test_content_hash_creation(self, fs): - """Test ContentHash model creation""" - from zscaler.zaiguard.models.policy_detection import ContentHash - - # Test with data - hash_obj = ContentHash({"hashType": "SHA256", "hashValue": "abc123"}) - assert hash_obj.hash_type == "SHA256" - assert hash_obj.hash_value == "abc123" - - # Test without data - empty_hash = ContentHash() - assert empty_hash.hash_type is None - assert empty_hash.hash_value is None - - # Test request_format - format_dict = hash_obj.request_format() - assert format_dict["hashType"] == "SHA256" - assert format_dict["hashValue"] == "abc123" - - def test_detector_response_creation(self, fs): - """Test DetectorResponse model creation""" - from zscaler.zaiguard.models.policy_detection import DetectorResponse - - # Test with full data - detector = DetectorResponse( - { - "statusCode": 200, - "errorMsg": None, - "triggered": True, - "action": "BLOCK", - "latency": 150, - "deviceType": "test", - "details": {"key": "value"}, - "severity": "HIGH", - "contentHash": {"hashType": "SHA256", "hashValue": "xyz"}, - } - ) - - assert detector.status_code == 200 - assert detector.triggered is True - assert detector.action == "BLOCK" - assert detector.severity == "HIGH" - assert detector.content_hash is not None - assert detector.content_hash.hash_type == "SHA256" - - # Test without data - empty_detector = DetectorResponse() - assert empty_detector.triggered is None - assert empty_detector.action is None - - def test_rate_limit_throttling_detail_creation(self, fs): - """Test RateLimitThrottlingDetail model creation""" - from zscaler.zaiguard.models.policy_detection import RateLimitThrottlingDetail - - # Test with data - throttle = RateLimitThrottlingDetail({"rlcId": 12345, "metric": "rq", "retryAfterMillis": 5000}) - - assert throttle.rlc_id == 12345 - assert throttle.metric == "rq" - assert throttle.retry_after_millis == 5000 - - # Test request_format - format_dict = throttle.request_format() - assert format_dict["rlcId"] == 12345 - assert format_dict["metric"] == "rq" - assert format_dict["retryAfterMillis"] == 5000 - - def test_execute_policy_request_creation(self, fs): - """Test ExecuteDetectionsPolicyRequest model creation""" - from zscaler.zaiguard.models.policy_detection import ExecuteDetectionsPolicyRequest - - # Test with full data - request_obj = ExecuteDetectionsPolicyRequest( - {"transactionId": "abc-123", "content": "test content", "direction": "IN", "policyId": 12345} - ) - - assert request_obj.transaction_id == "abc-123" - assert request_obj.content == "test content" - assert request_obj.direction == "IN" - assert request_obj.policy_id == 12345 - - # Test request_format - format_dict = request_obj.request_format() - assert format_dict["transactionId"] == "abc-123" - assert format_dict["content"] == "test content" - - def test_execute_policy_response_creation(self, fs): - """Test ExecuteDetectionsPolicyResponse model creation""" - from zscaler.zaiguard.models.policy_detection import ExecuteDetectionsPolicyResponse - - # Test with nested objects - response_obj = ExecuteDetectionsPolicyResponse( - { - "transactionId": "xyz-789", - "statusCode": 200, - "action": "ALLOW", - "severity": "LOW", - "direction": "OUT", - "detectorResponses": {"detector1": {"statusCode": 200, "triggered": False, "action": "ALLOW"}}, - "throttlingDetails": [{"rlcId": 999, "metric": "rq", "retryAfterMillis": 1000}], - } - ) - - assert response_obj.transaction_id == "xyz-789" - assert response_obj.action == "ALLOW" - assert "detector1" in response_obj.detector_responses - assert len(response_obj.throttling_details) == 1 - assert response_obj.throttling_details[0].metric == "rq" - - def test_resolve_and_execute_response_creation(self, fs): - """Test ResolveAndExecuteDetectionsPolicyResponse model creation""" - from zscaler.zaiguard.models.policy_detection import ResolveAndExecuteDetectionsPolicyResponse - - # Test with policy metadata - response_obj = ResolveAndExecuteDetectionsPolicyResponse( - { - "transactionId": "policy-123", - "statusCode": 200, - "action": "DETECT", - "direction": "IN", - "policyId": 555, - "policyName": "Test Policy", - "policyVersion": "1.0", - "detectorResponses": {}, - "throttlingDetails": [], - } - ) - - assert response_obj.transaction_id == "policy-123" - assert response_obj.policy_id == 555 - assert response_obj.policy_name == "Test Policy" - assert response_obj.policy_version == "1.0" - - # Test request_format - format_dict = response_obj.request_format() - assert format_dict["policyId"] == 555 - assert format_dict["policyName"] == "Test Policy" - - -class TestZGuardServiceUnit: - """Unit Tests for the ZGuard Service to increase coverage""" - - def test_zguard_service_properties(self, fs): - """Test ZGuardService property accessors""" - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - from zscaler.zaiguard.zaiguard_service import ZGuardService - - mock_executor = Mock() - - service = ZGuardService(mock_executor) - - # Test that policy_detection property returns correct type - assert isinstance(service.policy_detection, PolicyDetectionAPI) - - # Verify it uses the correct executor - assert service.policy_detection._request_executor == mock_executor - - -class TestLegacyClientUnit: - """Unit Tests for the Legacy AIGuard Client""" - - def test_legacy_client_initialization(self, fs): - """Test LegacyZGuardClientHelper initialization""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - # Test with API key - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - assert client.api_key == "test_api_key" - assert client.env_cloud == "us1" - assert client.url == "https://api.us1.zseclipse.net" - assert client.auto_retry_on_rate_limit is True - assert client.max_rate_limit_retries == 3 - - def test_legacy_client_missing_api_key(self, fs): - """Test that missing API key raises ValueError""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - with pytest.raises(ValueError, match="API key is required"): - LegacyZGuardClientHelper(cloud="us1") - - def test_legacy_client_custom_url(self, fs): - """Test LegacyZGuardClientHelper with custom override URL""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - custom_url = "https://custom.api.example.com" - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1", override_url=custom_url) - - assert client.url == custom_url - - def test_legacy_client_rate_limit_config(self, fs): - """Test LegacyZGuardClientHelper rate limit configuration""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper( - api_key="test_api_key", cloud="us1", auto_retry_on_rate_limit=False, max_rate_limit_retries=5 - ) - - assert client.auto_retry_on_rate_limit is False - assert client.max_rate_limit_retries == 5 - - def test_get_base_url(self, fs): - """Test get_base_url method""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - base_url = client.get_base_url() - assert base_url == "https://api.us1.zseclipse.net" - - def test_get_rate_limit_stats(self, fs): - """Test get_rate_limit_stats method""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - stats = client.get_rate_limit_stats() - - assert isinstance(stats, dict) - assert "total_throttles" in stats - assert "request_count_throttles" in stats - assert "content_size_throttles" in stats - assert "currently_limited" in stats - - # Initial stats should be zero - assert stats["total_throttles"] == 0 - assert stats["request_count_throttles"] == 0 - assert stats["content_size_throttles"] == 0 - assert stats["currently_limited"] is False - - def test_reset_rate_limit_stats(self, fs): - """Test reset_rate_limit_stats method""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Manually increment counters - client._total_throttles = 5 - client._rq_throttles = 3 - client._cs_throttles = 2 - - # Reset stats - client.reset_rate_limit_stats() - - # Verify reset - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 0 - assert stats["request_count_throttles"] == 0 - assert stats["content_size_throttles"] == 0 - - def test_clear_rate_limits(self, fs): - """Test clear_rate_limits method""" - import time - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Set rate limit wait times - future_time = time.time() + 10 - client._request_count_wait_until = future_time - client._content_size_wait_until = future_time - - # Should be currently limited - stats_before = client.get_rate_limit_stats() - assert stats_before["currently_limited"] is True - - # Clear limits - client.clear_rate_limits() - - # Should no longer be limited - stats_after = client.get_rate_limit_stats() - assert stats_after["currently_limited"] is False - assert client._request_count_wait_until == 0 - assert client._content_size_wait_until == 0 - - def test_handle_throttling_details_rq_metric(self, fs): - """Test _handle_throttling_details with rq (request count) metric""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - from zscaler.zaiguard.models.policy_detection import RateLimitThrottlingDetail - - client = LegacyZGuardClientHelper( - api_key="test_api_key", cloud="us1", auto_retry_on_rate_limit=False # Disable auto-retry for testing - ) - - throttle = RateLimitThrottlingDetail({"rlcId": 123, "metric": "rq", "retryAfterMillis": 5000}) - - # Handle throttling - was_throttled = client._handle_throttling_details([throttle]) - - assert was_throttled is True - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 1 - assert stats["request_count_throttles"] == 1 - assert stats["content_size_throttles"] == 0 - - def test_handle_throttling_details_cs_metric(self, fs): - """Test _handle_throttling_details with cs (content size) metric""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - from zscaler.zaiguard.models.policy_detection import RateLimitThrottlingDetail - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1", auto_retry_on_rate_limit=False) - - throttle = RateLimitThrottlingDetail({"rlcId": 456, "metric": "cs", "retryAfterMillis": 3000}) - - # Handle throttling - was_throttled = client._handle_throttling_details([throttle]) - - assert was_throttled is True - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 1 - assert stats["request_count_throttles"] == 0 - assert stats["content_size_throttles"] == 1 - - def test_handle_throttling_details_multiple(self, fs): - """Test _handle_throttling_details with multiple throttles""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - from zscaler.zaiguard.models.policy_detection import RateLimitThrottlingDetail - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1", auto_retry_on_rate_limit=False) - - throttles = [ - RateLimitThrottlingDetail({"rlcId": 111, "metric": "rq", "retryAfterMillis": 5000}), - RateLimitThrottlingDetail({"rlcId": 222, "metric": "cs", "retryAfterMillis": 3000}), - ] - - # Handle throttling - was_throttled = client._handle_throttling_details(throttles) - - assert was_throttled is True - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 2 - assert stats["request_count_throttles"] == 1 - assert stats["content_size_throttles"] == 1 - - def test_handle_throttling_details_empty_list(self, fs): - """Test _handle_throttling_details with empty list""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Handle empty throttling list - was_throttled = client._handle_throttling_details([]) - - assert was_throttled is False - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 0 - - def test_set_auth_header(self, fs): - """Test set_auth_header method""" - import requests - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key_12345", cloud="us1") - - # Create a test request - req = requests.Request(method="POST", url="https://api.us1.zseclipse.net/v1/test") - prepared = req.prepare() - - # Set auth header - prepared = client.set_auth_header(prepared) - - # Verify Authorization header - assert "Authorization" in prepared.headers - assert prepared.headers["Authorization"] == "Bearer test_api_key_12345" - - # Verify other headers - assert "Content-Type" in prepared.headers or prepared.body is None - assert "User-Agent" in prepared.headers - - def test_policy_detection_property(self, fs): - """Test policy_detection property accessor""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Access policy_detection property - policy_api = client.policy_detection - - # Verify it returns the correct type - assert isinstance(policy_api, PolicyDetectionAPI) - - def test_custom_headers_methods(self, fs): - """Test custom headers management methods""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Test that methods exist and can be called - # (actual functionality tested through request_executor) - try: - client.set_custom_headers({"X-Custom": "value"}) - client.get_custom_headers() - client.get_default_headers() - client.clear_custom_headers() - except AttributeError: - pytest.fail("Custom header methods should be available") - - -class TestRateLimitingLogic: - """Unit Tests for rate limiting logic""" - - def test_should_wait_before_request_no_limit(self, fs): - """Test _should_wait_before_request returns None when no limits active""" - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - wait_time = client._should_wait_before_request() - assert wait_time is None - - def test_should_wait_before_request_with_limit(self, fs): - """Test _should_wait_before_request returns wait time when limited""" - import time - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # Set a rate limit 2 seconds in the future - client._request_count_wait_until = time.time() + 2 - - wait_time = client._should_wait_before_request() - assert wait_time is not None - assert wait_time > 0 - assert wait_time <= 2.1 # Allow small margin for execution time - - def test_wait_if_rate_limited(self, fs): - """Test _wait_if_rate_limited method""" - import time - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - # No rate limit - should not wait - waited = client._wait_if_rate_limited() - assert waited is False - - # Set a very short rate limit (0.1 seconds) - client._request_count_wait_until = time.time() + 0.1 - - start_time = time.time() - waited = client._wait_if_rate_limited() - elapsed = time.time() - start_time - - assert waited is True - assert elapsed >= 0.09 # Should have waited at least 0.09 seconds - - def test_thread_safety(self, fs): - """Test that rate limiting operations are thread-safe""" - import threading - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - from zscaler.zaiguard.models.policy_detection import RateLimitThrottlingDetail - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1", auto_retry_on_rate_limit=False) - - def increment_throttles(): - throttle = RateLimitThrottlingDetail({"metric": "rq", "retryAfterMillis": 100}) - client._handle_throttling_details([throttle]) - - # Run multiple threads incrementing throttles - threads = [threading.Thread(target=increment_throttles) for _ in range(10)] - for t in threads: - t.start() - for t in threads: - t.join() - - # Verify all increments were counted (thread-safe) - stats = client.get_rate_limit_stats() - assert stats["total_throttles"] == 10 - - def test_get_jsessionid(self, fs): - """Test get_jsessionid returns None for AIGuard""" - import requests - - from zscaler.zaiguard.legacy import LegacyZGuardClientHelper - - client = LegacyZGuardClientHelper(api_key="test_api_key", cloud="us1") - - req = requests.Request(method="GET", url="https://api.us1.zseclipse.net/v1/test") - prepared = req.prepare() - - session_id = client.get_jsessionid(prepared) - assert session_id is None, "AIGuard should not use JSESSIONID" diff --git a/zscaler/__init__.py b/zscaler/__init__.py index ff073e71..9e8f4ee3 100644 --- a/zscaler/__init__.py +++ b/zscaler/__init__.py @@ -29,7 +29,7 @@ __contributors__ = [ "William Guilherme", ] -__version__ = "1.9.38" +__version__ = "1.9.39" from zscaler.oneapi_client import Client as ZscalerClient # noqa diff --git a/zscaler/aiguard/__init__.py b/zscaler/aiguard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/zscaler/aiguard/aiguard_service.py b/zscaler/aiguard/aiguard_service.py new file mode 100644 index 00000000..86da04d8 --- /dev/null +++ b/zscaler/aiguard/aiguard_service.py @@ -0,0 +1,84 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from zscaler.aiguard.llm_application_credentials import LLMApplicationCredentialsAPI +from zscaler.aiguard.llm_applications import LLMApplicationsAPI +from zscaler.aiguard.llm_provider_credentials import LLMProviderCredentialsAPI +from zscaler.aiguard.llm_providers import LLMProvidersAPI +from zscaler.aiguard.policies import PoliciesAPI +from zscaler.aiguard.policy_match_rules import PolicyMatchRulesAPI +from zscaler.request_executor import RequestExecutor + + +class AIGuardService: + """ + AI Guard Service client, exposing the AI Guard configuration APIs over OneAPI. + + Policy detection (``/v1/detection/*``) is **not** available through OneAPI and is + therefore not exposed here -- use ``LegacyAIGuardClient(...).aiguard.policy_detection``. + """ + + def __init__(self, request_executor: RequestExecutor) -> None: + self._request_executor = request_executor + + @property + def policies(self) -> PoliciesAPI: + """ + The interface object for the :ref:`AI Guard Detection Policies interface `. + + """ + return PoliciesAPI(self._request_executor) + + @property + def policy_match_rules(self) -> PolicyMatchRulesAPI: + """ + The interface object for the :ref:`AI Guard Policy Match Rules interface `. + + """ + return PolicyMatchRulesAPI(self._request_executor) + + @property + def llm_providers(self) -> LLMProvidersAPI: + """ + The interface object for the :ref:`AI Guard LLM Providers interface `. + + """ + return LLMProvidersAPI(self._request_executor) + + @property + def llm_provider_credentials(self) -> LLMProviderCredentialsAPI: + """ + The interface object for the :ref:`AI Guard LLM Provider Credentials interface `. + + """ + return LLMProviderCredentialsAPI(self._request_executor) + + @property + def llm_applications(self) -> LLMApplicationsAPI: + """ + The interface object for the :ref:`AI Guard LLM Applications interface `. + + """ + return LLMApplicationsAPI(self._request_executor) + + @property + def llm_application_credentials(self) -> LLMApplicationCredentialsAPI: + """ + The interface object for the + :ref:`AI Guard LLM Application Credentials interface `. + + """ + return LLMApplicationCredentialsAPI(self._request_executor) diff --git a/zscaler/zaiguard/legacy.py b/zscaler/aiguard/legacy.py similarity index 98% rename from zscaler/zaiguard/legacy.py rename to zscaler/aiguard/legacy.py index a6202e85..c252be2e 100644 --- a/zscaler/zaiguard/legacy.py +++ b/zscaler/aiguard/legacy.py @@ -35,7 +35,7 @@ # Import all AIGuard API classes for type hints only (to avoid circular imports) if TYPE_CHECKING: - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI + from zscaler.aiguard.policy_detection import PolicyDetectionAPI class LegacyZGuardClientHelper: @@ -146,7 +146,7 @@ def __init__( config=config, cache=self.cache, http_client=None, - zguard_legacy_client=self, + aiguard_legacy_client=self, ) self._session = None @@ -159,7 +159,7 @@ def policy_detection(self) -> "PolicyDetectionAPI": Returns: PolicyDetectionAPI: Interface for policy detection operations """ - from zscaler.zaiguard.policy_detection import PolicyDetectionAPI + from zscaler.aiguard.policy_detection import PolicyDetectionAPI return PolicyDetectionAPI(self.request_executor) diff --git a/zscaler/aiguard/llm_application_credentials.py b/zscaler/aiguard/llm_application_credentials.py new file mode 100644 index 00000000..3d21fba7 --- /dev/null +++ b/zscaler/aiguard/llm_application_credentials.py @@ -0,0 +1,423 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.llm_application_credentials import LlmApplicationCredentials +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class LLMApplicationCredentialsAPI(APIClient): + """ + A Client object for the AI Guard LLM Application Credentials resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_credentials(self, query_params: Optional[dict] = None) -> APIResult[List[LlmApplicationCredentials]]: + """ + Lists the LLM application credentials configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of LlmApplicationCredentials instances, Response, error) + + Examples: + List LLM application credentials: + + >>> credential_list, _, error = client.aiguard.llm_application_credentials.list_credentials() + >>> if error: + ... print(f"Error listing LLM application credentials: {error}") + ... return + ... print(f"Total LLM application credentials found: {len(credential_list)}") + ... for credential in credential_list: + ... print(credential.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(LlmApplicationCredentials(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_credential(self, credential_id: int) -> APIResult[LlmApplicationCredentials]: + """ + Fetches a specific LLM application credential by ID. + + Args: + credential_id (int): The unique identifier for the LLM application credential. + + Returns: + tuple: A tuple containing (LlmApplicationCredentials instance, Response, error). + + Examples: + Print a specific LLM application credential: + + >>> fetched_credential, _, error = client.aiguard.llm_application_credentials.get_credential(1013) + >>> if error: + ... print(f"Error fetching LLM application credential by ID: {error}") + ... return + ... print(f"Fetched LLM application credential by ID: {fetched_credential.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials/{credential_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplicationCredentials) + if error: + return (None, response, error) + + try: + result = LlmApplicationCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_credential_by_name(self, name: str) -> APIResult[LlmApplicationCredentials]: + """ + Fetches a specific LLM application credential by name. + + Args: + name (str): The name of the LLM application credential. + + Returns: + tuple: A tuple containing (LlmApplicationCredentials instance, Response, error). + + Examples: + Print a specific LLM application credential by name: + + >>> fetched_credential, _, error = client.aiguard.llm_application_credentials.get_credential_by_name('Credential01') + >>> if error: + ... print(f"Error fetching LLM application credential by name: {error}") + ... return + ... print(f"Fetched LLM application credential by name: {fetched_credential.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplicationCredentials) + if error: + return (None, response, error) + + try: + result = LlmApplicationCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + # NOTE: The referential-check endpoint is currently returning HTTP 404 for every + # resource type, including via Postman with a known-good id. The method is + # commented out until the API supports it; re-enable once the endpoint is live. + # def referential_check(self, credential_id: int) -> APIResult[dict]: + # """ + # Performs a referential check for the specified LLM application credential, returning the resources that reference it. + # + # Args: + # credential_id (int): The unique identifier for the LLM application credential. + # + # Returns: + # tuple: A tuple containing (the raw response value, Response, error). + # + # Examples: + # >>> result, _, error = client.aiguard.llm_application_credentials.referential_check(1013) + # >>> if error: + # ... print(f"Error calling referential_check: {error}") + # ... return + # ... print(f"Result: {result}") + # """ + # http_method = "get".upper() + # api_url = format_url(f""" + # {self._aiguard_base_endpoint} + # /llm-application-credentials/{credential_id}/referential-check + # """) + # + # body = {} + # headers = {} + # + # request, error = self._request_executor.create_request(http_method, api_url, body, headers) + # + # if error: + # return (None, None, error) + # + # response, error = self._request_executor.execute(request) + # if error: + # return (None, response, error) + # + # try: + # result = self.form_response_body(response.get_body()) + # except Exception as error: + # return (None, response, error) + # return (result, response, None) + + def add_credential(self, **kwargs) -> APIResult[LlmApplicationCredentials]: + """ + Creates a new LLM application credential. + + Args: + name (str): The name of the LLM application credential. + **kwargs: Optional keyword args. + + Keyword Args: + application_id (str): The application id for this LLM application credential. + provider_id (str): The provider id for this LLM application credential. + provider_credentials_id (str): The provider credentials id for this LLM application credential. + mode (str): The mode for this LLM application credential. + + Returns: + tuple: A tuple containing the newly added LlmApplicationCredentials instance, response, and error. + + Examples: + Add a new LLM application credential. ``applicationId``, ``providerId`` and + ``providerCredentialsId`` must all reference existing resources: + + >>> application, _, error = client.aiguard.llm_applications.get_application_by_name("App01") + >>> provider, _, error = client.aiguard.llm_providers.get_provider_by_name("Default Anthropic Provider") + >>> added_credential, _, error = client.aiguard.llm_application_credentials.add_credential( + ... applicationId=application.id, + ... providerId=provider.id, + ... providerCredentialsId=739, + ... name="IDB01", + ... mode="PROXY", + ... ) + >>> if error: + ... print(f"Error adding LLM application credential: {error}") + ... return + ... print(f"LLM application credential added successfully: {added_credential.as_dict()}") + + Note: + The create response returns a generated ``key`` -- treat it as a secret and + do not log it. + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplicationCredentials) + if error: + return (None, response, error) + + try: + result = LlmApplicationCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def regenerate_credential(self, credential_id: int) -> APIResult[LlmApplicationCredentials]: + """ + Regenerates the key material for the specified LLM application credential. + + Args: + credential_id (int): The unique identifier for the LLM application credential. + + Returns: + tuple: A tuple containing the resulting LlmApplicationCredentials instance, response, and error. + The raw response body is available via ``response.get_body()``. + + Examples: + >>> result, resp, error = client.aiguard.llm_application_credentials.regenerate_credential(1013) + >>> if error: + ... print(f"Error calling regenerate_credential: {error}") + ... return + ... print(f"Action completed successfully: {result.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials/{credential_id}/regenerate + """) + + body = {} + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplicationCredentials) + if error: + return (None, response, error) + + try: + result = LlmApplicationCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_credential(self, credential_id: int, **kwargs) -> APIResult[LlmApplicationCredentials]: + """ + Updates information for the specified LLM application credential. + + Args: + credential_id (int): The unique identifier for the LLM application credential. + + Keyword Args: + name (str): The name of the LLM application credential. + application_id (str): The application id for this LLM application credential. + provider_id (str): The provider id for this LLM application credential. + provider_credentials_id (str): The provider credentials id for this LLM application credential. + mode (str): The mode for this LLM application credential. + + Returns: + tuple: A tuple containing the updated LlmApplicationCredentials instance, response, and error. + + Examples: + Update an existing LLM application credential: + + >>> updated_credential, _, error = client.aiguard.llm_application_credentials.update_credential( + ... credential_id=1075, + ... applicationId=647, + ... providerId=6099, + ... providerCredentialsId=739, + ... name="IDB01_Updated", + ... mode="PROXY", + ... ) + >>> if error: + ... print(f"Error updating LLM application credential: {error}") + ... return + ... print(f"LLM application credential updated successfully: {updated_credential.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials/{credential_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplicationCredentials) + if error: + return (None, response, error) + + try: + result = LlmApplicationCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_credential(self, credential_id: int) -> APIResult[None]: + """ + Deletes the specified LLM application credential. + + Args: + credential_id (int): The unique identifier for the LLM application credential. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a LLM application credential: + + >>> _, _, error = client.aiguard.llm_application_credentials.delete_credential(1013) + >>> if error: + ... print(f"Error deleting LLM application credential: {error}") + ... return + ... print(f"Llm application credential deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-application-credentials/{credential_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/aiguard/llm_applications.py b/zscaler/aiguard/llm_applications.py new file mode 100644 index 00000000..09cdbabe --- /dev/null +++ b/zscaler/aiguard/llm_applications.py @@ -0,0 +1,374 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.llm_applications import LlmApplications +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class LLMApplicationsAPI(APIClient): + """ + A Client object for the AI Guard LLM Applications resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_applications(self, query_params: Optional[dict] = None) -> APIResult[List[LlmApplications]]: + """ + Lists the LLM applications configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of LlmApplications instances, Response, error) + + Examples: + List LLM applications: + + >>> application_list, _, error = client.aiguard.llm_applications.list_applications() + >>> if error: + ... print(f"Error listing LLM applications: {error}") + ... return + ... print(f"Total LLM applications found: {len(application_list)}") + ... for application in application_list: + ... print(application.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(LlmApplications(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_application(self, application_id: int) -> APIResult[LlmApplications]: + """ + Fetches a specific LLM application by ID. + + Args: + application_id (int): The unique identifier for the LLM application. + + Returns: + tuple: A tuple containing (LlmApplications instance, Response, error). + + Examples: + Print a specific LLM application: + + >>> fetched_application, _, error = client.aiguard.llm_applications.get_application(1013) + >>> if error: + ... print(f"Error fetching LLM application by ID: {error}") + ... return + ... print(f"Fetched LLM application by ID: {fetched_application.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications/{application_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplications) + if error: + return (None, response, error) + + try: + result = LlmApplications(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_application_by_name(self, name: str) -> APIResult[LlmApplications]: + """ + Fetches a specific LLM application by name. + + Args: + name (str): The name of the LLM application. + + Returns: + tuple: A tuple containing (LlmApplications instance, Response, error). + + Examples: + Print a specific LLM application by name: + + >>> fetched_application, _, error = client.aiguard.llm_applications.get_application_by_name('Application01') + >>> if error: + ... print(f"Error fetching LLM application by name: {error}") + ... return + ... print(f"Fetched LLM application by name: {fetched_application.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplications) + if error: + return (None, response, error) + + try: + result = LlmApplications(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + # NOTE: The referential-check endpoint is currently returning HTTP 404 for every + # resource type, including via Postman with a known-good id. The method is + # commented out until the API supports it; re-enable once the endpoint is live. + # def referential_check(self, application_id: int) -> APIResult[dict]: + # """ + # Performs a referential check for the specified LLM application, returning the resources that reference it. + # + # Args: + # application_id (int): The unique identifier for the LLM application. + # + # Returns: + # tuple: A tuple containing (the raw response value, Response, error). + # + # Examples: + # >>> result, _, error = client.aiguard.llm_applications.referential_check(1013) + # >>> if error: + # ... print(f"Error calling referential_check: {error}") + # ... return + # ... print(f"Result: {result}") + # """ + # http_method = "get".upper() + # api_url = format_url(f""" + # {self._aiguard_base_endpoint} + # /llm-applications/{application_id}/referential-check + # """) + # + # body = {} + # headers = {} + # + # request, error = self._request_executor.create_request(http_method, api_url, body, headers) + # + # if error: + # return (None, None, error) + # + # response, error = self._request_executor.execute(request) + # if error: + # return (None, response, error) + # + # try: + # result = self.form_response_body(response.get_body()) + # except Exception as error: + # return (None, response, error) + # return (result, response, None) + + def add_application(self, **kwargs) -> APIResult[LlmApplications]: + """ + Creates a new LLM application. + + Args: + name (str): The name of the LLM application. + **kwargs: Optional keyword args. + + Keyword Args: + owner_email (str): The owner email for this LLM application. + application_settings (str): The application settings for this LLM application. + + Returns: + tuple: A tuple containing the newly added LlmApplications instance, response, and error. + + Examples: + Add a new LLM application: + + >>> added_application, _, error = client.aiguard.llm_applications.add_application( + ... name="App10", + ... ownerEmail="jdoe@acme.com", + ... applicationSettings={ + ... "includeEventContents": True, + ... "encryptEventContents": False, + ... }, + ... ) + >>> if error: + ... print(f"Error adding LLM application: {error}") + ... return + ... print(f"LLM application added successfully: {added_application.as_dict()}") + + Note: + Setting ``encryptEventContents`` to ``True`` requires a customer-managed key + (CMK) configured in the tenant settings; the API rejects the request + otherwise. + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplications) + if error: + return (None, response, error) + + try: + result = LlmApplications(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_application(self, application_id: int, **kwargs) -> APIResult[LlmApplications]: + """ + Updates information for the specified LLM application. + + Args: + application_id (int): The unique identifier for the LLM application. + + Keyword Args: + name (str): The name of the LLM application. + owner_email (str): The owner email for this LLM application. + application_settings (str): The application settings for this LLM application. + + Returns: + tuple: A tuple containing the updated LlmApplications instance, response, and error. + + Examples: + Update an existing LLM application: + + >>> updated_application, _, error = client.aiguard.llm_applications.update_application( + ... application_id=1575, + ... name="App10_Updated", + ... ownerEmail="jdoe@acme.com", + ... applicationSettings={ + ... "includeEventContents": True, + ... "encryptEventContents": False, + ... }, + ... ) + >>> if error: + ... print(f"Error updating LLM application: {error}") + ... return + ... print(f"LLM application updated successfully: {updated_application.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications/{application_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmApplications) + if error: + return (None, response, error) + + try: + result = LlmApplications(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_application(self, application_id: int) -> APIResult[None]: + """ + Deletes the specified LLM application. + + Args: + application_id (int): The unique identifier for the LLM application. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a LLM application: + + >>> _, _, error = client.aiguard.llm_applications.delete_application(1013) + >>> if error: + ... print(f"Error deleting LLM application: {error}") + ... return + ... print(f"Llm application deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-applications/{application_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/aiguard/llm_provider_credentials.py b/zscaler/aiguard/llm_provider_credentials.py new file mode 100644 index 00000000..2ccfd6c1 --- /dev/null +++ b/zscaler/aiguard/llm_provider_credentials.py @@ -0,0 +1,373 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.llm_provider_credentials import LlmProviderCredentials +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class LLMProviderCredentialsAPI(APIClient): + """ + A Client object for the AI Guard LLM Provider Credentials resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_credentials(self, query_params: Optional[dict] = None) -> APIResult[List[LlmProviderCredentials]]: + """ + Lists the LLM provider credentials configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of LlmProviderCredentials instances, Response, error) + + Examples: + List LLM provider credentials: + + >>> credential_list, _, error = client.aiguard.llm_provider_credentials.list_credentials() + >>> if error: + ... print(f"Error listing LLM provider credentials: {error}") + ... return + ... print(f"Total LLM provider credentials found: {len(credential_list)}") + ... for credential in credential_list: + ... print(credential.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(LlmProviderCredentials(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_credential(self, credential_id: int) -> APIResult[LlmProviderCredentials]: + """ + Fetches a specific LLM provider credential by ID. + + Args: + credential_id (int): The unique identifier for the LLM provider credential. + + Returns: + tuple: A tuple containing (LlmProviderCredentials instance, Response, error). + + Examples: + Print a specific LLM provider credential: + + >>> fetched_credential, _, error = client.aiguard.llm_provider_credentials.get_credential(1013) + >>> if error: + ... print(f"Error fetching LLM provider credential by ID: {error}") + ... return + ... print(f"Fetched LLM provider credential by ID: {fetched_credential.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials/{credential_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviderCredentials) + if error: + return (None, response, error) + + try: + result = LlmProviderCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_credential_by_name(self, name: str) -> APIResult[LlmProviderCredentials]: + """ + Fetches a specific LLM provider credential by name. + + Args: + name (str): The name of the LLM provider credential. + + Returns: + tuple: A tuple containing (LlmProviderCredentials instance, Response, error). + + Examples: + Print a specific LLM provider credential by name: + + >>> fetched_credential, _, error = client.aiguard.llm_provider_credentials.get_credential_by_name('Credential01') + >>> if error: + ... print(f"Error fetching LLM provider credential by name: {error}") + ... return + ... print(f"Fetched LLM provider credential by name: {fetched_credential.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviderCredentials) + if error: + return (None, response, error) + + try: + result = LlmProviderCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + # NOTE: The referential-check endpoint is currently returning HTTP 404 for every + # resource type, including via Postman with a known-good id. The method is + # commented out until the API supports it; re-enable once the endpoint is live. + # def referential_check(self, credential_id: int) -> APIResult[dict]: + # """ + # Performs a referential check for the specified LLM provider credential, returning the resources that reference it. + # + # Args: + # credential_id (int): The unique identifier for the LLM provider credential. + # + # Returns: + # tuple: A tuple containing (the raw response value, Response, error). + # + # Examples: + # >>> result, _, error = client.aiguard.llm_provider_credentials.referential_check(1013) + # >>> if error: + # ... print(f"Error calling referential_check: {error}") + # ... return + # ... print(f"Result: {result}") + # """ + # http_method = "get".upper() + # api_url = format_url(f""" + # {self._aiguard_base_endpoint} + # /llm-provider-credentials/{credential_id}/referential-check + # """) + # + # body = {} + # headers = {} + # + # request, error = self._request_executor.create_request(http_method, api_url, body, headers) + # + # if error: + # return (None, None, error) + # + # response, error = self._request_executor.execute(request) + # if error: + # return (None, response, error) + # + # try: + # result = self.form_response_body(response.get_body()) + # except Exception as error: + # return (None, response, error) + # return (result, response, None) + + def add_credential(self, **kwargs) -> APIResult[LlmProviderCredentials]: + """ + Creates a new LLM provider credential. + + Args: + name (str): The name of the LLM provider credential. + **kwargs: Optional keyword args. + + Keyword Args: + provider_id (str): The provider id for this LLM provider credential. + expire_time_millis (str): The expire time millis for this LLM provider credential. + api_credentials (str): The api credentials for this LLM provider credential. + + Returns: + tuple: A tuple containing the newly added LlmProviderCredentials instance, response, and error. + + Examples: + Add a new LLM provider credential. ``providerId`` must reference an existing + provider -- resolve it with :meth:`~zscaler.aiguard.llm_providers.LlmProvidersAPI.get_provider_by_name`: + + >>> provider, _, error = client.aiguard.llm_providers.get_provider_by_name("Default Anthropic Provider") + >>> added_credential, _, error = client.aiguard.llm_provider_credentials.add_credential( + ... name="Anthropic_API02", + ... providerId=provider.id, + ... apiCredentials={"type": "API_KEY", "key": ""}, + ... ) + >>> if error: + ... print(f"Error adding LLM provider credential: {error}") + ... return + ... print(f"LLM provider credential added successfully: {added_credential.as_dict()}") + + Note: + ``apiCredentials`` is write-only and is never returned in responses. Valid + ``type`` values are API_KEY, BEARER, CROSS_ACCOUNT_ROLE, ACCESS_KEY and + TRANSPARENT. ``expireTimeMillis`` is optional -- omit it for credentials + that do not expire. + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviderCredentials) + if error: + return (None, response, error) + + try: + result = LlmProviderCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_credential(self, credential_id: int, **kwargs) -> APIResult[LlmProviderCredentials]: + """ + Updates information for the specified LLM provider credential. + + Args: + credential_id (int): The unique identifier for the LLM provider credential. + + Keyword Args: + name (str): The name of the LLM provider credential. + provider_id (str): The provider id for this LLM provider credential. + expire_time_millis (str): The expire time millis for this LLM provider credential. + api_credentials (str): The api credentials for this LLM provider credential. + + Returns: + tuple: A tuple containing the updated LlmProviderCredentials instance, response, and error. + + Examples: + Update an existing LLM provider credential: + + >>> updated_credential, _, error = client.aiguard.llm_provider_credentials.update_credential( + ... credential_id=739, + ... name="Anthropic_API02_Updated", + ... providerId=6099, + ... apiCredentials={"type": "API_KEY", "key": ""}, + ... ) + >>> if error: + ... print(f"Error updating LLM provider credential: {error}") + ... return + ... print(f"LLM provider credential updated successfully: {updated_credential.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials/{credential_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviderCredentials) + if error: + return (None, response, error) + + try: + result = LlmProviderCredentials(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_credential(self, credential_id: int) -> APIResult[None]: + """ + Deletes the specified LLM provider credential. + + Args: + credential_id (int): The unique identifier for the LLM provider credential. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a LLM provider credential: + + >>> _, _, error = client.aiguard.llm_provider_credentials.delete_credential(1013) + >>> if error: + ... print(f"Error deleting LLM provider credential: {error}") + ... return + ... print(f"Llm provider credential deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-credentials/{credential_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/aiguard/llm_providers.py b/zscaler/aiguard/llm_providers.py new file mode 100644 index 00000000..944bc3a9 --- /dev/null +++ b/zscaler/aiguard/llm_providers.py @@ -0,0 +1,468 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.llm_providers import LlmProviders +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class LLMProvidersAPI(APIClient): + """ + A Client object for the AI Guard LLM Providers resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_providers(self, query_params: Optional[dict] = None) -> APIResult[List[LlmProviders]]: + """ + Lists the LLM providers configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of LlmProviders instances, Response, error) + + Examples: + List LLM providers: + + >>> provider_list, _, error = client.aiguard.llm_providers.list_providers() + >>> if error: + ... print(f"Error listing LLM providers: {error}") + ... return + ... print(f"Total LLM providers found: {len(provider_list)}") + ... for provider in provider_list: + ... print(provider.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(LlmProviders(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_provider(self, provider_id: int) -> APIResult[LlmProviders]: + """ + Fetches a specific LLM provider by ID. + + Args: + provider_id (int): The unique identifier for the LLM provider. + + Returns: + tuple: A tuple containing (LlmProviders instance, Response, error). + + Examples: + Print a specific LLM provider: + + >>> fetched_provider, _, error = client.aiguard.llm_providers.get_provider(1013) + >>> if error: + ... print(f"Error fetching LLM provider by ID: {error}") + ... return + ... print(f"Fetched LLM provider by ID: {fetched_provider.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers/{provider_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviders) + if error: + return (None, response, error) + + try: + result = LlmProviders(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_provider_by_name(self, name: str) -> APIResult[LlmProviders]: + """ + Fetches a specific LLM provider by name. + + Args: + name (str): The name of the LLM provider. + + Returns: + tuple: A tuple containing (LlmProviders instance, Response, error). + + Examples: + Print a specific LLM provider by name: + + >>> fetched_provider, _, error = client.aiguard.llm_providers.get_provider_by_name('Provider01') + >>> if error: + ... print(f"Error fetching LLM provider by name: {error}") + ... return + ... print(f"Fetched LLM provider by name: {fetched_provider.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviders) + if error: + return (None, response, error) + + try: + result = LlmProviders(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + # NOTE: The referential-check endpoint is currently returning HTTP 404 for every + # resource type, including via Postman with a known-good id. The method is + # commented out until the API supports it; re-enable once the endpoint is live. + # def referential_check(self, provider_id: int) -> APIResult[dict]: + # """ + # Performs a referential check for the specified LLM provider, returning the resources that reference it (e.g. credentials, applications, rules). + # + # Args: + # provider_id (int): The unique identifier for the LLM provider. + # + # Returns: + # tuple: A tuple containing (the raw response value, Response, error). + # + # Examples: + # >>> result, _, error = client.aiguard.llm_providers.referential_check(1013) + # >>> if error: + # ... print(f"Error calling referential_check: {error}") + # ... return + # ... print(f"Result: {result}") + # """ + # http_method = "get".upper() + # api_url = format_url(f""" + # {self._aiguard_base_endpoint} + # /llm-providers/{provider_id}/referential-check + # """) + # + # body = {} + # headers = {} + # + # request, error = self._request_executor.create_request(http_method, api_url, body, headers) + # + # if error: + # return (None, None, error) + # + # response, error = self._request_executor.execute(request) + # if error: + # return (None, response, error) + # + # try: + # result = self.form_response_body(response.get_body()) + # except Exception as error: + # return (None, response, error) + # return (result, response, None) + + def list_provider_types(self, query_params: Optional[dict] = None) -> APIResult[list]: + """ + Lists the LLM provider types supported by AI Guard. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of LLM providers, Response, error) + + Examples: + List LLM providers: + + >>> provider_list, _, error = client.aiguard.llm_providers.list_provider_types() + >>> if error: + ... print(f"Error listing LLM providers: {error}") + ... return + ... print(f"Total LLM providers found: {len(provider_list)}") + ... for provider in provider_list: + ... print(provider.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-types + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = response.get_results() + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_provider_type(self, provider_type: str) -> APIResult[dict]: + """ + Fetches details for a specific LLM provider type. + + Args: + provider_type (str): The LLM provider type (e.g. ``OPENAI``). + + Returns: + tuple: A tuple containing (the raw response value, Response, error). + + Examples: + >>> result, _, error = client.aiguard.llm_providers.get_provider_type('OPENAI') + >>> if error: + ... print(f"Error calling get_provider_type: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-provider-types/{provider_type} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_provider(self, **kwargs) -> APIResult[LlmProviders]: + """ + Creates a new LLM provider. + + Args: + name (str): The name of the LLM provider. + **kwargs: Optional keyword args. + + Keyword Args: + type (str): The type for this LLM provider. + public (str): The public for this LLM provider. + + Returns: + tuple: A tuple containing the newly added LlmProviders instance, response, and error. + + Examples: + Add a new public LLM provider: + + >>> added_provider, _, error = client.aiguard.llm_providers.add_provider( + ... name="BDAnthropic", + ... type="xai", + ... public=True, + ... ) + >>> if error: + ... print(f"Error adding LLM provider: {error}") + ... return + ... print(f"LLM provider added successfully: {added_provider.as_dict()}") + + Note: + A private provider (``public=False``) additionally requires a ``servers`` + payload. Use :meth:`list_provider_types` to discover the supported values + for ``type``. + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviders) + if error: + return (None, response, error) + + try: + result = LlmProviders(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_provider(self, provider_id: int, **kwargs) -> APIResult[LlmProviders]: + """ + Updates information for the specified LLM provider. + + Args: + provider_id (int): The unique identifier for the LLM provider. + + Keyword Args: + name (str): The name of the LLM provider. + type (str): The type for this LLM provider. + public (str): The public for this LLM provider. + + Returns: + tuple: A tuple containing the updated LlmProviders instance, response, and error. + + Examples: + Update an existing LLM provider: + + >>> updated_provider, _, error = client.aiguard.llm_providers.update_provider( + ... provider_id=29103, + ... name="BDAnthropic_Updated", + ... type="xai", + ... public=False, + ... ) + >>> if error: + ... print(f"Error updating LLM provider: {error}") + ... return + ... print(f"LLM provider updated successfully: {updated_provider.as_dict()}") + + Note: + Public providers are not editable -- the API rejects the update with + "A public provider is not editable." + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers/{provider_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, LlmProviders) + if error: + return (None, response, error) + + try: + result = LlmProviders(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_provider(self, provider_id: int) -> APIResult[None]: + """ + Deletes the specified LLM provider. + + Args: + provider_id (int): The unique identifier for the LLM provider. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a LLM provider: + + >>> _, _, error = client.aiguard.llm_providers.delete_provider(1013) + >>> if error: + ... print(f"Error deleting LLM provider: {error}") + ... return + ... print(f"Llm provider deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /llm-providers/{provider_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/tests/integration/zaiguard/__init__.py b/zscaler/aiguard/models/__init__.py similarity index 100% rename from tests/integration/zaiguard/__init__.py rename to zscaler/aiguard/models/__init__.py diff --git a/zscaler/aiguard/models/llm_application_credentials.py b/zscaler/aiguard/models/llm_application_credentials.py new file mode 100644 index 00000000..798b4bb4 --- /dev/null +++ b/zscaler/aiguard/models/llm_application_credentials.py @@ -0,0 +1,71 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class LlmApplicationCredentials(ZscalerObject): + """ + A class for LlmApplicationCredentials objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LlmApplicationCredentials model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.application_id = config["applicationId"] if "applicationId" in config else None + self.provider_id = config["providerId"] if "providerId" in config else None + self.provider_credentials_id = config["providerCredentialsId"] if "providerCredentialsId" in config else None + self.name = config["name"] if "name" in config else None + self.mode = config["mode"] if "mode" in config else None + self.create_time_millis = config["createTimeMillis"] if "createTimeMillis" in config else None + self.update_time_millis = config["updateTimeMillis"] if "updateTimeMillis" in config else None + else: + self.id = None + self.application_id = None + self.provider_id = None + self.provider_credentials_id = None + self.name = None + self.mode = None + self.create_time_millis = None + self.update_time_millis = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "applicationId": self.application_id, + "providerId": self.provider_id, + "providerCredentialsId": self.provider_credentials_id, + "name": self.name, + "mode": self.mode, + "createTimeMillis": self.create_time_millis, + "updateTimeMillis": self.update_time_millis, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/aiguard/models/llm_applications.py b/zscaler/aiguard/models/llm_applications.py new file mode 100644 index 00000000..5d5a7f18 --- /dev/null +++ b/zscaler/aiguard/models/llm_applications.py @@ -0,0 +1,108 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class LlmApplications(ZscalerObject): + """ + A class for LlmApplications objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LlmApplications model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.owner_email = config["ownerEmail"] if "ownerEmail" in config else None + self.create_time_millis = config["createTimeMillis"] if "createTimeMillis" in config else None + self.update_time_millis = config["updateTimeMillis"] if "updateTimeMillis" in config else None + + if "applicationSettings" in config: + if isinstance(config["applicationSettings"], ApplicationSettings): + self.application_settings = config["applicationSettings"] + elif config["applicationSettings"] is not None: + self.application_settings = ApplicationSettings(config["applicationSettings"]) + else: + self.application_settings = None + else: + self.application_settings = None + else: + self.id = None + self.name = None + self.owner_email = None + self.create_time_millis = None + self.update_time_millis = None + self.application_settings = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "ownerEmail": self.owner_email, + "createTimeMillis": self.create_time_millis, + "updateTimeMillis": self.update_time_millis, + "applicationSettings": self.application_settings, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class ApplicationSettings(ZscalerObject): + """ + A class for ApplicationSettings objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the ApplicationSettings model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.include_event_contents = config["includeEventContents"] if "includeEventContents" in config else None + self.encrypt_event_contents = config["encryptEventContents"] if "encryptEventContents" in config else None + else: + self.include_event_contents = None + self.encrypt_event_contents = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "includeEventContents": self.include_event_contents, + "encryptEventContents": self.encrypt_event_contents, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/aiguard/models/llm_provider_credentials.py b/zscaler/aiguard/models/llm_provider_credentials.py new file mode 100644 index 00000000..9d661b77 --- /dev/null +++ b/zscaler/aiguard/models/llm_provider_credentials.py @@ -0,0 +1,99 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class LlmProviderCredentials(ZscalerObject): + """ + A class for LlmProviderCredentials objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LlmProviderCredentials model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.provider_id = config["providerId"] if "providerId" in config else None + self.name = config["name"] if "name" in config else None + self.expire_time_millis = config["expireTimeMillis"] if "expireTimeMillis" in config else None + + if "apiCredentials" in config: + if isinstance(config["apiCredentials"], ApiCredentials): + self.api_credentials = config["apiCredentials"] + elif config["apiCredentials"] is not None: + self.api_credentials = ApiCredentials(config["apiCredentials"]) + else: + self.api_credentials = None + else: + self.api_credentials = None + else: + self.provider_id = None + self.name = None + self.expire_time_millis = None + self.api_credentials = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "providerId": self.provider_id, + "name": self.name, + "expireTimeMillis": self.expire_time_millis, + "apiCredentials": self.api_credentials, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class ApiCredentials(ZscalerObject): + """ + A class for ApiCredentials objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the ApiCredentials model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.type = config["type"] if "type" in config else None + else: + self.type = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "type": self.type, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/aiguard/models/llm_providers.py b/zscaler/aiguard/models/llm_providers.py new file mode 100644 index 00000000..6bb86aab --- /dev/null +++ b/zscaler/aiguard/models/llm_providers.py @@ -0,0 +1,65 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class LlmProviders(ZscalerObject): + """ + A class for LlmProviders objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LlmProviders model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.type = config["type"] if "type" in config else None + self.create_time_millis = config["createTimeMillis"] if "createTimeMillis" in config else None + self.update_time_millis = config["updateTimeMillis"] if "updateTimeMillis" in config else None + self.public = config["public"] if "public" in config else None + else: + self.id = None + self.name = None + self.type = None + self.create_time_millis = None + self.update_time_millis = None + self.public = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "type": self.type, + "createTimeMillis": self.create_time_millis, + "updateTimeMillis": self.update_time_millis, + "public": self.public, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/aiguard/models/policies.py b/zscaler/aiguard/models/policies.py new file mode 100644 index 00000000..5be17279 --- /dev/null +++ b/zscaler/aiguard/models/policies.py @@ -0,0 +1,207 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class Policies(ZscalerObject): + """ + A class for Policies objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Policies model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.version = config["version"] if "version" in config else None + self.description = config["description"] if "description" in config else None + self.create_time_millis = config["createTimeMillis"] if "createTimeMillis" in config else None + self.update_time_millis = config["updateTimeMillis"] if "updateTimeMillis" in config else None + self.input_detector_policies = ZscalerCollection.form_list( + config["inputDetectorPolicies"] if "inputDetectorPolicies" in config else [], InputDetectorPolicy + ) + self.output_detector_policies = ZscalerCollection.form_list( + config["outputDetectorPolicies"] if "outputDetectorPolicies" in config else [], InputDetectorPolicy + ) + else: + self.id = None + self.name = None + self.version = None + self.description = None + self.create_time_millis = None + self.update_time_millis = None + self.input_detector_policies = [] + self.output_detector_policies = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "version": self.version, + "description": self.description, + "createTimeMillis": self.create_time_millis, + "updateTimeMillis": self.update_time_millis, + "inputDetectorPolicies": [item.request_format() for item in (self.input_detector_policies or [])], + "outputDetectorPolicies": [item.request_format() for item in (self.output_detector_policies or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class InputDetectorPolicy(ZscalerObject): + """ + A class for InputDetectorPolicy objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the InputDetectorPolicy model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.detector = config["detector"] if "detector" in config else None + self.enabled = config["enabled"] if "enabled" in config else None + self.severity = config["severity"] if "severity" in config else None + + if "configuration" in config: + if isinstance(config["configuration"], Configuration): + self.configuration = config["configuration"] + elif config["configuration"] is not None: + self.configuration = Configuration(config["configuration"]) + else: + self.configuration = None + else: + self.configuration = None + else: + self.detector = None + self.enabled = None + self.severity = None + self.configuration = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "detector": self.detector, + "enabled": self.enabled, + "severity": self.severity, + "configuration": self.configuration, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Configuration(ZscalerObject): + """ + A class for Configuration objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Configuration model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.action = config["action"] if "action" in config else None + self.threshold = config["threshold"] if "threshold" in config else None + self.anonymization = config["anonymization"] if "anonymization" in config else None + self.default_action = config["defaultAction"] if "defaultAction" in config else None + self.replace_with_masked_content = ( + config["replaceWithMaskedContent"] if "replaceWithMaskedContent" in config else None + ) + self.entities = ZscalerCollection.form_list(config["entities"] if "entities" in config else [], Entity) + else: + self.action = None + self.threshold = None + self.anonymization = None + self.default_action = None + self.replace_with_masked_content = None + self.entities = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "action": self.action, + "threshold": self.threshold, + "anonymization": self.anonymization, + "defaultAction": self.default_action, + "replaceWithMaskedContent": self.replace_with_masked_content, + "entities": [item.request_format() for item in (self.entities or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Entity(ZscalerObject): + """ + A class for Entity objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Entity model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.action = config["action"] if "action" in config else None + self.entity_type = config["entityType"] if "entityType" in config else None + else: + self.action = None + self.entity_type = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "action": self.action, + "entityType": self.entity_type, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zaiguard/models/policy_detection.py b/zscaler/aiguard/models/policy_detection.py similarity index 100% rename from zscaler/zaiguard/models/policy_detection.py rename to zscaler/aiguard/models/policy_detection.py diff --git a/zscaler/aiguard/models/policy_match_rules.py b/zscaler/aiguard/models/policy_match_rules.py new file mode 100644 index 00000000..3547f9fe --- /dev/null +++ b/zscaler/aiguard/models/policy_match_rules.py @@ -0,0 +1,165 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class PolicyMatchRules(ZscalerObject): + """ + A class for PolicyMatchRules objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyMatchRules model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.policy_id = config["policyId"] if "policyId" in config else None + self.name = config["name"] if "name" in config else None + self.enabled = config["enabled"] if "enabled" in config else None + self.rule_order = config["ruleOrder"] if "ruleOrder" in config else None + self.version = config["version"] if "version" in config else None + + if "matchCriteria" in config: + if isinstance(config["matchCriteria"], MatchCriteria): + self.match_criteria = config["matchCriteria"] + elif config["matchCriteria"] is not None: + self.match_criteria = MatchCriteria(config["matchCriteria"]) + else: + self.match_criteria = None + else: + self.match_criteria = None + else: + self.id = None + self.policy_id = None + self.name = None + self.enabled = None + self.rule_order = None + self.version = None + self.match_criteria = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "policyId": self.policy_id, + "name": self.name, + "enabled": self.enabled, + "ruleOrder": self.rule_order, + "version": self.version, + "matchCriteria": self.match_criteria, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class MatchCriteria(ZscalerObject): + """ + A class for MatchCriteria objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the MatchCriteria model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.type = config["type"] if "type" in config else None + self.llm_applications = ZscalerCollection.form_list( + config["llmApplications"] if "llmApplications" in config else [], LlmApplication + ) + self.source_ip_addresses = ZscalerCollection.form_list( + config["sourceIpAddresses"] if "sourceIpAddresses" in config else [], str + ) + self.application_groups = ZscalerCollection.form_list( + config["applicationGroups"] if "applicationGroups" in config else [], str + ) + self.custom_request_headers = ZscalerCollection.form_list( + config["customRequestHeaders"] if "customRequestHeaders" in config else [], str + ) + else: + self.type = None + self.llm_applications = [] + self.source_ip_addresses = [] + self.application_groups = [] + self.custom_request_headers = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "type": self.type, + "llmApplications": [item.request_format() for item in (self.llm_applications or [])], + "sourceIpAddresses": self.source_ip_addresses, + "applicationGroups": self.application_groups, + "customRequestHeaders": self.custom_request_headers, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class LlmApplication(ZscalerObject): + """ + A class for LlmApplication objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LlmApplication model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.application_id = config["applicationId"] if "applicationId" in config else None + self.application_credentials_ids = ZscalerCollection.form_list( + config["applicationCredentialsIds"] if "applicationCredentialsIds" in config else [], int + ) + else: + self.application_id = None + self.application_credentials_ids = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "applicationId": self.application_id, + "applicationCredentialsIds": self.application_credentials_ids, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/aiguard/policies.py b/zscaler/aiguard/policies.py new file mode 100644 index 00000000..fb7a77ef --- /dev/null +++ b/zscaler/aiguard/policies.py @@ -0,0 +1,368 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.policies import Policies +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class PoliciesAPI(APIClient): + """ + A Client object for the AI Guard Detection Policies resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_policies(self, query_params: Optional[dict] = None) -> APIResult[List[Policies]]: + """ + Lists the detection policies configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of Policies instances, Response, error) + + Examples: + List detection policies: + + >>> policy_list, _, error = client.aiguard.policies.list_policies() + >>> if error: + ... print(f"Error listing detection policies: {error}") + ... return + ... print(f"Total detection policies found: {len(policy_list)}") + ... for policy in policy_list: + ... print(policy.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(Policies(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_policy(self, policy_id: int) -> APIResult[Policies]: + """ + Fetches a specific detection policy by ID. + + Args: + policy_id (int): The unique identifier for the detection policy. + + Returns: + tuple: A tuple containing (Policies instance, Response, error). + + Examples: + Print a specific detection policy: + + >>> fetched_policy, _, error = client.aiguard.policies.get_policy(1013) + >>> if error: + ... print(f"Error fetching detection policy by ID: {error}") + ... return + ... print(f"Fetched detection policy by ID: {fetched_policy.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies/{policy_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, Policies) + if error: + return (None, response, error) + + try: + result = Policies(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_policy_by_name(self, name: str) -> APIResult[Policies]: + """ + Fetches a specific detection policy by name. + + Args: + name (str): The name of the detection policy. + + Returns: + tuple: A tuple containing (Policies instance, Response, error). + + Examples: + Print a specific detection policy by name: + + >>> fetched_policy, _, error = client.aiguard.policies.get_policy_by_name('Policy01') + >>> if error: + ... print(f"Error fetching detection policy by name: {error}") + ... return + ... print(f"Fetched detection policy by name: {fetched_policy.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, Policies) + if error: + return (None, response, error) + + try: + result = Policies(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_policy(self, **kwargs) -> APIResult[Policies]: + """ + Creates a new detection policy. + + Args: + name (str): The name of the detection policy. + **kwargs: Optional keyword args. + + Keyword Args: + version (str): The version for this detection policy. + description (str): Additional information about the detection policy. + input_detector_policies (str): The input detector policies for this detection policy. + output_detector_policies (str): The output detector policies for this detection policy. + + Returns: + tuple: A tuple containing the newly added Policies instance, response, and error. + + Examples: + Add a new detection policy with input and output detectors: + + >>> added_policy, _, error = client.aiguard.policies.add_policy( + ... name="PolicyRule01", + ... inputDetectorPolicies=[ + ... { + ... "detector": "toxicity", + ... "enabled": True, + ... "severity": "HIGH", + ... "configuration": {"action": "BLOCK", "threshold": 0.87}, + ... }, + ... { + ... "detector": "prompt_injection", + ... "enabled": True, + ... "severity": "CRITICAL", + ... "configuration": {"action": "BLOCK", "threshold": 0.75}, + ... }, + ... ], + ... outputDetectorPolicies=[ + ... { + ... "detector": "pii", + ... "enabled": False, + ... "severity": "CRITICAL", + ... "configuration": { + ... "entities": [ + ... {"action": "BLOCK", "entityType": "CREDIT_CARD"}, + ... {"action": "BLOCK", "entityType": "US_SSN"}, + ... {"action": "DETECT", "entityType": "EMAIL_ADDRESS"}, + ... ], + ... "threshold": 0.5, + ... "anonymization": "NONE", + ... "defaultAction": "BLOCK", + ... "replaceWithMaskedContent": False, + ... }, + ... }, + ... ], + ... ) + >>> if error: + ... print(f"Error adding detection policy: {error}") + ... return + ... print(f"Detection policy added successfully: {added_policy.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, Policies) + if error: + return (None, response, error) + + try: + result = Policies(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_policy(self, policy_id: int, **kwargs) -> APIResult[Policies]: + """ + Updates information for the specified detection policy. + + Args: + policy_id (int): The unique identifier for the detection policy. + + Keyword Args: + name (str): The name of the detection policy. + version (str): The version for this detection policy. + description (str): Additional information about the detection policy. + input_detector_policies (str): The input detector policies for this detection policy. + output_detector_policies (str): The output detector policies for this detection policy. + + Returns: + tuple: A tuple containing the updated Policies instance, response, and error. + + Examples: + Update an existing detection policy. The update replaces the policy, so the + detector lists are sent in full: + + >>> updated_policy, _, error = client.aiguard.policies.update_policy( + ... policy_id=2916, + ... name="PolicyRule01", + ... inputDetectorPolicies=[ + ... { + ... "detector": "toxicity", + ... "enabled": True, + ... "severity": "HIGH", + ... "configuration": {"action": "BLOCK", "threshold": 0.87}, + ... }, + ... ], + ... outputDetectorPolicies=[ + ... { + ... "detector": "toxicity", + ... "enabled": True, + ... "severity": "CRITICAL", + ... "configuration": {"action": "BLOCK", "threshold": 0.87}, + ... }, + ... ], + ... ) + >>> if error: + ... print(f"Error updating detection policy: {error}") + ... return + ... print(f"Detection policy updated successfully: {updated_policy.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies/{policy_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, Policies) + if error: + return (None, response, error) + + try: + result = Policies(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_policy(self, policy_id: int) -> APIResult[None]: + """ + Deletes the specified detection policy. + + Args: + policy_id (int): The unique identifier for the detection policy. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a detection policy: + + >>> _, _, error = client.aiguard.policies.delete_policy(1013) + >>> if error: + ... print(f"Error deleting detection policy: {error}") + ... return + ... print(f"Detection policy deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policies/{policy_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zaiguard/policy_detection.py b/zscaler/aiguard/policy_detection.py similarity index 88% rename from zscaler/zaiguard/policy_detection.py rename to zscaler/aiguard/policy_detection.py index c1079429..fd73913c 100644 --- a/zscaler/zaiguard/policy_detection.py +++ b/zscaler/aiguard/policy_detection.py @@ -16,19 +16,37 @@ from typing import Optional +from zscaler.aiguard.models.policy_detection import ( + ExecuteDetectionsPolicyResponse, + ResolveAndExecuteDetectionsPolicyResponse, +) from zscaler.api_client import APIClient from zscaler.request_executor import RequestExecutor from zscaler.types import APIResult from zscaler.utils import format_url -from zscaler.zaiguard.models.policy_detection import ( - ExecuteDetectionsPolicyResponse, - ResolveAndExecuteDetectionsPolicyResponse, -) class PolicyDetectionAPI(APIClient): """ - API client for AIGuard Policy Detection operations. + API client for AI Guard Policy Detection operations. + + .. warning:: + + These endpoints are **legacy only**. ``/v1/detection/execute-policy`` and + ``/v1/detection/resolve-and-execute-policy`` are not exposed through OneAPI, + so this interface is reached with + :class:`~zscaler.oneapi_client.LegacyAIGuardClient` (AI Guard API key against + ``https://api..zseclipse.net``) rather than ``ZscalerClient``. + + All other AI Guard resources are OneAPI only. + + Examples: + >>> from zscaler.oneapi_client import LegacyAIGuardClient + >>> with LegacyAIGuardClient({"api_key": "", "cloud": "us1"}) as client: + ... result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + ... content="User prompt or AI response to scan", + ... direction="IN", + ... ) """ def __init__(self, request_executor: "RequestExecutor") -> None: diff --git a/zscaler/aiguard/policy_match_rules.py b/zscaler/aiguard/policy_match_rules.py new file mode 100644 index 00000000..76335306 --- /dev/null +++ b/zscaler/aiguard/policy_match_rules.py @@ -0,0 +1,349 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.aiguard.models.policy_match_rules import PolicyMatchRules +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url + + +class PolicyMatchRulesAPI(APIClient): + """ + A Client object for the AI Guard Policy Match Rules resource. + """ + + _aiguard_base_endpoint = "/aiguard/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_rules(self, query_params: Optional[dict] = None) -> APIResult[List[PolicyMatchRules]]: + """ + Lists the policy match rules configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of PolicyMatchRules instances, Response, error) + + Examples: + List policy match rules: + + >>> rule_list, _, error = client.aiguard.policy_match_rules.list_rules() + >>> if error: + ... print(f"Error listing policy match rules: {error}") + ... return + ... print(f"Total policy match rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(PolicyMatchRules(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_rule(self, rule_id: int) -> APIResult[PolicyMatchRules]: + """ + Fetches a specific policy match rule by ID. + + Args: + rule_id (int): The unique identifier for the policy match rule. + + Returns: + tuple: A tuple containing (PolicyMatchRules instance, Response, error). + + Examples: + Print a specific policy match rule: + + >>> fetched_rule, _, error = client.aiguard.policy_match_rules.get_rule(1013) + >>> if error: + ... print(f"Error fetching policy match rule by ID: {error}") + ... return + ... print(f"Fetched policy match rule by ID: {fetched_rule.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules/{rule_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyMatchRules) + if error: + return (None, response, error) + + try: + result = PolicyMatchRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_rule_by_name(self, name: str) -> APIResult[PolicyMatchRules]: + """ + Fetches a specific policy match rule by name. + + Args: + name (str): The name of the policy match rule. + + Returns: + tuple: A tuple containing (PolicyMatchRules instance, Response, error). + + Examples: + Print a specific policy match rule by name: + + >>> fetched_rule, _, error = client.aiguard.policy_match_rules.get_rule_by_name('Rule01') + >>> if error: + ... print(f"Error fetching policy match rule by name: {error}") + ... return + ... print(f"Fetched policy match rule by name: {fetched_rule.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules/name/{name} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyMatchRules) + if error: + return (None, response, error) + + try: + result = PolicyMatchRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_rule(self, **kwargs) -> APIResult[PolicyMatchRules]: + """ + Creates a new policy match rule. + + Args: + name (str): The name of the policy match rule. + **kwargs: Optional keyword args. + + Keyword Args: + policy_id (str): The policy id for this policy match rule. + enabled (bool): Indicates whether the policy match rule is enabled. + rule_order (str): The rule order for this policy match rule. + version (str): The version for this policy match rule. + match_criteria (str): The match criteria for this policy match rule. + + Returns: + tuple: A tuple containing the newly added PolicyMatchRules instance, response, and error. + + Examples: + Add a new policy match rule. ``policyId``, ``applicationId`` and + ``applicationCredentialsIds`` must reference existing resources -- create the + detection policy, the LLM application and the application credential first and + reuse the ids returned by those calls: + + >>> added_rule, _, error = client.aiguard.policy_match_rules.add_rule( + ... policyId=2916, + ... name="PolicyRule01", + ... enabled=True, + ... ruleOrder=2, + ... matchCriteria={ + ... "llmApplications": [ + ... { + ... "applicationId": 647, + ... "applicationCredentialsIds": [1075], + ... } + ... ], + ... "type": "DAS_APPLICATION", + ... }, + ... ) + >>> if error: + ... print(f"Error adding policy match rule: {error}") + ... return + ... print(f"Policy match rule added successfully: {added_rule.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyMatchRules) + if error: + return (None, response, error) + + try: + result = PolicyMatchRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_rule(self, rule_id: int, **kwargs) -> APIResult[PolicyMatchRules]: + """ + Updates information for the specified policy match rule. + + Args: + rule_id (int): The unique identifier for the policy match rule. + + Keyword Args: + name (str): The name of the policy match rule. + policy_id (str): The policy id for this policy match rule. + enabled (bool): Indicates whether the policy match rule is enabled. + rule_order (str): The rule order for this policy match rule. + version (str): The version for this policy match rule. + match_criteria (str): The match criteria for this policy match rule. + + Returns: + tuple: A tuple containing the updated PolicyMatchRules instance, response, and error. + + Examples: + Update an existing policy match rule. The update replaces the rule, so the + match criteria are sent in full: + + >>> updated_rule, _, error = client.aiguard.policy_match_rules.update_rule( + ... rule_id=1013, + ... policyId=2916, + ... name="PolicyRule01_Updated", + ... enabled=True, + ... ruleOrder=2, + ... matchCriteria={ + ... "llmApplications": [ + ... { + ... "applicationId": 647, + ... "applicationCredentialsIds": [1075], + ... } + ... ], + ... "type": "DAS_APPLICATION", + ... }, + ... ) + >>> if error: + ... print(f"Error updating policy match rule: {error}") + ... return + ... print(f"Policy match rule updated successfully: {updated_rule.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules/{rule_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyMatchRules) + if error: + return (None, response, error) + + try: + result = PolicyMatchRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_rule(self, rule_id: int) -> APIResult[None]: + """ + Deletes the specified policy match rule. + + Args: + rule_id (int): The unique identifier for the policy match rule. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a policy match rule: + + >>> _, _, error = client.aiguard.policy_match_rules.delete_rule(1013) + >>> if error: + ... print(f"Error deleting policy match rule: {error}") + ... return + ... print(f"Policy match rule deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._aiguard_base_endpoint} + /detections/policy-match-rules/{rule_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/helpers.py b/zscaler/helpers.py index 04bd8cb1..0eccf254 100644 --- a/zscaler/helpers.py +++ b/zscaler/helpers.py @@ -53,6 +53,10 @@ def to_snake_case(string): "extranetDNSList": "extranet_dns_list", "primaryDNSServer": "primary_dns_server", "secondaryDNSServer": "secondary_dns_server", + # ZIA Endpoint DLP edge cases — the generic heuristic splits the + # trailing acronyms letter-by-letter (bundleID -> bundle_i_d). + "bundleID": "bundle_id", + "modUId": "mod_uid", # ZCC Edge Case Attributes "enableUDPTransportSelection": "enable_udp_transport_selection", "interceptZIATrafficAllAdapters": "intercept_zia_traffic_all_adapters", @@ -207,6 +211,12 @@ def to_lower_camel_case(string): "extranet_dns_list": "extranetDNSList", "primary_dns_server": "primaryDNSServer", "secondary_dns_server": "secondaryDNSServer", + # ZIA Endpoint DLP edge cases (see the matching entries in + # to_snake_case). Note: the read-only Version threat-metadata object + # uses a literal snake_case "bundle_id" wire key; that response-only + # path is unaffected by request-body camel-casing. + "bundle_id": "bundleID", + "mod_uid": "modUId", # ZCC Edge Case Attributes "enable_udp_transport_selection": "enableUDPTransportSelection", "intercept_zia_traffic_all_adapters": "interceptZIATrafficAllAdapters", diff --git a/zscaler/oneapi_client.py b/zscaler/oneapi_client.py index c8af336d..b4951f24 100644 --- a/zscaler/oneapi_client.py +++ b/zscaler/oneapi_client.py @@ -4,6 +4,8 @@ import requests +from zscaler.aiguard.aiguard_service import AIGuardService +from zscaler.aiguard.legacy import LegacyZGuardClientHelper from zscaler.cache.no_op_cache import NoOpCache from zscaler.cache.zscaler_cache import ZscalerCache from zscaler.config.config_setter import ConfigSetter @@ -11,8 +13,6 @@ from zscaler.logger import setup_logging from zscaler.oneapi_oauth_client import OAuth from zscaler.request_executor import RequestExecutor -from zscaler.zaiguard.legacy import LegacyZGuardClientHelper -from zscaler.zaiguard.zaiguard_service import ZGuardService from zscaler.zbi.zbi_service import ZBIService from zscaler.zcc.legacy import LegacyZCCClientHelper from zscaler.zcc.zcc_service import ZCCService @@ -48,7 +48,7 @@ def __init__( zia_legacy_client: Optional[LegacyZIAClientHelper] = None, zwa_legacy_client: Optional[LegacyZWAClientHelper] = None, ztb_legacy_client: Optional[LegacyZTBClientHelper] = None, - zguard_legacy_client: Optional[LegacyZGuardClientHelper] = None, + aiguard_legacy_client: Optional[LegacyZGuardClientHelper] = None, use_legacy_client: bool = False, ) -> None: self.use_legacy_client = use_legacy_client @@ -59,7 +59,7 @@ def __init__( self.zia_legacy_client = zia_legacy_client self.zwa_legacy_client = zwa_legacy_client self.ztb_legacy_client = ztb_legacy_client - self.zguard_legacy_client = zguard_legacy_client + self.aiguard_legacy_client = aiguard_legacy_client # ZCC Legacy client initialization logic if use_legacy_client and zcc_legacy_client: @@ -69,6 +69,14 @@ def __init__( self.logger.info("Legacy ZCC client initialized successfully.") return + # AI Guard Legacy client initialization logic (policy detection only) + if use_legacy_client and aiguard_legacy_client: + self._config = {} + self._request_executor = aiguard_legacy_client + self.logger = logging.getLogger(__name__) + self.logger.info("Legacy AI Guard client initialized successfully.") + return + # ZDX Legacy client initialization logic if use_legacy_client and zdx_legacy_client: self._config = {} @@ -117,14 +125,6 @@ def __init__( self.logger.info("Legacy ZTB client initialized successfully.") return - # ZGuard Legacy client initialization logic - if use_legacy_client and zguard_legacy_client: - self._config = {} - self._request_executor = zguard_legacy_client - self.logger = logging.getLogger(__name__) - self.logger.info("Legacy ZGuard client initialized successfully.") - return - # Assuming user_config is a dictionary or an object with a 'logging' attribute logging_config = ( user_config.get("logging", {}) if isinstance(user_config, dict) else getattr(user_config, "logging", {}) @@ -136,7 +136,6 @@ def __init__( self.zia_legacy_client = zia_legacy_client self.zwa_legacy_client = zwa_legacy_client self.ztb_legacy_client = ztb_legacy_client - self.zguard_legacy_client = zguard_legacy_client # Extract enabled and verbose from the logging configuration enabled = logging_config.get("enabled", None) @@ -220,7 +219,6 @@ def __init__( self.zia_legacy_client, self.zwa_legacy_client, self.ztb_legacy_client, - self.zguard_legacy_client, ) # self.logger.debug("Request executor initialized.") @@ -237,7 +235,7 @@ def __init__( self._zins = None # Z-Insights (GraphQL Analytics API) self._zms = None # ZMS - Zscaler Microsegmentation (GraphQL API) self._zbi = None # Zscaler Business Insights (REST API) - self._zguard = None + self._aiguard = None # Zscaler AI Guard (OneAPI only) self._zcell = None # Zscaler Cellular (OneAPI only) def authenticate(self): @@ -343,12 +341,48 @@ def zeasm(self): return self._zeasm @property - def zguard(self): + def aiguard(self): + """ + Zscaler AI Guard Service. + + AI Guard is split across two authentication paths: + + **OneAPI** (``ZscalerClient``) -- all configuration APIs: + - Detection Policies and Policy Match Rules + - LLM Providers and LLM Provider Credentials + - LLM Applications and LLM Application Credentials + + **Legacy** (``LegacyAIGuardClient``) -- policy detection only: + - ``policy_detection.execute_policy`` + - ``policy_detection.resolve_and_execute_policy`` + + The policy detection endpoints (``/v1/detection/*``) are **not** exposed + through OneAPI, so they must be reached with ``LegacyAIGuardClient`` and an + AI Guard API key. Every other AI Guard resource is OneAPI only. + + Examples: + OneAPI -- configuration APIs:: + + with ZscalerClient(config) as client: + policies, _, err = client.aiguard.policies.list_policies() + + Legacy -- policy detection:: + + with LegacyAIGuardClient({"api_key": "...", "cloud": "us1"}) as client: + result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + content="User prompt to scan", direction="IN" + ) + """ if self.use_legacy_client: - return self._require_legacy_client("ZGuard", self.zguard_legacy_client) - if self._zguard is None: - self._zguard = ZGuardService(self._request_executor) - return self._zguard + return self._require_legacy_client("AI Guard", self.aiguard_legacy_client) + if self._aiguard is None: + self._aiguard = AIGuardService(self._request_executor) + return self._aiguard + + @property + def zguard(self): + """Deprecated alias for the :obj:`aiguard` property.""" + return self.aiguard @property def zins(self): @@ -441,6 +475,12 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): """Automatically close session within context manager.""" + # AI Guard's legacy client authenticates per request with an API key: there is no + # session to close and no deauthenticate endpoint (unlike ZIA/ZTW), so skip the + # teardown entirely rather than logging a session close that never happens. + if getattr(self, "aiguard_legacy_client", None) is not None: + return + self.logger.debug("Exiting context manager, closing session.") if hasattr(self, "_session"): self._session.close() @@ -628,6 +668,50 @@ def __init__( super().__init__(config, zcc_legacy_client=legacy_helper, use_legacy_client=True) +class LegacyAIGuardClient(Client): + """ + Legacy AI Guard client, used **only** for the policy detection endpoints. + + The ``/v1/detection/execute-policy`` and ``/v1/detection/resolve-and-execute-policy`` + endpoints are not exposed through OneAPI, so they are reached directly against + ``https://api..zseclipse.net`` with an AI Guard API key. + + Every other AI Guard resource (detection policies, policy match rules, LLM + providers/applications and their credentials) is OneAPI only -- use + :class:`ZscalerClient` for those. + + Examples: + >>> from zscaler.oneapi_client import LegacyAIGuardClient + >>> with LegacyAIGuardClient({"api_key": "", "cloud": "us1"}) as client: + ... result, _, err = client.aiguard.policy_detection.resolve_and_execute_policy( + ... content="User prompt to scan", + ... direction="IN", + ... ) + """ + + def __init__( + self, + config: dict = {}, + ): + api_key = config.get("api_key", os.getenv("AIGUARD_API_KEY")) + cloud = config.get("cloud", os.getenv("AIGUARD_CLOUD", "us1")) + timeout = config.get("timeout", 240) + cache = config.get("cache", None) + fail_safe = config.get("failSafe", None) + request_executor_impl = config.get("requestExecutor", None) + + # Initialize the LegacyZGuardClientHelper with the extracted parameters + legacy_helper = LegacyZGuardClientHelper( + api_key=api_key, + cloud=cloud, + timeout=timeout, + cache=cache, + fail_safe=fail_safe, + request_executor_impl=request_executor_impl, + ) + super().__init__(config, aiguard_legacy_client=legacy_helper, use_legacy_client=True) + + class LegacyZDXClient(Client): def __init__( self, @@ -700,27 +784,3 @@ def __init__( request_executor_impl=request_executor_impl, ) super().__init__(config, ztb_legacy_client=legacy_helper, use_legacy_client=True) - - -class LegacyZGuardClient(Client): - def __init__( - self, - config: dict = {}, - ): - api_key = config.get("api_key", os.getenv("AIGUARD_API_KEY")) - cloud = config.get("cloud", os.getenv("AIGUARD_CLOUD", "us1")) - timeout = config.get("timeout", 240) - cache = config.get("cache", None) - fail_safe = config.get("failSafe", None) - request_executor_impl = config.get("requestExecutor", None) - - # Initialize the LegacyZGuardClientHelper with the extracted parameters - legacy_helper = LegacyZGuardClientHelper( - api_key=api_key, - cloud=cloud, - timeout=timeout, - cache=cache, - fail_safe=fail_safe, - request_executor_impl=request_executor_impl, - ) - super().__init__(config, zguard_legacy_client=legacy_helper, use_legacy_client=True) diff --git a/zscaler/oneapi_http_client.py b/zscaler/oneapi_http_client.py index 1829c923..a7767d6a 100644 --- a/zscaler/oneapi_http_client.py +++ b/zscaler/oneapi_http_client.py @@ -7,7 +7,6 @@ import requests from zscaler.logger import dump_request, dump_response -from zscaler.zaiguard.legacy import LegacyZGuardClientHelper from zscaler.zcc.legacy import LegacyZCCClientHelper from zscaler.zdx.legacy import LegacyZDXClientHelper from zscaler.zia.legacy import LegacyZIAClientHelper @@ -37,7 +36,7 @@ def __init__( zia_legacy_client: Optional[LegacyZIAClientHelper] = None, zwa_legacy_client: Optional[LegacyZWAClientHelper] = None, ztb_legacy_client: Optional[LegacyZTBClientHelper] = None, - zguard_legacy_client: Optional[LegacyZGuardClientHelper] = None, + aiguard_legacy_client=None, ) -> None: # Get headers from Request Executor @@ -49,7 +48,7 @@ def __init__( self.zia_legacy_client: Optional[LegacyZIAClientHelper] = zia_legacy_client self.zwa_legacy_client: Optional[LegacyZWAClientHelper] = zwa_legacy_client self.ztb_legacy_client: Optional[LegacyZTBClientHelper] = ztb_legacy_client - self.zguard_legacy_client: Optional[LegacyZGuardClientHelper] = zguard_legacy_client + self.aiguard_legacy_client = aiguard_legacy_client # Determine if legacy clients are enabled self.use_zcc_legacy_client: bool = zcc_legacy_client is not None @@ -59,7 +58,6 @@ def __init__( self.use_zia_legacy_client: bool = zia_legacy_client is not None self.use_zwa_legacy_client: bool = zwa_legacy_client is not None self.use_ztb_legacy_client: bool = ztb_legacy_client is not None - self.use_zguard_legacy_client: bool = zguard_legacy_client is not None # Set timeout for all HTTP requests request_timeout: Optional[int] = http_config.get("requestTimeout", None) @@ -317,28 +315,6 @@ def send_request(self, request: Dict[str, Any]) -> Tuple[Optional[requests.Respo } ) - elif self.use_zguard_legacy_client: - parsed_url = urlparse(request["url"]) - path = parsed_url.path - logger.debug(f"Sending request via AIGuard legacy client. Path: {path}") - - response = self.zguard_legacy_client.send( - method=request["method"], - path=path, - params=request["params"], - json=request.get("json") or request.get("data"), - ) - - logger.debug(f"AIGuard Legacy Client Response: {response}") - - if response is None: - error_msg = f"AIGuard Legacy client returned None for path: {path}" - logger.error(error_msg) - return (None, ValueError(error_msg)) - - # For AIGuard, the response is just the requests.Response object - # No need to update params as authentication is already handled - else: # Standard session if self._session: diff --git a/zscaler/oneapi_response.py b/zscaler/oneapi_response.py index 9eb0c2bf..86024453 100644 --- a/zscaler/oneapi_response.py +++ b/zscaler/oneapi_response.py @@ -241,6 +241,22 @@ def _build_json_response(self, response_body: str) -> None: items = val break self._list = items + elif self._service_type == "aiguard": + # AI Guard wraps list responses in an {"items": [...]} envelope with + # no page/pageSize/total metadata. Unwrap the items transparently and + # treat the response as complete (single page). Single-object + # responses (e.g. GET-by-id) come back as a plain dict without an + # "items" key — expose those as a one-item list so get_results() + # and get_body() both work. + if isinstance(self._body, dict) and isinstance(self._body.get("items"), list): + self._list = self._body["items"] + self._is_flat_list_response = True + elif isinstance(self._body, dict): + self._list = [self._body] + self._is_flat_list_response = True + else: + self._list = self._body if isinstance(self._body, list) else [] + self._is_flat_list_response = True elif self._service_type == "zcell": # ZCell wraps paginated list responses in a {"totalElements", "totalPages", # "size", "content": [...]} envelope. Single-object responses (e.g. a diff --git a/zscaler/request_executor.py b/zscaler/request_executor.py index ad047230..7292d881 100644 --- a/zscaler/request_executor.py +++ b/zscaler/request_executor.py @@ -13,7 +13,6 @@ from zscaler.oneapi_oauth_client import OAuth from zscaler.oneapi_response import ZscalerAPIResponse from zscaler.user_agent import UserAgent -from zscaler.zaiguard.legacy import LegacyZGuardClientHelper from zscaler.zcc.legacy import LegacyZCCClientHelper from zscaler.zdx.legacy import LegacyZDXClientHelper from zscaler.zia.legacy import LegacyZIAClientHelper @@ -44,7 +43,7 @@ def __init__( zia_legacy_client: LegacyZIAClientHelper = None, zwa_legacy_client: LegacyZWAClientHelper = None, ztb_legacy_client: LegacyZTBClientHelper = None, - zguard_legacy_client: LegacyZGuardClientHelper = None, + aiguard_legacy_client=None, ): """ Constructor for Request Executor object for Zscaler SDK Client. @@ -61,7 +60,9 @@ def __init__( self.zia_legacy_client = zia_legacy_client self.zwa_legacy_client = zwa_legacy_client self.ztb_legacy_client = ztb_legacy_client - self.zguard_legacy_client = zguard_legacy_client + # AI Guard is OneAPI-only except for policy detection, which the OneAPI + # gateway does not expose -- those endpoints stay on the legacy client. + self.aiguard_legacy_client = aiguard_legacy_client self.use_legacy_client = ( zpa_legacy_client is not None @@ -71,7 +72,7 @@ def __init__( or ztw_legacy_client is not None or zdx_legacy_client is not None or ztb_legacy_client is not None - or zguard_legacy_client is not None + or aiguard_legacy_client is not None ) # Validate and set request timeout @@ -132,7 +133,7 @@ def __init__( zia_legacy_client=self.zia_legacy_client, zwa_legacy_client=self.zwa_legacy_client, ztb_legacy_client=self.ztb_legacy_client, - zguard_legacy_client=self.zguard_legacy_client, + aiguard_legacy_client=self.aiguard_legacy_client, ) exceptions.raise_exception = self._config["client"].get("raiseException", False) @@ -220,8 +221,14 @@ def get_service_type(self, url): return "zins" elif "/zms" in url: return "zms" - elif "/v1/detection" in url or (self.zguard_legacy_client and "/v1/" in url): - return "zguard" + elif "/aiguard" in url: + # OneAPI AI Guard (/aiguard/v1/...). Checked before the legacy branch below: + # "/aiguard/v1/detections/policies" also contains the substring "/v1/detection". + return "aiguard" + elif "/v1/detection/" in url: + # Legacy AI Guard policy detection (api..zseclipse.net/v1/detection/*). + # These endpoints are not available through OneAPI. + return "aiguard_legacy" if self.use_legacy_client: url = self.remove_oneapi_endpoint_prefix(url) # Recheck for service type after removing the prefix @@ -304,8 +311,8 @@ def create_request( base_url = self.zwa_legacy_client.get_base_url(endpoint) elif service_type == "ztb": base_url = self.ztb_legacy_client.get_base_url(endpoint) - elif service_type == "zguard": - base_url = self.zguard_legacy_client.get_base_url(endpoint) + elif service_type == "aiguard_legacy": + base_url = self.aiguard_legacy_client.get_base_url(endpoint) else: base_url = self.get_base_url(endpoint) else: diff --git a/zscaler/utils.py b/zscaler/utils.py index d1deba4a..be01b432 100644 --- a/zscaler/utils.py +++ b/zscaler/utils.py @@ -100,7 +100,6 @@ ("connector_ids", "connectors"), ("server_group_ids", "serverGroups"), ("user_portal_ids", "userPortals"), - ] diff --git a/zscaler/zaiguard/__init__.py b/zscaler/zaiguard/__init__.py deleted file mode 100644 index 1791cae7..00000000 --- a/zscaler/zaiguard/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Copyright (c) 2023, Zscaler Inc. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -""" diff --git a/zscaler/zaiguard/models/__init__.py b/zscaler/zaiguard/models/__init__.py deleted file mode 100644 index 1791cae7..00000000 --- a/zscaler/zaiguard/models/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Copyright (c) 2023, Zscaler Inc. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -""" diff --git a/zscaler/zaiguard/zaiguard_service.py b/zscaler/zaiguard/zaiguard_service.py deleted file mode 100644 index 8f701981..00000000 --- a/zscaler/zaiguard/zaiguard_service.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Copyright (c) 2023, Zscaler Inc. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -""" - -from zscaler.request_executor import RequestExecutor -from zscaler.zaiguard.policy_detection import PolicyDetectionAPI - - -class ZGuardService: - """ZGuard Service client, exposing AIGuard APIs""" - - def __init__(self, request_executor: RequestExecutor) -> None: - self._request_executor = request_executor - - @property - def policy_detection(self) -> PolicyDetectionAPI: - """ - The interface object for the :ref:`AIGuard Policy Detection interface `. - - """ - return PolicyDetectionAPI(self._request_executor) diff --git a/zscaler/zia/azure_integration.py b/zscaler/zia/azure_integration.py index 0661262e..8447908c 100644 --- a/zscaler/zia/azure_integration.py +++ b/zscaler/zia/azure_integration.py @@ -31,8 +31,7 @@ def __init__(self, request_executor: "RequestExecutor") -> None: super().__init__() self._request_executor: RequestExecutor = request_executor - def list_azure_refresh_connection_status( - self, query_params=None) -> APIResult[List[AzureVirtualHub]]: + def list_azure_refresh_connection_status(self, query_params=None) -> APIResult[List[AzureVirtualHub]]: """ List azures (refreshConnectionStatus). @@ -53,8 +52,7 @@ def list_azure_refresh_connection_status( body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) @@ -122,8 +120,7 @@ def list_azures_tunnel_configuration(self, query_params=None) -> APIResult[List[ body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) @@ -170,8 +167,7 @@ def add_azures_tunnel_configuration(self, **kwargs) -> APIResult[AzureVirtualHub return (None, response, error) return (result, response, None) - def list_azures_un_configure_tunnel_status( - self, query_params=None) -> APIResult[List[AzureVirtualHub]]: + def list_azures_un_configure_tunnel_status(self, query_params=None) -> APIResult[List[AzureVirtualHub]]: """ List azures (unConfigureTunnelStatus). @@ -192,8 +188,7 @@ def list_azures_un_configure_tunnel_status( body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) @@ -230,8 +225,7 @@ def list_azures_virtual_hub_sync(self, query_params=None) -> APIResult[List[Azur body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) @@ -299,8 +293,7 @@ def list_azures_virtual_hubs(self, query_params=None) -> APIResult[List[AzureVir body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) diff --git a/zscaler/zia/dlp_endpoint_resource.py b/zscaler/zia/dlp_endpoint_resource.py new file mode 100644 index 00000000..6aa0a6dc --- /dev/null +++ b/zscaler/zia/dlp_endpoint_resource.py @@ -0,0 +1,305 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.dlp_endpoint_resource import DlpEndpointResource + + +class DLPEndpointResourceAPI(APIClient): + """ + A Client object for the DLP Endpoint Resources resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def add_resource(self, **kwargs) -> APIResult[DlpEndpointResource]: + """ + Creates a new DLP endpoint resource. + + Args: + name (str): The name of the DLP endpoint resource. + **kwargs: Optional keyword args. + + Keyword Args: + channel (str): The channel for this DLP endpoint resource. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + is_predefined (bool): A Boolean value indicating whether is predefined applies to this DLP endpoint resource. + network_drive_type (str): The network drive type for this DLP endpoint resource. Accepted values include e.g. ``ALL_DIRECTORIES``. + description (str): Additional information about the DLP endpoint resource. + server_name (str): The server name for this DLP endpoint resource. + app_id (int): The app id for this DLP endpoint resource. + network_drives (list): The list of network drives for this DLP endpoint resource. + printer (dict): The printer configuration for this DLP endpoint resource. + removable_storage (dict): The removable storage configuration for this DLP endpoint resource. + application (dict): The application configuration for this DLP endpoint resource. + + Returns: + tuple: A tuple containing the newly added DlpEndpointResource instance, response, and error. + + Examples: + Add a new DLP endpoint resource: + + >>> added_resource, _, error = client.zia.dlp_endpoint_resource.add_resource( + ... name=f"NewResource_{random.randint(1000, 10000)}", + ... description=f"NewResource_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding DLP endpoint resource: {error}") + ... return + ... print(f"Dlp endpoint resource added successfully: {added_resource.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DlpEndpointResource) + if error: + return (None, response, error) + + try: + result = DlpEndpointResource(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_resource(self, resource_id: int, **kwargs) -> APIResult[DlpEndpointResource]: + """ + Updates information for the specified DLP endpoint resource. + + Args: + resource_id (int): The unique identifier for the DLP endpoint resource. + + Keyword Args: + name (str): The name of the DLP endpoint resource. + channel (str): The channel for this DLP endpoint resource. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + is_predefined (bool): A Boolean value indicating whether is predefined applies to this DLP endpoint resource. + network_drive_type (str): The network drive type for this DLP endpoint resource. Accepted values include e.g. ``ALL_DIRECTORIES``. + description (str): Additional information about the DLP endpoint resource. + server_name (str): The server name for this DLP endpoint resource. + app_id (int): The app id for this DLP endpoint resource. + network_drives (list): The list of network drives for this DLP endpoint resource. + printer (dict): The printer configuration for this DLP endpoint resource. + removable_storage (dict): The removable storage configuration for this DLP endpoint resource. + application (dict): The application configuration for this DLP endpoint resource. + + Returns: + tuple: A tuple containing the updated DlpEndpointResource instance, response, and error. + + Examples: + Update an existing DLP endpoint resource: + + >>> updated_resource, _, error = client.zia.dlp_endpoint_resource.update_resource( + ... resource_id=1013, + ... name=f"UpdatedResource_{random.randint(1000, 10000)}", + ... description=f"UpdatedResource_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating DLP endpoint resource: {error}") + ... return + ... print(f"Dlp endpoint resource updated successfully: {updated_resource.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource/{resource_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DlpEndpointResource) + if error: + return (None, response, error) + + try: + result = DlpEndpointResource(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_resource(self, resource_id: int) -> APIResult[None]: + """ + Deletes the specified DLP endpoint resource. + + Args: + resource_id (int): The unique identifier for the DLP endpoint resource. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a DLP endpoint resource: + + >>> _, _, error = client.zia.dlp_endpoint_resource.delete_resource(1013) + >>> if error: + ... print(f"Error deleting DLP endpoint resource: {error}") + ... return + ... print(f"Dlp endpoint resource deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource/{resource_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def list_resources_by_channel( + self, channel: str, query_params: Optional[dict] = None + ) -> APIResult[List[DlpEndpointResource]]: + """ + Lists the DLP resources configured for the specified channel. Supported channels are PRINTING, REMOVABLE_DRIVE_TRANSFER, NETWORK_DRIVE_TRANSFER, and PERSONAL_CLOUD_STORAGE. + + Args: + channel (str): The DLP endpoint resource channel (e.g. ``PRINTING``). + query_params {dict}: Map of query parameters for the request. + ``[query_params.sort_order]`` {str}: Sorting order for the list by ascending or descending order of the DLP resource names. + ``[query_params.name]`` {str}: Search string used to filter the list by DLP resource name and other fields. + + Returns: + tuple: A tuple containing (list of DlpEndpointResource instances, Response, error) + + Examples: + List DLP endpoint resources: + + >>> resource_list, _, error = client.zia.dlp_endpoint_resource.list_resources_by_channel('PRINTING') + >>> if error: + ... print(f"Error listing DLP endpoint resources: {error}") + ... return + ... print(f"Total DLP endpoint resources found: {len(resource_list)}") + ... for resource in resource_list: + ... print(resource.as_dict()) + + List DLP endpoint resources using filters: + + >>> resource_list, _, error = client.zia.dlp_endpoint_resource.list_resources_by_channel( + ... 'PRINTING', query_params={'sort_order': 'VALUE'}) + >>> if error: + ... print(f"Error listing DLP endpoint resources: {error}") + ... return + ... print(f"Total DLP endpoint resources found: {len(resource_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource/{channel} + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(DlpEndpointResource(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_resource_by_channel(self, channel: str, resource_id: int) -> APIResult[DlpEndpointResource]: + """ + Fetches a single DLP resource with the specified ID for the given channel. + + Args: + channel (str): The DLP endpoint resource channel (e.g. ``PRINTING``). + resource_id (int): The unique identifier for the DLP endpoint resource. + + Returns: + tuple: A tuple containing (DlpEndpointResource instance, Response, error). + + Examples: + Print a specific DLP endpoint resource: + + >>> fetched_resource, _, error = client.zia.dlp_endpoint_resource.get_resource_by_channel('PRINTING', 1013) + >>> if error: + ... print(f"Error fetching DLP endpoint resource by ID: {error}") + ... return + ... print(f"Fetched DLP endpoint resource by ID: {fetched_resource.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource/{channel}/{resource_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DlpEndpointResource) + if error: + return (None, response, error) + + try: + result = DlpEndpointResource(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/dns_application_groups.py b/zscaler/zia/dns_application_groups.py new file mode 100644 index 00000000..df06d18e --- /dev/null +++ b/zscaler/zia/dns_application_groups.py @@ -0,0 +1,274 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.dns_application_groups import DnsApplicationGroups + + +class DNSApplicationGroupsAPI(APIClient): + """ + A Client object for the DNS Application Groups resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_groups(self, query_params: Optional[dict] = None) -> APIResult[List[DnsApplicationGroups]]: + """ + Lists the DNS application groups configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of DnsApplicationGroups instances, Response, error) + + Examples: + List DNS application groups: + + >>> group_list, _, error = client.zia.dns_application_groups.list_groups() + >>> if error: + ... print(f"Error listing DNS application groups: {error}") + ... return + ... print(f"Total DNS application groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dnsApplicationGroups + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(DnsApplicationGroups(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_group(self, group_id: int) -> APIResult[DnsApplicationGroups]: + """ + Fetches a specific DNS application group by ID. + + Args: + group_id (int): The unique identifier for the DNS application group. + + Returns: + tuple: A tuple containing (DnsApplicationGroups instance, Response, error). + + Examples: + Print a specific DNS application group: + + >>> fetched_group, _, error = client.zia.dns_application_groups.get_group(1013) + >>> if error: + ... print(f"Error fetching DNS application group by ID: {error}") + ... return + ... print(f"Fetched DNS application group by ID: {fetched_group.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dnsApplicationGroups/{group_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DnsApplicationGroups) + if error: + return (None, response, error) + + try: + result = DnsApplicationGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_group(self, **kwargs) -> APIResult[DnsApplicationGroups]: + """ + Creates a new DNS application group. + + Args: + name (str): The name of the DNS application group. + **kwargs: Optional keyword args. + + Keyword Args: + description (str): Additional information about the DNS application group. + dns_applications (list): The list of dns applications for this DNS application group. + + Returns: + tuple: A tuple containing the newly added DnsApplicationGroups instance, response, and error. + + Examples: + Add a new DNS application group: + + >>> added_group, _, error = client.zia.dns_application_groups.add_group( + ... name=f"NewGroup_{random.randint(1000, 10000)}", + ... description=f"NewGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding DNS application group: {error}") + ... return + ... print(f"Dns application group added successfully: {added_group.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dnsApplicationGroups + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DnsApplicationGroups) + if error: + return (None, response, error) + + try: + result = DnsApplicationGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_group(self, group_id: int, **kwargs) -> APIResult[DnsApplicationGroups]: + """ + Updates information for the specified DNS application group. + + Args: + group_id (int): The unique identifier for the DNS application group. + + Keyword Args: + name (str): The name of the DNS application group. + description (str): Additional information about the DNS application group. + dns_applications (list): The list of dns applications for this DNS application group. + + Returns: + tuple: A tuple containing the updated DnsApplicationGroups instance, response, and error. + + Examples: + Update an existing DNS application group: + + >>> updated_group, _, error = client.zia.dns_application_groups.update_group( + ... group_id=1013, + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating DNS application group: {error}") + ... return + ... print(f"Dns application group updated successfully: {updated_group.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dnsApplicationGroups/{group_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DnsApplicationGroups) + if error: + return (None, response, error) + + try: + result = DnsApplicationGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_group(self, group_id: int) -> APIResult[None]: + """ + Deletes the specified DNS application group. + + Args: + group_id (int): The unique identifier for the DNS application group. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a DNS application group: + + >>> _, _, error = client.zia.dns_application_groups.delete_group(1013) + >>> if error: + ... print(f"Error deleting DNS application group: {error}") + ... return + ... print(f"Dns application group deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dnsApplicationGroups/{group_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zia/end_user_notification_templates.py b/zscaler/zia/end_user_notification_templates.py new file mode 100644 index 00000000..002d742f --- /dev/null +++ b/zscaler/zia/end_user_notification_templates.py @@ -0,0 +1,311 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.eun_feature_enablement_status import EunFeatureEnablementStatus +from zscaler.zia.models.eun_template_product import EunTemplateProduct +from zscaler.zia.models.eun_user_confirmation_product import EunUserConfirmationProduct + + +class EndUserNotificationTemplatesAPI(APIClient): + """ + A Client object for the End User Notification Templates resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_eun_templates_by_product( + self, template_type: str, product: str, query_params: Optional[dict] = None + ) -> APIResult[List[EunTemplateProduct]]: + """ + Lists the end user notification templates for the specified template type and product (e.g. browser-based or Zscaler Client Connector). + + Args: + template_type (str): The notification template type (e.g. ``ZCC``, ``BROWSER``). + product (str): The product the template applies to. + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of EunTemplateProduct instances, Response, error) + + Examples: + List EUN notification templates: + + >>> template_list, _, error = client.zia.end_user_notification.list_eun_templates_by_product('ZCC', 'ALL') + >>> if error: + ... print(f"Error listing EUN notification templates: {error}") + ... return + ... print(f"Total EUN notification templates found: {len(template_list)}") + ... for template in template_list: + ... print(template.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /eunTemplate/{template_type}/product/{product} + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EunTemplateProduct(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_eun_feature_enablement_status( + self, template_type: str, query_params: Optional[dict] = None + ) -> APIResult[EunFeatureEnablementStatus]: + """ + Retrieves the end user notification feature enablement status for the specified template type. + + Args: + template_type (str): The notification template type (e.g. ``ZCC``, ``BROWSER``). + query_params {dict}: Map of query parameters for the request. + ``[query_params.product_type]`` {str}: Optional policy type filter (e.g. ``INLINE``, ``ENDPOINT_DLP``). + + Returns: + tuple: A tuple containing (EunFeatureEnablementStatus instance, Response, error). + + Examples: + Print a specific EUN notification template: + + >>> fetched_template, _, error = client.zia.end_user_notification.get_eun_feature_enablement_status('ZCC') + >>> if error: + ... print(f"Error fetching EUN notification template: {error}") + ... return + ... print(f"Fetched EUN notification template: {fetched_template.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /eunTemplate/{template_type}/featureEnablementStatus + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EunFeatureEnablementStatus) + if error: + return (None, response, error) + + try: + result = EunFeatureEnablementStatus(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_user_confirmation_by_policy( + self, product: str, query_params: Optional[dict] = None + ) -> APIResult[List[EunUserConfirmationProduct]]: + """ + Lists the user confirmation notification templates for the specified policy type (INLINE, ENDPOINT_DLP, CLOUDAPP, URL, FILE_TYPE, FIREWALL, DNS, IPS). + + Args: + product (str): The policy type the confirmation template applies to. + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of EunUserConfirmationProduct instances, Response, error) + + Examples: + List EUN notification templates: + + >>> template_list, _, error = client.zia.end_user_notification.list_user_confirmation_by_policy('INLINE') + >>> if error: + ... print(f"Error listing EUN notification templates: {error}") + ... return + ... print(f"Total EUN notification templates found: {len(template_list)}") + ... for template in template_list: + ... print(template.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /userConfirmation/product/{product} + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EunUserConfirmationProduct(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_global_default_templates( + self, query_params: Optional[dict] = None + ) -> APIResult[List[EunUserConfirmationProduct]]: + """ + Lists the global default user confirmation templates for all policy types and channels. Takes no parameters. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of EunUserConfirmationProduct instances, Response, error) + + Examples: + List EUN notification templates: + + >>> template_list, _, error = client.zia.end_user_notification.list_global_default_templates() + >>> if error: + ... print(f"Error listing EUN notification templates: {error}") + ... return + ... print(f"Total EUN notification templates found: {len(template_list)}") + ... for template in template_list: + ... print(template.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /userConfirmation/globalDefaultTemplates + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EunUserConfirmationProduct(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_notification_enablement_status(self, template_type: str, query_params: Optional[dict] = None) -> APIResult[dict]: + """ + Retrieves the user confirmation notification enablement status for the specified template type. User confirmation notifications are supported via Zscaler Client Connector only, so the type is typically ``ZCC``. + + Args: + template_type (str): The notification template type (e.g. ``ZCC``). + query_params {dict}: Map of query parameters for the request. + ``[query_params.product_type]`` {str}: Optional policy type filter (e.g. ``INLINE``, ``ENDPOINT_DLP``). + + Returns: + tuple: A tuple containing (the raw response value, Response, error). + + Examples: + >>> result, _, error = client.zia.end_user_notification.get_notification_enablement_status('ZCC') + >>> if error: + ... print(f"Error calling get_notification_enablement_status: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /userConfirmation/{template_type}/featureEnablementStatus + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/endpoint_application_groups.py b/zscaler/zia/endpoint_application_groups.py new file mode 100644 index 00000000..b52ee5d8 --- /dev/null +++ b/zscaler/zia/endpoint_application_groups.py @@ -0,0 +1,343 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.endpoint_application_groups import EndpointApplicationGroups +from zscaler.zia.models.endpoint_applications_policies import EndpointApplicationsPolicies + + +class EndpointApplicationGroupsAPI(APIClient): + """ + A Client object for the Endpoint Application Groups resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_groups(self, query_params: Optional[dict] = None) -> APIResult[List[EndpointApplicationGroups]]: + """ + Lists the endpoint application groups configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of EndpointApplicationGroups instances, Response, error) + + Examples: + List endpoint application groups: + + >>> group_list, _, error = client.zia.endpoint_application_groups.list_groups() + >>> if error: + ... print(f"Error listing endpoint application groups: {error}") + ... return + ... print(f"Total endpoint application groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationGroups(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_group(self, **kwargs) -> APIResult[EndpointApplicationGroups]: + """ + Creates a new endpoint application group. + + Args: + name (str): The name of the endpoint application group. + **kwargs: Optional keyword args. + + Keyword Args: + description (str): Additional information about the endpoint application group. + mod_uid (int): The mod uid for this endpoint application group. + end_point_applications (list): The list of end point applications for this endpoint application group. + + Returns: + tuple: A tuple containing the newly added EndpointApplicationGroups instance, response, and error. + + Examples: + Add a new endpoint application group: + + >>> added_group, _, error = client.zia.endpoint_application_groups.add_group( + ... name=f"NewGroup_{random.randint(1000, 10000)}", + ... description=f"NewGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding endpoint application group: {error}") + ... return + ... print(f"Endpoint application group added successfully: {added_group.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointApplicationGroups) + if error: + return (None, response, error) + + try: + result = EndpointApplicationGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_group(self, group_id: int, **kwargs) -> APIResult[EndpointApplicationGroups]: + """ + Updates information for the specified endpoint application group. + + Args: + group_id (int): The unique identifier for the endpoint application group. + + Keyword Args: + name (str): The name of the endpoint application group. + description (str): Additional information about the endpoint application group. + mod_uid (int): The mod uid for this endpoint application group. + end_point_applications (list): The list of end point applications for this endpoint application group. + + Returns: + tuple: A tuple containing the updated EndpointApplicationGroups instance, response, and error. + + Examples: + Update an existing endpoint application group: + + >>> updated_group, _, error = client.zia.endpoint_application_groups.update_group( + ... group_id=1013, + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating endpoint application group: {error}") + ... return + ... print(f"Endpoint application group updated successfully: {updated_group.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups/{group_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointApplicationGroups) + if error: + return (None, response, error) + + try: + result = EndpointApplicationGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_group(self, group_id: int) -> APIResult[None]: + """ + Deletes the specified endpoint application group. + + Args: + group_id (int): The unique identifier for the endpoint application group. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a endpoint application group: + + >>> _, _, error = client.zia.endpoint_application_groups.delete_group(1013) + >>> if error: + ... print(f"Error deleting endpoint application group: {error}") + ... return + ... print(f"Endpoint application group deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups/{group_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def update_application_group_resources(self, group_id: int, **kwargs) -> APIResult[dict]: + """ + Associates endpoint applications with, or removes them from, the specified application group. Pass ``resources_to_be_added`` and/or ``resources_to_be_deleted`` lists of application resource IDs. + + Args: + group_id (int): The unique identifier for the endpoint application group. + + Returns: + tuple: A tuple containing (result, Response, error). + + Examples: + >>> result, _, error = client.zia.endpoint_application_groups.update_application_group_resources( + ... 1013, + ... ) + >>> if error: + ... print(f"Error calling update_application_group_resources: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups/{group_id}/resources + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_application_group_policies( + self, query_params: Optional[dict] = None + ) -> APIResult[List[EndpointApplicationsPolicies]]: + """ + Lists the policy rules currently associated with the endpoint application group(s) identified by the given resource IDs. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.resourceId]`` {list}: One or more application group resource IDs (repeated query parameter). + + Returns: + tuple: A tuple containing (list of EndpointApplicationsPolicies instances, Response, error) + + Examples: + List endpoint application groups: + + >>> group_list, _, error = client.zia.endpoint_application_groups.get_application_group_policies() + >>> if error: + ... print(f"Error listing endpoint application groups: {error}") + ... return + ... print(f"Total endpoint application groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + List endpoint application groups using filters: + + >>> group_list, _, error = client.zia.endpoint_application_groups.get_application_group_policies( + ... query_params={'resourceId': 'VALUE'}) + >>> if error: + ... print(f"Error listing endpoint application groups: {error}") + ... return + ... print(f"Total endpoint application groups found: {len(group_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplicationGroups/policies + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationsPolicies(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/endpoint_applications.py b/zscaler/zia/endpoint_applications.py new file mode 100644 index 00000000..d1c3267c --- /dev/null +++ b/zscaler/zia/endpoint_applications.py @@ -0,0 +1,402 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.endpoint_applications_custom_apps import EndpointApplicationsCustomApps +from zscaler.zia.models.endpoint_applications_custom_apps_lite import EndpointApplicationsCustomAppsLite +from zscaler.zia.models.endpoint_applications_policies import EndpointApplicationsPolicies + + +class EndpointApplicationsAPI(APIClient): + """ + A Client object for the Endpoint Applications resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_applications(self, query_params: Optional[dict] = None) -> APIResult[List[EndpointApplicationsCustomApps]]: + """ + Lists the endpoint applications configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system (e.g. + ``[query_params.application_type]`` {str}: Filters the results by application type (e.g. + + Returns: + tuple: A tuple containing (list of EndpointApplicationsCustomApps instances, Response, error) + + Examples: + List endpoint applications: + + >>> application_list, _, error = client.zia.endpoint_applications.list_applications() + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + ... for application in application_list: + ... print(application.as_dict()) + + List endpoint applications using filters: + + >>> application_list, _, error = client.zia.endpoint_applications.list_applications( + ... query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationsCustomApps(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_applications_lite( + self, query_params: Optional[dict] = None + ) -> APIResult[List[EndpointApplicationsCustomAppsLite]]: + """ + Lists a lightweight version of the endpoint applications. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system (e.g. + ``[query_params.application_type]`` {str}: Filters the results by application type (e.g. + + Returns: + tuple: A tuple containing (list of EndpointApplicationsCustomAppsLite instances, Response, error) + + Examples: + List endpoint applications: + + >>> application_list, _, error = client.zia.endpoint_applications.list_applications_lite() + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + ... for application in application_list: + ... print(application.as_dict()) + + List endpoint applications using filters: + + >>> application_list, _, error = client.zia.endpoint_applications.list_applications_lite( + ... query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/lite + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationsCustomAppsLite(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_application_count(self, query_params: Optional[dict] = None) -> APIResult[dict]: + """ + Retrieves the count of all endpoint applications. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system. + ``[query_params.application_type]`` {str}: Filters the results by application type. + + Returns: + tuple: A tuple containing (int: the count of all endpoint applications, Response, error). + + Examples: + >>> result, _, error = client.zia.endpoint_applications.get_application_count() + >>> if error: + ... print(f"Error calling get_application_count: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/count + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_cloud_apps_count(self, query_params: Optional[dict] = None) -> APIResult[dict]: + """ + Retrieves the count of well-known and discovered endpoint applications as determined by the Zscaler service. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system. + ``[query_params.application_type]`` {str}: Filters the results by application type. + + Returns: + tuple: A tuple containing (int: the count of well-known and discovered endpoint applications, Response, error). + + Examples: + >>> result, _, error = client.zia.endpoint_applications.get_cloud_apps_count() + >>> if error: + ... print(f"Error calling get_cloud_apps_count: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/cloudApps/count + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_application_policies(self, query_params: Optional[dict] = None) -> APIResult[List[EndpointApplicationsPolicies]]: + """ + Lists the policy rules currently associated with the endpoint application(s) identified by the given resource IDs. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.resourceId]`` {list}: One or more endpoint application resource IDs (repeated query parameter). + + Returns: + tuple: A tuple containing (list of EndpointApplicationsPolicies instances, Response, error) + + Examples: + List endpoint applications: + + >>> application_list, _, error = client.zia.endpoint_applications.get_application_policies() + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + ... for application in application_list: + ... print(application.as_dict()) + + List endpoint applications using filters: + + >>> application_list, _, error = client.zia.endpoint_applications.get_application_policies( + ... query_params={'resourceId': 'VALUE'}) + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/policies + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationsPolicies(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_categories_with_non_empty_apps(self, query_params: Optional[dict] = None) -> APIResult[list]: + """ + Lists the categories that currently have endpoint applications grouped within them. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system. + + Returns: + tuple: A tuple containing (list of category name strings, Response, error) + + Examples: + List endpoint applications: + + >>> application_list, _, error = client.zia.endpoint_applications.list_categories_with_non_empty_apps() + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + ... for application in application_list: + ... print(application.as_dict()) + + List endpoint applications using filters: + + >>> application_list, _, error = client.zia.endpoint_applications.list_categories_with_non_empty_apps( + ... query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing endpoint applications: {error}") + ... return + ... print(f"Total endpoint applications found: {len(application_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/getCategoriesWithNonEmptyApps + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = response.get_results() + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/endpoint_custom_apps.py b/zscaler/zia/endpoint_custom_apps.py new file mode 100644 index 00000000..cb075df4 --- /dev/null +++ b/zscaler/zia/endpoint_custom_apps.py @@ -0,0 +1,310 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.dlp_endpoint_resource import DlpEndpointResource +from zscaler.zia.models.endpoint_applications_custom_apps import EndpointApplicationsCustomApps + + +class EndpointCustomAppsAPI(APIClient): + """ + A Client object for the Endpoint Custom Apps resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_custom_apps(self, query_params: Optional[dict] = None) -> APIResult[List[EndpointApplicationsCustomApps]]: + """ + Lists the custom endpoint applications configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {str}: Search string used to match against application names. + ``[query_params.os_type]`` {str}: Filters the results by operating system (e.g. + + Returns: + tuple: A tuple containing (list of EndpointApplicationsCustomApps instances, Response, error) + + Examples: + List custom endpoint applications: + + >>> custom_app_list, _, error = client.zia.endpoint_custom_apps.list_custom_apps() + >>> if error: + ... print(f"Error listing custom endpoint applications: {error}") + ... return + ... print(f"Total custom endpoint applications found: {len(custom_app_list)}") + ... for custom_app in custom_app_list: + ... print(custom_app.as_dict()) + + List custom endpoint applications using filters: + + >>> custom_app_list, _, error = client.zia.endpoint_custom_apps.list_custom_apps( + ... query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing custom endpoint applications: {error}") + ... return + ... print(f"Total custom endpoint applications found: {len(custom_app_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/customApps + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointApplicationsCustomApps(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_custom_app(self, app_id: int) -> APIResult[EndpointApplicationsCustomApps]: + """ + Fetches a specific custom endpoint application by ID. + + Args: + app_id (int): The unique identifier for the custom endpoint application. + + Returns: + tuple: A tuple containing (EndpointApplicationsCustomApps instance, Response, error). + + Examples: + Print a specific custom endpoint application: + + >>> fetched_custom_app, _, error = client.zia.endpoint_custom_apps.get_custom_app(1013) + >>> if error: + ... print(f"Error fetching custom endpoint application by ID: {error}") + ... return + ... print(f"Fetched custom endpoint application by ID: {fetched_custom_app.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/customApp/{app_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointApplicationsCustomApps) + if error: + return (None, response, error) + + try: + result = EndpointApplicationsCustomApps(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_custom_app(self, **kwargs) -> APIResult[DlpEndpointResource]: + """ + Creates a new custom endpoint application. + + Args: + name (str): The name of the custom endpoint application. + **kwargs: Optional keyword args. + + Keyword Args: + resource_id (int): The resource id for this custom endpoint application. + description (str): Additional information about the custom endpoint application. + os_type (str): The os type for this custom endpoint application. Accepted values include e.g. ``ANY``. + application_name (str): The application name for this custom endpoint application. + bundle_id (str): The bundle id for this custom endpoint application. + filename (str): The filename for this custom endpoint application. + original_file_name (str): The original file name for this custom endpoint application. + digitally_signed (bool): A Boolean value indicating whether digitally signed applies to this custom endpoint application. + mod_uid (int): The mod uid for this custom endpoint application. + application_type (str): The application type for this custom endpoint application. Accepted values include e.g. ``WELLKNOWN``. + zapp_id (str): The zapp id for this custom endpoint application. + deleted (bool): A Boolean value indicating whether deleted applies to this custom endpoint application. + versions (list): The list of versions for this custom endpoint application. + version (dict): The version configuration for this custom endpoint application. + + Returns: + tuple: A tuple containing the newly added DlpEndpointResource instance, response, and error. + + Examples: + Add a new custom endpoint application: + + >>> added_custom_app, _, error = client.zia.endpoint_custom_apps.add_custom_app( + ... name=f"NewCustomApp_{random.randint(1000, 10000)}", + ... description=f"NewCustomApp_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding custom endpoint application: {error}") + ... return + ... print(f"Custom endpoint application added successfully: {added_custom_app.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/customApp + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DlpEndpointResource) + if error: + return (None, response, error) + + try: + result = DlpEndpointResource(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_custom_app(self, app_id: int, **kwargs) -> APIResult[DlpEndpointResource]: + """ + Updates information for the specified custom endpoint application. + + Args: + app_id (int): The unique identifier for the custom endpoint application. + + Keyword Args: + name (str): The name of the custom endpoint application. + resource_id (int): The resource id for this custom endpoint application. + description (str): Additional information about the custom endpoint application. + os_type (str): The os type for this custom endpoint application. Accepted values include e.g. ``ANY``. + application_name (str): The application name for this custom endpoint application. + bundle_id (str): The bundle id for this custom endpoint application. + filename (str): The filename for this custom endpoint application. + original_file_name (str): The original file name for this custom endpoint application. + digitally_signed (bool): A Boolean value indicating whether digitally signed applies to this custom endpoint application. + mod_uid (int): The mod uid for this custom endpoint application. + application_type (str): The application type for this custom endpoint application. Accepted values include e.g. ``WELLKNOWN``. + zapp_id (str): The zapp id for this custom endpoint application. + deleted (bool): A Boolean value indicating whether deleted applies to this custom endpoint application. + versions (list): The list of versions for this custom endpoint application. + version (dict): The version configuration for this custom endpoint application. + + Returns: + tuple: A tuple containing the updated DlpEndpointResource instance, response, and error. + + Examples: + Update an existing custom endpoint application: + + >>> updated_custom_app, _, error = client.zia.endpoint_custom_apps.update_custom_app( + ... app_id=1013, + ... name=f"UpdatedCustomApp_{random.randint(1000, 10000)}", + ... description=f"UpdatedCustomApp_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating custom endpoint application: {error}") + ... return + ... print(f"Custom endpoint application updated successfully: {updated_custom_app.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/customApp/{app_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, DlpEndpointResource) + if error: + return (None, response, error) + + try: + result = DlpEndpointResource(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_custom_app(self, app_id: int) -> APIResult[None]: + """ + Deletes the specified custom endpoint application. + + Args: + app_id (int): The unique identifier for the custom endpoint application. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a custom endpoint application: + + >>> _, _, error = client.zia.endpoint_custom_apps.delete_custom_app(1013) + >>> if error: + ... print(f"Error deleting custom endpoint application: {error}") + ... return + ... print(f"Custom endpoint application deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointApplications/customApp/{app_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zia/endpoint_dlp_resource_groups.py b/zscaler/zia/endpoint_dlp_resource_groups.py new file mode 100644 index 00000000..f873f20f --- /dev/null +++ b/zscaler/zia/endpoint_dlp_resource_groups.py @@ -0,0 +1,407 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.dlp_endpoint_resource import DlpEndpointResource +from zscaler.zia.models.endpoint_dlp_resource_groups import EndpointDlpResourceGroups + + +class EndpointDLPResourceGroupsAPI(APIClient): + """ + A Client object for the Endpoint DLP Resource Groups resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def add_group(self, **kwargs) -> APIResult[EndpointDlpResourceGroups]: + """ + Creates a new endpoint DLP resource group. + + Args: + name (str): The name of the endpoint DLP resource group. + **kwargs: Optional keyword args. + + Keyword Args: + channel (str): The channel for this endpoint DLP resource group. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP resource group. + resource_count (int): The resource count for this endpoint DLP resource group. + resources (list): The IDs for the resources that this endpoint DLP resource group applies to. + + Returns: + tuple: A tuple containing the newly added EndpointDlpResourceGroups instance, response, and error. + + Examples: + Add a new endpoint DLP resource group: + + >>> added_group, _, error = client.zia.endpoint_dlp_resource_groups.add_group( + ... name=f"NewGroup_{random.randint(1000, 10000)}", + ... description=f"NewGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding endpoint DLP resource group: {error}") + ... return + ... print(f"Endpoint dlp resource group added successfully: {added_group.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointDlpResourceGroups) + if error: + return (None, response, error) + + try: + result = EndpointDlpResourceGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_group(self, group_id: int, **kwargs) -> APIResult[EndpointDlpResourceGroups]: + """ + Updates information for the specified endpoint DLP resource group. + + Args: + group_id (int): The unique identifier for the endpoint DLP resource group. + + Keyword Args: + name (str): The name of the endpoint DLP resource group. + channel (str): The channel for this endpoint DLP resource group. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP resource group. + resource_count (int): The resource count for this endpoint DLP resource group. + resources (list): The IDs for the resources that this endpoint DLP resource group applies to. + + Returns: + tuple: A tuple containing the updated EndpointDlpResourceGroups instance, response, and error. + + Examples: + Update an existing endpoint DLP resource group: + + >>> updated_group, _, error = client.zia.endpoint_dlp_resource_groups.update_group( + ... group_id=1013, + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating endpoint DLP resource group: {error}") + ... return + ... print(f"Endpoint dlp resource group updated successfully: {updated_group.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups/{group_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointDlpResourceGroups) + if error: + return (None, response, error) + + try: + result = EndpointDlpResourceGroups(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_group(self, group_id: int) -> APIResult[None]: + """ + Deletes the specified endpoint DLP resource group. + + Args: + group_id (int): The unique identifier for the endpoint DLP resource group. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a endpoint DLP resource group: + + >>> _, _, error = client.zia.endpoint_dlp_resource_groups.delete_group(1013) + >>> if error: + ... print(f"Error deleting endpoint DLP resource group: {error}") + ... return + ... print(f"Endpoint dlp resource group deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups/{group_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def get_resource_group_tags(self, dlp_resource_id: int, query_params: Optional[dict] = None) -> APIResult[list]: + """ + Retrieves the resource group tags associated with the specified DLP endpoint resource. + + Args: + dlp_resource_id (int): The unique identifier for the DLP endpoint resource. + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of endpoint DLP resource groups, Response, error) + + Examples: + List endpoint DLP resource groups: + + >>> group_list, _, error = client.zia.endpoint_dlp_resource_groups.get_resource_group_tags(1013) + >>> if error: + ... print(f"Error listing endpoint DLP resource groups: {error}") + ... return + ... print(f"Total endpoint DLP resource groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /dlpEndpointResource/{dlp_resource_id}/groups + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = response.get_results() + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_resource_group_tags( + self, channel: str, query_params: Optional[dict] = None + ) -> APIResult[List[EndpointDlpResourceGroups]]: + """ + Lists the DLP resource tag groups configured for the specified channel. + + Args: + channel (str): The DLP endpoint resource channel (e.g. ``PRINTING``). + query_params {dict}: Map of query parameters for the request. + ``[query_params.name]`` {str}: Search string used to filter the list by DLP resource name or other fields. + ``[query_params.sort_order]`` {str}: Sorting order for the list by ascending or descending order of the DLP resource tag names. + ``[query_params.search_resources]`` {bool}: Must be set to true to include search strings via the name parameter. + + Returns: + tuple: A tuple containing (list of EndpointDlpResourceGroups instances, Response, error) + + Examples: + List endpoint DLP resource groups: + + >>> group_list, _, error = client.zia.endpoint_dlp_resource_groups.list_resource_group_tags('PRINTING') + >>> if error: + ... print(f"Error listing endpoint DLP resource groups: {error}") + ... return + ... print(f"Total endpoint DLP resource groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + List endpoint DLP resource groups using filters: + + >>> group_list, _, error = client.zia.endpoint_dlp_resource_groups.list_resource_group_tags( + ... 'PRINTING', query_params={'name': 'VALUE'}) + >>> if error: + ... print(f"Error listing endpoint DLP resource groups: {error}") + ... return + ... print(f"Total endpoint DLP resource groups found: {len(group_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups/{channel} + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointDlpResourceGroups(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_dlp_resources_by_tag( + self, group_id: int, query_params: Optional[dict] = None + ) -> APIResult[List[DlpEndpointResource]]: + """ + Lists the DLP resources associated with the specified tag group. + + Args: + group_id (int): The unique identifier for the tag group. + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of DlpEndpointResource instances, Response, error) + + Examples: + List endpoint DLP resource groups: + + >>> group_list, _, error = client.zia.endpoint_dlp_resource_groups.get_dlp_resources_by_tag(1013) + >>> if error: + ... print(f"Error listing endpoint DLP resource groups: {error}") + ... return + ... print(f"Total endpoint DLP resource groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups/{group_id}/resources + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(DlpEndpointResource(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_dlp_resources_by_tag(self, group_id: int, **kwargs) -> APIResult[dict]: + """ + Associates DLP resources with, or removes them from, the specified tag group. Pass ``resources_to_be_added`` and/or ``resources_to_be_deleted`` lists of DLP resource IDs. + + Args: + group_id (int): The unique identifier for the endpoint DLP resource group. + + Returns: + tuple: A tuple containing (result, Response, error). + + Examples: + >>> result, _, error = client.zia.endpoint_dlp_resource_groups.update_dlp_resources_by_tag( + ... 1013, + ... ) + >>> if error: + ... print(f"Error calling update_dlp_resources_by_tag: {error}") + ... return + ... print(f"Result: {result}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpResourceGroups/{group_id}/resources + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + + try: + result = self.form_response_body(response.get_body()) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/endpoint_dlp_rules.py b/zscaler/zia/endpoint_dlp_rules.py new file mode 100644 index 00000000..89697b66 --- /dev/null +++ b/zscaler/zia/endpoint_dlp_rules.py @@ -0,0 +1,410 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.endpoint_dlp_rules import EndpointDlpRules + + +class EndpointDLPRulesAPI(APIClient): + """ + A Client object for the Endpoint DLP Rules resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_rules(self, query_params: Optional[dict] = None) -> APIResult[List[EndpointDlpRules]]: + """ + Lists the endpoint DLP rules configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of EndpointDlpRules instances, Response, error) + + Examples: + List endpoint DLP rules: + + >>> rule_list, _, error = client.zia.endpoint_dlp_rules.list_rules() + >>> if error: + ... print(f"Error listing endpoint DLP rules: {error}") + ... return + ... print(f"Total endpoint DLP rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(EndpointDlpRules(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_rule(self, rule_id: int) -> APIResult[EndpointDlpRules]: + """ + Fetches a specific endpoint DLP rule by ID. + + Args: + rule_id (int): The unique identifier for the endpoint DLP rule. + + Returns: + tuple: A tuple containing (EndpointDlpRules instance, Response, error). + + Examples: + Print a specific endpoint DLP rule: + + >>> fetched_rule, _, error = client.zia.endpoint_dlp_rules.get_rule(1013) + >>> if error: + ... print(f"Error fetching endpoint DLP rule by ID: {error}") + ... return + ... print(f"Fetched endpoint DLP rule by ID: {fetched_rule.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointDlpRules) + if error: + return (None, response, error) + + try: + result = EndpointDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_rule(self, **kwargs) -> APIResult[EndpointDlpRules]: + """ + Creates a new endpoint DLP rule. + + Args: + name (str): The name of the endpoint DLP rule. + **kwargs: Optional keyword args. + + Keyword Args: + state (str): The endpoint DLP rule state. Accepted values are 'ENABLED' or 'DISABLED'. + order (int): The order of the endpoint DLP rule, defaults to adding the endpoint DLP rule to the bottom of the list. + rank (int): The admin rank of the endpoint DLP rule. + file_types (str): The file types for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + data_transfer_method (str): The data transfer method for this endpoint DLP rule. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP rule. + min_size (int): The min size for this endpoint DLP rule. + action (str): The action taken when traffic matches the endpoint DLP rule criteria. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + parent_rule (int): The parent rule for this endpoint DLP rule. + severity (str): The severity level assigned to the endpoint DLP rule. + eun_enabled (bool): A Boolean value indicating whether eun is enabled for this endpoint DLP rule. + eun_template_id (int): The eun template id for this endpoint DLP rule. + uc_template_id (int): The uc template id for this endpoint DLP rule. + network_type (str): The network type for this endpoint DLP rule. Accepted values include e.g. ``TRUSTED``. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this endpoint DLP rule. + dlp_engines (list): The IDs for the dlp engines that this endpoint DLP rule applies to. + users (list): The IDs for the users that this endpoint DLP rule applies to. + groups (list): The IDs for the groups that this endpoint DLP rule applies to. + departments (list): The IDs for the departments that this endpoint DLP rule applies to. + devices (list): The IDs for the devices that this endpoint DLP rule applies to. + device_groups (list): The IDs for the device groups that this endpoint DLP rule applies to. + device_trust_levels (list): The list of device trust levels for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + time_windows (list): The IDs for the time windows that this endpoint DLP rule applies to. + labels (list): The IDs for the labels that this endpoint DLP rule applies to. + end_point_applications (list): The list of end point applications for this endpoint DLP rule. + end_point_application_groups (list): The list of end point application groups for this endpoint DLP rule. + resources (list): The IDs for the resources that this endpoint DLP rule applies to. + resource_groups (list): The IDs for the resource groups that this endpoint DLP rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + sub_rules (list): The IDs for the sub rules that this endpoint DLP rule applies to. + notification_template (dict): The ID of the notification template for this endpoint DLP rule, e.g. ``{'id': 12345}``. + auditor (dict): The ID of the auditor for this endpoint DLP rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this endpoint DLP rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the newly added EndpointDlpRules instance, response, and error. + + Examples: + Add a new endpoint DLP rule: + + >>> added_rule, _, error = client.zia.endpoint_dlp_rules.add_rule( + ... name=f"NewRule_{random.randint(1000, 10000)}", + ... description=f"NewRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... order=1, + ... rank=7, + ... device_trust_levels=['ANY'], + ... ) + >>> if error: + ... print(f"Error adding endpoint DLP rule: {error}") + ... return + ... print(f"Endpoint dlp rule added successfully: {added_rule.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointDlpRules) + if error: + return (None, response, error) + + try: + result = EndpointDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_rule(self, rule_id: int, **kwargs) -> APIResult[EndpointDlpRules]: + """ + Updates information for the specified endpoint DLP rule. + + Args: + rule_id (int): The unique identifier for the endpoint DLP rule. + + Keyword Args: + name (str): The name of the endpoint DLP rule. + state (str): The endpoint DLP rule state. Accepted values are 'ENABLED' or 'DISABLED'. + order (int): The order of the endpoint DLP rule, defaults to adding the endpoint DLP rule to the bottom of the list. + rank (int): The admin rank of the endpoint DLP rule. + file_types (str): The file types for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + data_transfer_method (str): The data transfer method for this endpoint DLP rule. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP rule. + min_size (int): The min size for this endpoint DLP rule. + action (str): The action taken when traffic matches the endpoint DLP rule criteria. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + parent_rule (int): The parent rule for this endpoint DLP rule. + severity (str): The severity level assigned to the endpoint DLP rule. + eun_enabled (bool): A Boolean value indicating whether eun is enabled for this endpoint DLP rule. + eun_template_id (int): The eun template id for this endpoint DLP rule. + uc_template_id (int): The uc template id for this endpoint DLP rule. + network_type (str): The network type for this endpoint DLP rule. Accepted values include e.g. ``TRUSTED``. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this endpoint DLP rule. + dlp_engines (list): The IDs for the dlp engines that this endpoint DLP rule applies to. + users (list): The IDs for the users that this endpoint DLP rule applies to. + groups (list): The IDs for the groups that this endpoint DLP rule applies to. + departments (list): The IDs for the departments that this endpoint DLP rule applies to. + devices (list): The IDs for the devices that this endpoint DLP rule applies to. + device_groups (list): The IDs for the device groups that this endpoint DLP rule applies to. + device_trust_levels (list): The list of device trust levels for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + time_windows (list): The IDs for the time windows that this endpoint DLP rule applies to. + labels (list): The IDs for the labels that this endpoint DLP rule applies to. + end_point_applications (list): The list of end point applications for this endpoint DLP rule. + end_point_application_groups (list): The list of end point application groups for this endpoint DLP rule. + resources (list): The IDs for the resources that this endpoint DLP rule applies to. + resource_groups (list): The IDs for the resource groups that this endpoint DLP rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this endpoint DLP rule. Accepted values include e.g. ``ANY``. + sub_rules (list): The IDs for the sub rules that this endpoint DLP rule applies to. + notification_template (dict): The ID of the notification template for this endpoint DLP rule, e.g. ``{'id': 12345}``. + auditor (dict): The ID of the auditor for this endpoint DLP rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this endpoint DLP rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the updated EndpointDlpRules instance, response, and error. + + Examples: + Update an existing endpoint DLP rule: + + >>> updated_rule, _, error = client.zia.endpoint_dlp_rules.update_rule( + ... rule_id=1013, + ... name=f"UpdatedRule_{random.randint(1000, 10000)}", + ... description=f"UpdatedRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... ) + >>> if error: + ... print(f"Error updating endpoint DLP rule: {error}") + ... return + ... print(f"Endpoint dlp rule updated successfully: {updated_rule.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, EndpointDlpRules) + if error: + return (None, response, error) + + try: + result = EndpointDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_rule(self, rule_id: int) -> APIResult[None]: + """ + Deletes the specified endpoint DLP rule. + + Args: + rule_id (int): The unique identifier for the endpoint DLP rule. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a endpoint DLP rule: + + >>> _, _, error = client.zia.endpoint_dlp_rules.delete_rule(1013) + >>> if error: + ... print(f"Error deleting endpoint DLP rule: {error}") + ... return + ... print(f"Endpoint dlp rule deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def list_file_type_categories(self, query_params: Optional[dict] = None) -> APIResult[list]: + """ + Lists the file types available in the Endpoint DLP policy rule criteria. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.external]`` {bool}: When true, retrieves the file types available when DLP engines are used with Content Matching enabled; when false, the set available when DLP engines are not used. + + Returns: + tuple: A tuple containing (list of endpoint DLP rules, Response, error) + + Examples: + List endpoint DLP rules: + + >>> rule_list, _, error = client.zia.endpoint_dlp_rules.list_file_type_categories() + >>> if error: + ... print(f"Error listing endpoint DLP rules: {error}") + ... return + ... print(f"Total endpoint DLP rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + List endpoint DLP rules using filters: + + >>> rule_list, _, error = client.zia.endpoint_dlp_rules.list_file_type_categories( + ... query_params={'external': 'VALUE'}) + >>> if error: + ... print(f"Error listing endpoint DLP rules: {error}") + ... return + ... print(f"Total endpoint DLP rules found: {len(rule_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/fileTypeCategories + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = response.get_results() + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/endpoint_dlp_sub_rules.py b/zscaler/zia/endpoint_dlp_sub_rules.py new file mode 100644 index 00000000..27de5fdf --- /dev/null +++ b/zscaler/zia/endpoint_dlp_sub_rules.py @@ -0,0 +1,246 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.endpoint_dlp_rules import SubRule + + +class EndpointDLPSubRulesAPI(APIClient): + """ + A Client object for the Endpoint DLP Sub-Rules resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def add_sub_rule(self, rule_id: int, **kwargs) -> APIResult[SubRule]: + """ + Creates a new sub-rule under the specified parent Endpoint DLP rule. + + Args: + rule_id (int): The unique identifier for the parent Endpoint DLP rule. + name (str): The name of the endpoint DLP sub-rule. + **kwargs: Optional keyword args. + + Keyword Args: + state (str): The endpoint DLP sub-rule state. Accepted values are 'ENABLED' or 'DISABLED'. + order (int): The order of the endpoint DLP sub-rule, defaults to adding the endpoint DLP sub-rule to the bottom of the list. + rank (int): The admin rank of the endpoint DLP sub-rule. + file_types (str): The file types for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + data_transfer_method (str): The data transfer method for this endpoint DLP sub-rule. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP sub-rule. + min_size (int): The min size for this endpoint DLP sub-rule. + action (str): The action taken when traffic matches the endpoint DLP sub-rule criteria. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + parent_rule (int): The parent rule for this endpoint DLP sub-rule. + severity (str): The severity level assigned to the endpoint DLP sub-rule. + eun_enabled (bool): A Boolean value indicating whether eun is enabled for this endpoint DLP sub-rule. + eun_template_id (int): The eun template id for this endpoint DLP sub-rule. + uc_template_id (int): The uc template id for this endpoint DLP sub-rule. + network_type (str): The network type for this endpoint DLP sub-rule. Accepted values include e.g. ``TRUSTED``. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this endpoint DLP sub-rule. + dlp_engines (list): The IDs for the dlp engines that this endpoint DLP sub-rule applies to. + users (list): The IDs for the users that this endpoint DLP sub-rule applies to. + groups (list): The IDs for the groups that this endpoint DLP sub-rule applies to. + departments (list): The IDs for the departments that this endpoint DLP sub-rule applies to. + devices (list): The IDs for the devices that this endpoint DLP sub-rule applies to. + device_groups (list): The IDs for the device groups that this endpoint DLP sub-rule applies to. + device_trust_levels (list): The list of device trust levels for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + time_windows (list): The IDs for the time windows that this endpoint DLP sub-rule applies to. + labels (list): The IDs for the labels that this endpoint DLP sub-rule applies to. + end_point_applications (list): The list of end point applications for this endpoint DLP sub-rule. + end_point_application_groups (list): The list of end point application groups for this endpoint DLP sub-rule. + resources (list): The IDs for the resources that this endpoint DLP sub-rule applies to. + resource_groups (list): The IDs for the resource groups that this endpoint DLP sub-rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + notification_template (dict): The ID of the notification template for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + auditor (dict): The ID of the auditor for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the newly added SubRule instance, response, and error. + + Examples: + Add a new endpoint DLP sub-rule: + + >>> added_sub_rule, _, error = client.zia.endpoint_dlp_sub_rules.add_sub_rule( + ... 1013, + ... name=f"NewSubRule_{random.randint(1000, 10000)}", + ... description=f"NewSubRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... order=1, + ... rank=7, + ... device_trust_levels=['ANY'], + ... ) + >>> if error: + ... print(f"Error adding endpoint DLP sub-rule: {error}") + ... return + ... print(f"Endpoint dlp sub-rule added successfully: {added_sub_rule.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id}/subRule + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, SubRule) + if error: + return (None, response, error) + + try: + result = SubRule(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_sub_rule(self, rule_id: int, sub_rule_id: int, **kwargs) -> APIResult[SubRule]: + """ + Updates the specified sub-rule of the parent Endpoint DLP rule. + + Args: + rule_id (int): The unique identifier for the parent Endpoint DLP rule. + sub_rule_id (int): The unique identifier for the endpoint DLP sub-rule. + + Keyword Args: + name (str): The name of the endpoint DLP sub-rule. + state (str): The endpoint DLP sub-rule state. Accepted values are 'ENABLED' or 'DISABLED'. + order (int): The order of the endpoint DLP sub-rule, defaults to adding the endpoint DLP sub-rule to the bottom of the list. + rank (int): The admin rank of the endpoint DLP sub-rule. + file_types (str): The file types for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + data_transfer_method (str): The data transfer method for this endpoint DLP sub-rule. Accepted values include e.g. ``NETWORK_DRIVE_TRANSFER``. + description (str): Additional information about the endpoint DLP sub-rule. + min_size (int): The min size for this endpoint DLP sub-rule. + action (str): The action taken when traffic matches the endpoint DLP sub-rule criteria. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + parent_rule (int): The parent rule for this endpoint DLP sub-rule. + severity (str): The severity level assigned to the endpoint DLP sub-rule. + eun_enabled (bool): A Boolean value indicating whether eun is enabled for this endpoint DLP sub-rule. + eun_template_id (int): The eun template id for this endpoint DLP sub-rule. + uc_template_id (int): The uc template id for this endpoint DLP sub-rule. + network_type (str): The network type for this endpoint DLP sub-rule. Accepted values include e.g. ``TRUSTED``. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this endpoint DLP sub-rule. + dlp_engines (list): The IDs for the dlp engines that this endpoint DLP sub-rule applies to. + users (list): The IDs for the users that this endpoint DLP sub-rule applies to. + groups (list): The IDs for the groups that this endpoint DLP sub-rule applies to. + departments (list): The IDs for the departments that this endpoint DLP sub-rule applies to. + devices (list): The IDs for the devices that this endpoint DLP sub-rule applies to. + device_groups (list): The IDs for the device groups that this endpoint DLP sub-rule applies to. + device_trust_levels (list): The list of device trust levels for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + time_windows (list): The IDs for the time windows that this endpoint DLP sub-rule applies to. + labels (list): The IDs for the labels that this endpoint DLP sub-rule applies to. + end_point_applications (list): The list of end point applications for this endpoint DLP sub-rule. + end_point_application_groups (list): The list of end point application groups for this endpoint DLP sub-rule. + resources (list): The IDs for the resources that this endpoint DLP sub-rule applies to. + resource_groups (list): The IDs for the resource groups that this endpoint DLP sub-rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this endpoint DLP sub-rule. Accepted values include e.g. ``ANY``. + notification_template (dict): The ID of the notification template for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + auditor (dict): The ID of the auditor for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this endpoint DLP sub-rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the updated SubRule instance, response, and error. + + Examples: + Update an existing endpoint DLP sub-rule: + + >>> updated_sub_rule, _, error = client.zia.endpoint_dlp_sub_rules.update_sub_rule( + ... 1013, + ... sub_rule_id=1013, + ... name=f"UpdatedSubRule_{random.randint(1000, 10000)}", + ... description=f"UpdatedSubRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... ) + >>> if error: + ... print(f"Error updating endpoint DLP sub-rule: {error}") + ... return + ... print(f"Endpoint dlp sub-rule updated successfully: {updated_sub_rule.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id}/subRule/{sub_rule_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, SubRule) + if error: + return (None, response, error) + + try: + result = SubRule(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_sub_rule(self, rule_id: int, sub_rule_id: int) -> APIResult[None]: + """ + Deletes the specified sub-rule of the parent Endpoint DLP rule. + + Args: + rule_id (int): The unique identifier for the parent Endpoint DLP rule. + sub_rule_id (int): The unique identifier for the endpoint DLP sub-rule. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a endpoint DLP sub-rule: + + >>> _, _, error = client.zia.endpoint_dlp_sub_rules.delete_sub_rule(1013, 1013) + >>> if error: + ... print(f"Error deleting endpoint DLP sub-rule: {error}") + ... return + ... print(f"Endpoint dlp sub-rule deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /endPointDlpRules/{rule_id}/subRule/{sub_rule_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zia/http_header_control.py b/zscaler/zia/http_header_control.py index e68d2bd8..134f1d3f 100644 --- a/zscaler/zia/http_header_control.py +++ b/zscaler/zia/http_header_control.py @@ -31,8 +31,7 @@ def __init__(self, request_executor: "RequestExecutor") -> None: super().__init__() self._request_executor: RequestExecutor = request_executor - def list_http_header_action_profiles( - self, query_params=None) -> APIResult[List[HttpHeaderActionProfile]]: + def list_http_header_action_profiles(self, query_params=None) -> APIResult[List[HttpHeaderActionProfile]]: """ List http_header_action_profiles. @@ -53,8 +52,7 @@ def list_http_header_action_profiles( body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) diff --git a/zscaler/zia/ips_categories.py b/zscaler/zia/ips_categories.py new file mode 100644 index 00000000..c8b5c5a3 --- /dev/null +++ b/zscaler/zia/ips_categories.py @@ -0,0 +1,103 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.ips_categories import IpsCategories + + +class IpsCategoriesAPI(APIClient): + """ + A Client object for the IPS Categories resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_categories(self, query_params: Optional[dict] = None) -> APIResult[List[IpsCategories]]: + """ + Lists the advanced threat categories (predefined and custom) against which network traffic can be monitored using the IPS Control policy. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.page]`` {int}: Specifies the page offset. + ``[query_params.page_size]`` {int}: Specifies the page size. + + Returns: + tuple: A tuple containing (list of IpsCategories instances, Response, error) + + Examples: + List IPS threat categories: + + >>> category_list, _, error = client.zia.ips_categories.list_categories() + >>> if error: + ... print(f"Error listing IPS threat categories: {error}") + ... return + ... print(f"Total IPS threat categories found: {len(category_list)}") + ... for category in category_list: + ... print(category.as_dict()) + + List IPS threat categories using filters: + + >>> category_list, _, error = client.zia.ips_categories.list_categories( + ... query_params={'page': 'VALUE'}) + >>> if error: + ... print(f"Error listing IPS threat categories: {error}") + ... return + ... print(f"Total IPS threat categories found: {len(category_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /ipsCategories + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(IpsCategories(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/legacy.py b/zscaler/zia/legacy.py index 6688a8f2..1f902e30 100644 --- a/zscaler/zia/legacy.py +++ b/zscaler/zia/legacy.py @@ -122,6 +122,7 @@ from zscaler.zia.user_management import UserManagementAPI from zscaler.zia.vzen_clusters import VZENClustersAPI from zscaler.zia.vzen_nodes import VZENNodesAPI + from zscaler.zia.web_dlp_global_options import WebDlpGlobalOptionsAPI from zscaler.zia.workload_groups import WorkloadGroupsAPI from zscaler.zia.zpa_gateway import ZPAGatewayAPI @@ -781,6 +782,14 @@ def dlp_resources(self) -> "DLPResourcesAPI": return DLPResourcesAPI(self.request_executor) + @property + def web_dlp_global_options(self) -> WebDlpGlobalOptionsAPI: + """ + The interface object for the :ref:`ZIA DLP Advanced Settings information interface `. + + """ + return WebDlpGlobalOptionsAPI(self.request_executor) + @property def end_user_notification(self) -> "EndUserNotificationAPI": """ diff --git a/zscaler/zia/models/dlp_endpoint_resource.py b/zscaler/zia/models/dlp_endpoint_resource.py new file mode 100644 index 00000000..b3ac897c --- /dev/null +++ b/zscaler/zia/models/dlp_endpoint_resource.py @@ -0,0 +1,261 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class DlpEndpointResource(ZscalerObject): + """ + A class for DlpEndpointResource objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the DlpEndpointResource model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.channel = config["channel"] if "channel" in config else None + self.is_predefined = config["isPredefined"] if "isPredefined" in config else None + self.network_drive_type = config["networkDriveType"] if "networkDriveType" in config else None + self.description = config["description"] if "description" in config else None + self.server_name = config["serverName"] if "serverName" in config else None + self.app_id = config["appId"] if "appId" in config else None + self.network_drives = ZscalerCollection.form_list( + config["networkDrives"] if "networkDrives" in config else [], NetworkDrive + ) + + if "printer" in config: + if isinstance(config["printer"], Printer): + self.printer = config["printer"] + elif config["printer"] is not None: + self.printer = Printer(config["printer"]) + else: + self.printer = None + else: + self.printer = None + + if "removableStorage" in config: + if isinstance(config["removableStorage"], RemovableStorage): + self.removable_storage = config["removableStorage"] + elif config["removableStorage"] is not None: + self.removable_storage = RemovableStorage(config["removableStorage"]) + else: + self.removable_storage = None + else: + self.removable_storage = None + + if "application" in config: + if isinstance(config["application"], Application): + self.application = config["application"] + elif config["application"] is not None: + self.application = Application(config["application"]) + else: + self.application = None + else: + self.application = None + else: + self.id = None + self.name = None + self.channel = None + self.is_predefined = None + self.network_drive_type = None + self.description = None + self.server_name = None + self.app_id = None + self.network_drives = [] + self.printer = None + self.removable_storage = None + self.application = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "channel": self.channel, + "isPredefined": self.is_predefined, + "networkDriveType": self.network_drive_type, + "description": self.description, + "serverName": self.server_name, + "appId": self.app_id, + "networkDrives": [item.request_format() for item in (self.network_drives or [])], + "printer": self.printer, + "removableStorage": self.removable_storage, + "application": self.application, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Printer(ZscalerObject): + """ + A class for Printer objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Printer model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.unc = config["unc"] if "unc" in config else None + self.ip_address = config["ipAddress"] if "ipAddress" in config else None + self.domain = config["domain"] if "domain" in config else None + else: + self.unc = None + self.ip_address = None + self.domain = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "unc": self.unc, + "ipAddress": self.ip_address, + "domain": self.domain, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class NetworkDrive(ZscalerObject): + """ + A class for NetworkDrive objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the NetworkDrive model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.network_path = config["networkPath"] if "networkPath" in config else None + else: + self.network_path = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "networkPath": self.network_path, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class RemovableStorage(ZscalerObject): + """ + A class for RemovableStorage objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the RemovableStorage model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.vendor_id = config["vendorId"] if "vendorId" in config else None + self.product_id = config["productId"] if "productId" in config else None + self.serial_number = config["serialNumber"] if "serialNumber" in config else None + else: + self.vendor_id = None + self.product_id = None + self.serial_number = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "vendorId": self.vendor_id, + "productId": self.product_id, + "serialNumber": self.serial_number, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Application(ZscalerObject): + """ + A class for Application objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Application model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.os_type = config["osType"] if "osType" in config else None + self.file_name = config["fileName"] if "fileName" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + else: + self.os_type = None + self.file_name = None + self.original_file_name = None + self.bundle_id = None + self.digitally_signed = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "osType": self.os_type, + "fileName": self.file_name, + "originalFileName": self.original_file_name, + "bundleID": self.bundle_id, + "digitallySigned": self.digitally_signed, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/dns_application_groups.py b/zscaler/zia/models/dns_application_groups.py new file mode 100644 index 00000000..476e66e0 --- /dev/null +++ b/zscaler/zia/models/dns_application_groups.py @@ -0,0 +1,62 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class DnsApplicationGroups(ZscalerObject): + """ + A class for DnsApplicationGroups objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the DnsApplicationGroups model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.dns_applications = ZscalerCollection.form_list( + config["dnsApplications"] if "dnsApplications" in config else [], str + ) + else: + self.id = None + self.name = None + self.description = None + self.dns_applications = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "description": self.description, + "dnsApplications": self.dns_applications, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_application_groups.py b/zscaler/zia/models/endpoint_application_groups.py new file mode 100644 index 00000000..eb50a33d --- /dev/null +++ b/zscaler/zia/models/endpoint_application_groups.py @@ -0,0 +1,201 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EndpointApplicationGroups(ZscalerObject): + """ + A class for EndpointApplicationGroups objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointApplicationGroups model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.group_id = config["groupId"] if "groupId" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.mod_uid = config["modUId"] if "modUId" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.end_point_applications = ZscalerCollection.form_list( + config["endPointApplications"] if "endPointApplications" in config else [], EndPointApplication + ) + else: + self.group_id = None + self.name = None + self.description = None + self.mod_uid = None + self.last_modified_time = None + self.end_point_applications = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "groupId": self.group_id, + "name": self.name, + "description": self.description, + "modUId": self.mod_uid, + "lastModifiedTime": self.last_modified_time, + "endPointApplications": [item.request_format() for item in (self.end_point_applications or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class EndPointApplication(ZscalerObject): + """ + A class for EndPointApplication objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndPointApplication model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.resource_id = config["resourceId"] if "resourceId" in config else None + self.description = config["description"] if "description" in config else None + self.os_type = config["osType"] if "osType" in config else None + self.application_name = config["applicationName"] if "applicationName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.filename = config["filename"] if "filename" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + self.mod_uid = config["modUId"] if "modUId" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.application_type = config["applicationType"] if "applicationType" in config else None + self.zapp_id = config["zappId"] if "zappId" in config else None + self.deleted = config["deleted"] if "deleted" in config else None + self.versions = ZscalerCollection.form_list(config["versions"] if "versions" in config else [], Version) + + if "version" in config: + if isinstance(config["version"], Version): + self.version = config["version"] + elif config["version"] is not None: + self.version = Version(config["version"]) + else: + self.version = None + else: + self.version = None + else: + self.resource_id = None + self.description = None + self.os_type = None + self.application_name = None + self.bundle_id = None + self.filename = None + self.original_file_name = None + self.digitally_signed = None + self.mod_uid = None + self.last_modified_time = None + self.application_type = None + self.zapp_id = None + self.deleted = None + self.versions = [] + self.version = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "resourceId": self.resource_id, + "description": self.description, + "osType": self.os_type, + "applicationName": self.application_name, + "bundleID": self.bundle_id, + "filename": self.filename, + "originalFileName": self.original_file_name, + "digitallySigned": self.digitally_signed, + "modUId": self.mod_uid, + "lastModifiedTime": self.last_modified_time, + "applicationType": self.application_type, + "zappId": self.zapp_id, + "deleted": self.deleted, + "versions": [item.request_format() for item in (self.versions or [])], + "version": self.version, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Version(ZscalerObject): + """ + A class for Version objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Version model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.version = config["version"] if "version" in config else None + self.z_ver_id_md32 = config["z_ver_id_md32"] if "z_ver_id_md32" in config else None + self.threat_type = config["threat_type"] if "threat_type" in config else None + self.threat_level = config["threat_level"] if "threat_level" in config else None + self.bundle_id = config["bundle_id"] if "bundle_id" in config else None + self.code_signing_certificate_status = ( + config["code_signing_certificate_status"] if "code_signing_certificate_status" in config else None + ) + self.threat_level_updated = config["threatLevelUpdated"] if "threatLevelUpdated" in config else None + else: + self.version = None + self.z_ver_id_md32 = None + self.threat_type = None + self.threat_level = None + self.bundle_id = None + self.code_signing_certificate_status = None + self.threat_level_updated = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "version": self.version, + "z_ver_id_md32": self.z_ver_id_md32, + "threat_type": self.threat_type, + "threat_level": self.threat_level, + "bundle_id": self.bundle_id, + "code_signing_certificate_status": self.code_signing_certificate_status, + "threatLevelUpdated": self.threat_level_updated, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_applications_custom_apps.py b/zscaler/zia/models/endpoint_applications_custom_apps.py new file mode 100644 index 00000000..aac2a593 --- /dev/null +++ b/zscaler/zia/models/endpoint_applications_custom_apps.py @@ -0,0 +1,153 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EndpointApplicationsCustomApps(ZscalerObject): + """ + A class for EndpointApplicationsCustomApps objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointApplicationsCustomApps model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.resource_id = config["resourceId"] if "resourceId" in config else None + self.description = config["description"] if "description" in config else None + self.os_type = config["osType"] if "osType" in config else None + self.application_name = config["applicationName"] if "applicationName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.filename = config["filename"] if "filename" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + self.mod_uid = config["modUId"] if "modUId" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.application_type = config["applicationType"] if "applicationType" in config else None + self.zapp_id = config["zappId"] if "zappId" in config else None + self.deleted = config["deleted"] if "deleted" in config else None + self.versions = ZscalerCollection.form_list(config["versions"] if "versions" in config else [], Version) + + if "version" in config: + if isinstance(config["version"], Version): + self.version = config["version"] + elif config["version"] is not None: + self.version = Version(config["version"]) + else: + self.version = None + else: + self.version = None + else: + self.resource_id = None + self.description = None + self.os_type = None + self.application_name = None + self.bundle_id = None + self.filename = None + self.original_file_name = None + self.digitally_signed = None + self.mod_uid = None + self.last_modified_time = None + self.application_type = None + self.zapp_id = None + self.deleted = None + self.versions = [] + self.version = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "resourceId": self.resource_id, + "description": self.description, + "osType": self.os_type, + "applicationName": self.application_name, + "bundleID": self.bundle_id, + "filename": self.filename, + "originalFileName": self.original_file_name, + "digitallySigned": self.digitally_signed, + "modUId": self.mod_uid, + "lastModifiedTime": self.last_modified_time, + "applicationType": self.application_type, + "zappId": self.zapp_id, + "deleted": self.deleted, + "versions": [item.request_format() for item in (self.versions or [])], + "version": self.version, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Version(ZscalerObject): + """ + A class for Version objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Version model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.version = config["version"] if "version" in config else None + self.z_ver_id_md32 = config["z_ver_id_md32"] if "z_ver_id_md32" in config else None + self.threat_type = config["threat_type"] if "threat_type" in config else None + self.threat_level = config["threat_level"] if "threat_level" in config else None + self.bundle_id = config["bundle_id"] if "bundle_id" in config else None + self.code_signing_certificate_status = ( + config["code_signing_certificate_status"] if "code_signing_certificate_status" in config else None + ) + self.threat_level_updated = config["threatLevelUpdated"] if "threatLevelUpdated" in config else None + else: + self.version = None + self.z_ver_id_md32 = None + self.threat_type = None + self.threat_level = None + self.bundle_id = None + self.code_signing_certificate_status = None + self.threat_level_updated = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "version": self.version, + "z_ver_id_md32": self.z_ver_id_md32, + "threat_type": self.threat_type, + "threat_level": self.threat_level, + "bundle_id": self.bundle_id, + "code_signing_certificate_status": self.code_signing_certificate_status, + "threatLevelUpdated": self.threat_level_updated, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_applications_custom_apps_lite.py b/zscaler/zia/models/endpoint_applications_custom_apps_lite.py new file mode 100644 index 00000000..d706956a --- /dev/null +++ b/zscaler/zia/models/endpoint_applications_custom_apps_lite.py @@ -0,0 +1,83 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class EndpointApplicationsCustomAppsLite(ZscalerObject): + """ + A class for EndpointApplicationsCustomAppsLite objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointApplicationsCustomAppsLite model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.resource_id = config["resourceId"] if "resourceId" in config else None + self.os_type = config["osType"] if "osType" in config else None + self.z_ver_id_md32 = config["z_ver_id_md32"] if "z_ver_id_md32" in config else None + self.threat_level = config["threatLevel"] if "threatLevel" in config else None + self.application_name = config["applicationName"] if "applicationName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.filename = config["filename"] if "filename" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + self.application_type = config["applicationType"] if "applicationType" in config else None + self.deleted = config["deleted"] if "deleted" in config else None + self.zapp_id = config["zappId"] if "zappId" in config else None + else: + self.resource_id = None + self.os_type = None + self.z_ver_id_md32 = None + self.threat_level = None + self.application_name = None + self.bundle_id = None + self.filename = None + self.original_file_name = None + self.digitally_signed = None + self.application_type = None + self.deleted = None + self.zapp_id = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "resourceId": self.resource_id, + "osType": self.os_type, + "z_ver_id_md32": self.z_ver_id_md32, + "threatLevel": self.threat_level, + "applicationName": self.application_name, + "bundleID": self.bundle_id, + "filename": self.filename, + "originalFileName": self.original_file_name, + "digitallySigned": self.digitally_signed, + "applicationType": self.application_type, + "deleted": self.deleted, + "zappId": self.zapp_id, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_applications_policies.py b/zscaler/zia/models/endpoint_applications_policies.py new file mode 100644 index 00000000..984cf740 --- /dev/null +++ b/zscaler/zia/models/endpoint_applications_policies.py @@ -0,0 +1,56 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class EndpointApplicationsPolicies(ZscalerObject): + """ + A class for EndpointApplicationsPolicies objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointApplicationsPolicies model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.rule_name = config["ruleName"] if "ruleName" in config else None + self.rule_type = config["ruleType"] if "ruleType" in config else None + self.id = config["id"] if "id" in config else None + else: + self.rule_name = None + self.rule_type = None + self.id = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "ruleName": self.rule_name, + "ruleType": self.rule_type, + "id": self.id, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_dlp_resource_groups.py b/zscaler/zia/models/endpoint_dlp_resource_groups.py new file mode 100644 index 00000000..e0f10d5e --- /dev/null +++ b/zscaler/zia/models/endpoint_dlp_resource_groups.py @@ -0,0 +1,307 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EndpointDlpResourceGroups(ZscalerObject): + """ + A class for EndpointDlpResourceGroups objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointDlpResourceGroups model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.channel = config["channel"] if "channel" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.resource_count = config["resourceCount"] if "resourceCount" in config else None + self.resources = ZscalerCollection.form_list(config["resources"] if "resources" in config else [], Resource) + else: + self.id = None + self.channel = None + self.name = None + self.description = None + self.resource_count = None + self.resources = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "channel": self.channel, + "name": self.name, + "description": self.description, + "resourceCount": self.resource_count, + "resources": [item.request_format() for item in (self.resources or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Resource(ZscalerObject): + """ + A class for Resource objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Resource model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.channel = config["channel"] if "channel" in config else None + self.is_predefined = config["isPredefined"] if "isPredefined" in config else None + self.network_drive_type = config["networkDriveType"] if "networkDriveType" in config else None + self.description = config["description"] if "description" in config else None + self.server_name = config["serverName"] if "serverName" in config else None + self.app_id = config["appId"] if "appId" in config else None + self.network_drives = ZscalerCollection.form_list( + config["networkDrives"] if "networkDrives" in config else [], NetworkDrive + ) + + if "printer" in config: + if isinstance(config["printer"], Printer): + self.printer = config["printer"] + elif config["printer"] is not None: + self.printer = Printer(config["printer"]) + else: + self.printer = None + else: + self.printer = None + + if "removableStorage" in config: + if isinstance(config["removableStorage"], RemovableStorage): + self.removable_storage = config["removableStorage"] + elif config["removableStorage"] is not None: + self.removable_storage = RemovableStorage(config["removableStorage"]) + else: + self.removable_storage = None + else: + self.removable_storage = None + + if "application" in config: + if isinstance(config["application"], Application): + self.application = config["application"] + elif config["application"] is not None: + self.application = Application(config["application"]) + else: + self.application = None + else: + self.application = None + else: + self.id = None + self.name = None + self.channel = None + self.is_predefined = None + self.network_drive_type = None + self.description = None + self.server_name = None + self.app_id = None + self.network_drives = [] + self.printer = None + self.removable_storage = None + self.application = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "channel": self.channel, + "isPredefined": self.is_predefined, + "networkDriveType": self.network_drive_type, + "description": self.description, + "serverName": self.server_name, + "appId": self.app_id, + "networkDrives": [item.request_format() for item in (self.network_drives or [])], + "printer": self.printer, + "removableStorage": self.removable_storage, + "application": self.application, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Printer(ZscalerObject): + """ + A class for Printer objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Printer model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.unc = config["unc"] if "unc" in config else None + self.ip_address = config["ipAddress"] if "ipAddress" in config else None + self.domain = config["domain"] if "domain" in config else None + else: + self.unc = None + self.ip_address = None + self.domain = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "unc": self.unc, + "ipAddress": self.ip_address, + "domain": self.domain, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class NetworkDrive(ZscalerObject): + """ + A class for NetworkDrive objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the NetworkDrive model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.network_path = config["networkPath"] if "networkPath" in config else None + else: + self.network_path = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "networkPath": self.network_path, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class RemovableStorage(ZscalerObject): + """ + A class for RemovableStorage objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the RemovableStorage model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.vendor_id = config["vendorId"] if "vendorId" in config else None + self.product_id = config["productId"] if "productId" in config else None + self.serial_number = config["serialNumber"] if "serialNumber" in config else None + else: + self.vendor_id = None + self.product_id = None + self.serial_number = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "vendorId": self.vendor_id, + "productId": self.product_id, + "serialNumber": self.serial_number, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Application(ZscalerObject): + """ + A class for Application objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Application model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.os_type = config["osType"] if "osType" in config else None + self.file_name = config["fileName"] if "fileName" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + else: + self.os_type = None + self.file_name = None + self.original_file_name = None + self.bundle_id = None + self.digitally_signed = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "osType": self.os_type, + "fileName": self.file_name, + "originalFileName": self.original_file_name, + "bundleID": self.bundle_id, + "digitallySigned": self.digitally_signed, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_dlp_resource_groups_resources.py b/zscaler/zia/models/endpoint_dlp_resource_groups_resources.py new file mode 100644 index 00000000..9725052f --- /dev/null +++ b/zscaler/zia/models/endpoint_dlp_resource_groups_resources.py @@ -0,0 +1,58 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EndpointDlpResourceGroupsResources(ZscalerObject): + """ + A class for EndpointDlpResourceGroupsResources objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointDlpResourceGroupsResources model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.resources_to_be_added = ZscalerCollection.form_list( + config["resourcesToBeAdded"] if "resourcesToBeAdded" in config else [], int + ) + self.resources_to_be_deleted = ZscalerCollection.form_list( + config["resourcesToBeDeleted"] if "resourcesToBeDeleted" in config else [], int + ) + else: + self.resources_to_be_added = [] + self.resources_to_be_deleted = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "resourcesToBeAdded": self.resources_to_be_added, + "resourcesToBeDeleted": self.resources_to_be_deleted, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/endpoint_dlp_rules.py b/zscaler/zia/models/endpoint_dlp_rules.py new file mode 100644 index 00000000..e1174e6a --- /dev/null +++ b/zscaler/zia/models/endpoint_dlp_rules.py @@ -0,0 +1,652 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject +from zscaler.zia.models import common as common + + +class EndpointDlpRules(ZscalerObject): + """ + A class for EndpointDlpRules objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndpointDlpRules model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.state = config["state"] if "state" in config else None + self.order = config["order"] if "order" in config else None + self.rank = config["rank"] if "rank" in config else None + self.file_types = config["fileTypes"] if "fileTypes" in config else None + self.data_transfer_method = config["dataTransferMethod"] if "dataTransferMethod" in config else None + self.description = config["description"] if "description" in config else None + self.min_size = config["minSize"] if "minSize" in config else None + self.action = config["action"] if "action" in config else None + self.external_auditor_email = config["externalAuditorEmail"] if "externalAuditorEmail" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.parent_rule = config["parentRule"] if "parentRule" in config else None + self.severity = config["severity"] if "severity" in config else None + self.eun_enabled = config["eunEnabled"] if "eunEnabled" in config else None + self.eun_template_id = config["eunTemplateId"] if "eunTemplateId" in config else None + self.uc_template_id = config["ucTemplateId"] if "ucTemplateId" in config else None + self.network_type = config["networkType"] if "networkType" in config else None + self.without_content_inspection = ( + config["withoutContentInspection"] if "withoutContentInspection" in config else None + ) + self.dlp_engines = ZscalerCollection.form_list( + config["dlpEngines"] if "dlpEngines" in config else [], common.ResourceReference + ) + self.users = ZscalerCollection.form_list(config["users"] if "users" in config else [], common.ResourceReference) + self.groups = ZscalerCollection.form_list(config["groups"] if "groups" in config else [], common.ResourceReference) + self.departments = ZscalerCollection.form_list( + config["departments"] if "departments" in config else [], common.ResourceReference + ) + self.devices = ZscalerCollection.form_list( + config["devices"] if "devices" in config else [], common.ResourceReference + ) + self.device_groups = ZscalerCollection.form_list( + config["deviceGroups"] if "deviceGroups" in config else [], common.ResourceReference + ) + self.device_trust_levels = ZscalerCollection.form_list( + config["deviceTrustLevels"] if "deviceTrustLevels" in config else [], str + ) + self.time_windows = ZscalerCollection.form_list( + config["timeWindows"] if "timeWindows" in config else [], common.ResourceReference + ) + self.labels = ZscalerCollection.form_list(config["labels"] if "labels" in config else [], common.ResourceReference) + self.end_point_applications = ZscalerCollection.form_list( + config["endPointApplications"] if "endPointApplications" in config else [], EndPointApplication + ) + self.end_point_application_groups = ZscalerCollection.form_list( + config["endPointApplicationGroups"] if "endPointApplicationGroups" in config else [], EndPointApplicationGroup + ) + self.resources = ZscalerCollection.form_list( + config["resources"] if "resources" in config else [], common.ResourceReference + ) + self.resource_groups = ZscalerCollection.form_list( + config["resourceGroups"] if "resourceGroups" in config else [], common.ResourceReference + ) + self.user_risk_score_levels = ZscalerCollection.form_list( + config["userRiskScoreLevels"] if "userRiskScoreLevels" in config else [], str + ) + self.sub_rules = ZscalerCollection.form_list(config["subRules"] if "subRules" in config else [], SubRule) + + if "notificationTemplate" in config: + if isinstance(config["notificationTemplate"], common.CommonBlocks): + self.notification_template = config["notificationTemplate"] + elif config["notificationTemplate"] is not None: + self.notification_template = common.CommonBlocks(config["notificationTemplate"]) + else: + self.notification_template = None + else: + self.notification_template = None + + if "auditor" in config: + if isinstance(config["auditor"], common.CommonBlocks): + self.auditor = config["auditor"] + elif config["auditor"] is not None: + self.auditor = common.CommonBlocks(config["auditor"]) + else: + self.auditor = None + else: + self.auditor = None + + if "lastModifiedBy" in config: + if isinstance(config["lastModifiedBy"], common.CommonBlocks): + self.last_modified_by = config["lastModifiedBy"] + elif config["lastModifiedBy"] is not None: + self.last_modified_by = common.CommonBlocks(config["lastModifiedBy"]) + else: + self.last_modified_by = None + else: + self.last_modified_by = None + + if "receiver" in config: + if isinstance(config["receiver"], Receiver): + self.receiver = config["receiver"] + elif config["receiver"] is not None: + self.receiver = Receiver(config["receiver"]) + else: + self.receiver = None + else: + self.receiver = None + else: + self.id = None + self.name = None + self.state = None + self.order = None + self.rank = None + self.file_types = None + self.data_transfer_method = None + self.description = None + self.min_size = None + self.action = None + self.external_auditor_email = None + self.last_modified_time = None + self.parent_rule = None + self.severity = None + self.eun_enabled = None + self.eun_template_id = None + self.uc_template_id = None + self.network_type = None + self.without_content_inspection = None + self.dlp_engines = [] + self.users = [] + self.groups = [] + self.departments = [] + self.devices = [] + self.device_groups = [] + self.device_trust_levels = [] + self.time_windows = [] + self.labels = [] + self.end_point_applications = [] + self.end_point_application_groups = [] + self.resources = [] + self.resource_groups = [] + self.user_risk_score_levels = [] + self.sub_rules = [] + self.notification_template = None + self.auditor = None + self.last_modified_by = None + self.receiver = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "state": self.state, + "order": self.order, + "rank": self.rank, + "fileTypes": self.file_types, + "dataTransferMethod": self.data_transfer_method, + "description": self.description, + "minSize": self.min_size, + "action": self.action, + "externalAuditorEmail": self.external_auditor_email, + "lastModifiedTime": self.last_modified_time, + "parentRule": self.parent_rule, + "severity": self.severity, + "eunEnabled": self.eun_enabled, + "eunTemplateId": self.eun_template_id, + "ucTemplateId": self.uc_template_id, + "networkType": self.network_type, + "withoutContentInspection": self.without_content_inspection, + "dlpEngines": [item.request_format() for item in (self.dlp_engines or [])], + "users": [item.request_format() for item in (self.users or [])], + "groups": [item.request_format() for item in (self.groups or [])], + "departments": [item.request_format() for item in (self.departments or [])], + "devices": [item.request_format() for item in (self.devices or [])], + "deviceGroups": [item.request_format() for item in (self.device_groups or [])], + "deviceTrustLevels": self.device_trust_levels, + "timeWindows": [item.request_format() for item in (self.time_windows or [])], + "labels": [item.request_format() for item in (self.labels or [])], + "endPointApplications": [item.request_format() for item in (self.end_point_applications or [])], + "endPointApplicationGroups": [item.request_format() for item in (self.end_point_application_groups or [])], + "resources": [item.request_format() for item in (self.resources or [])], + "resourceGroups": [item.request_format() for item in (self.resource_groups or [])], + "userRiskScoreLevels": self.user_risk_score_levels, + "subRules": [item.request_format() for item in (self.sub_rules or [])], + "notificationTemplate": self.notification_template, + "auditor": self.auditor, + "lastModifiedBy": self.last_modified_by, + "receiver": self.receiver, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class EndPointApplication(ZscalerObject): + """ + A class for EndPointApplication objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndPointApplication model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.resource_id = config["resourceId"] if "resourceId" in config else None + self.description = config["description"] if "description" in config else None + self.os_type = config["osType"] if "osType" in config else None + self.application_name = config["applicationName"] if "applicationName" in config else None + self.bundle_id = config["bundleID"] if "bundleID" in config else None + self.filename = config["filename"] if "filename" in config else None + self.original_file_name = config["originalFileName"] if "originalFileName" in config else None + self.digitally_signed = config["digitallySigned"] if "digitallySigned" in config else None + self.mod_uid = config["modUId"] if "modUId" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.application_type = config["applicationType"] if "applicationType" in config else None + self.zapp_id = config["zappId"] if "zappId" in config else None + self.deleted = config["deleted"] if "deleted" in config else None + self.versions = ZscalerCollection.form_list(config["versions"] if "versions" in config else [], Version) + + if "version" in config: + if isinstance(config["version"], Version): + self.version = config["version"] + elif config["version"] is not None: + self.version = Version(config["version"]) + else: + self.version = None + else: + self.version = None + else: + self.resource_id = None + self.description = None + self.os_type = None + self.application_name = None + self.bundle_id = None + self.filename = None + self.original_file_name = None + self.digitally_signed = None + self.mod_uid = None + self.last_modified_time = None + self.application_type = None + self.zapp_id = None + self.deleted = None + self.versions = [] + self.version = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "resourceId": self.resource_id, + "description": self.description, + "osType": self.os_type, + "applicationName": self.application_name, + "bundleID": self.bundle_id, + "filename": self.filename, + "originalFileName": self.original_file_name, + "digitallySigned": self.digitally_signed, + "modUId": self.mod_uid, + "lastModifiedTime": self.last_modified_time, + "applicationType": self.application_type, + "zappId": self.zapp_id, + "deleted": self.deleted, + "versions": [item.request_format() for item in (self.versions or [])], + "version": self.version, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Version(ZscalerObject): + """ + A class for Version objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Version model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.version = config["version"] if "version" in config else None + self.z_ver_id_md32 = config["z_ver_id_md32"] if "z_ver_id_md32" in config else None + self.threat_type = config["threat_type"] if "threat_type" in config else None + self.threat_level = config["threat_level"] if "threat_level" in config else None + self.bundle_id = config["bundle_id"] if "bundle_id" in config else None + self.code_signing_certificate_status = ( + config["code_signing_certificate_status"] if "code_signing_certificate_status" in config else None + ) + self.threat_level_updated = config["threatLevelUpdated"] if "threatLevelUpdated" in config else None + else: + self.version = None + self.z_ver_id_md32 = None + self.threat_type = None + self.threat_level = None + self.bundle_id = None + self.code_signing_certificate_status = None + self.threat_level_updated = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "version": self.version, + "z_ver_id_md32": self.z_ver_id_md32, + "threat_type": self.threat_type, + "threat_level": self.threat_level, + "bundle_id": self.bundle_id, + "code_signing_certificate_status": self.code_signing_certificate_status, + "threatLevelUpdated": self.threat_level_updated, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class EndPointApplicationGroup(ZscalerObject): + """ + A class for EndPointApplicationGroup objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EndPointApplicationGroup model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.group_id = config["groupId"] if "groupId" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.mod_uid = config["modUId"] if "modUId" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.end_point_applications = ZscalerCollection.form_list( + config["endPointApplications"] if "endPointApplications" in config else [], EndPointApplication + ) + else: + self.group_id = None + self.name = None + self.description = None + self.mod_uid = None + self.last_modified_time = None + self.end_point_applications = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "groupId": self.group_id, + "name": self.name, + "description": self.description, + "modUId": self.mod_uid, + "lastModifiedTime": self.last_modified_time, + "endPointApplications": [item.request_format() for item in (self.end_point_applications or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Receiver(ZscalerObject): + """ + A class for Receiver objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Receiver model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.type = config["type"] if "type" in config else None + + if "tenant" in config: + if isinstance(config["tenant"], common.CommonBlocks): + self.tenant = config["tenant"] + elif config["tenant"] is not None: + self.tenant = common.CommonBlocks(config["tenant"]) + else: + self.tenant = None + else: + self.tenant = None + else: + self.id = None + self.name = None + self.type = None + self.tenant = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "type": self.type, + "tenant": self.tenant, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class SubRule(ZscalerObject): + """ + A class for SubRule objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the SubRule model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.state = config["state"] if "state" in config else None + self.order = config["order"] if "order" in config else None + self.rank = config["rank"] if "rank" in config else None + self.file_types = config["fileTypes"] if "fileTypes" in config else None + self.data_transfer_method = config["dataTransferMethod"] if "dataTransferMethod" in config else None + self.description = config["description"] if "description" in config else None + self.min_size = config["minSize"] if "minSize" in config else None + self.action = config["action"] if "action" in config else None + self.external_auditor_email = config["externalAuditorEmail"] if "externalAuditorEmail" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.parent_rule = config["parentRule"] if "parentRule" in config else None + self.severity = config["severity"] if "severity" in config else None + self.eun_enabled = config["eunEnabled"] if "eunEnabled" in config else None + self.eun_template_id = config["eunTemplateId"] if "eunTemplateId" in config else None + self.uc_template_id = config["ucTemplateId"] if "ucTemplateId" in config else None + self.network_type = config["networkType"] if "networkType" in config else None + self.without_content_inspection = ( + config["withoutContentInspection"] if "withoutContentInspection" in config else None + ) + self.dlp_engines = ZscalerCollection.form_list( + config["dlpEngines"] if "dlpEngines" in config else [], common.ResourceReference + ) + self.users = ZscalerCollection.form_list(config["users"] if "users" in config else [], common.ResourceReference) + self.groups = ZscalerCollection.form_list(config["groups"] if "groups" in config else [], common.ResourceReference) + self.departments = ZscalerCollection.form_list( + config["departments"] if "departments" in config else [], common.ResourceReference + ) + self.devices = ZscalerCollection.form_list( + config["devices"] if "devices" in config else [], common.ResourceReference + ) + self.device_groups = ZscalerCollection.form_list( + config["deviceGroups"] if "deviceGroups" in config else [], common.ResourceReference + ) + self.device_trust_levels = ZscalerCollection.form_list( + config["deviceTrustLevels"] if "deviceTrustLevels" in config else [], str + ) + self.time_windows = ZscalerCollection.form_list( + config["timeWindows"] if "timeWindows" in config else [], common.ResourceReference + ) + self.labels = ZscalerCollection.form_list(config["labels"] if "labels" in config else [], common.ResourceReference) + self.end_point_applications = ZscalerCollection.form_list( + config["endPointApplications"] if "endPointApplications" in config else [], EndPointApplication + ) + self.end_point_application_groups = ZscalerCollection.form_list( + config["endPointApplicationGroups"] if "endPointApplicationGroups" in config else [], EndPointApplicationGroup + ) + self.resources = ZscalerCollection.form_list( + config["resources"] if "resources" in config else [], common.ResourceReference + ) + self.resource_groups = ZscalerCollection.form_list( + config["resourceGroups"] if "resourceGroups" in config else [], common.ResourceReference + ) + self.user_risk_score_levels = ZscalerCollection.form_list( + config["userRiskScoreLevels"] if "userRiskScoreLevels" in config else [], str + ) + + if "notificationTemplate" in config: + if isinstance(config["notificationTemplate"], common.CommonBlocks): + self.notification_template = config["notificationTemplate"] + elif config["notificationTemplate"] is not None: + self.notification_template = common.CommonBlocks(config["notificationTemplate"]) + else: + self.notification_template = None + else: + self.notification_template = None + + if "auditor" in config: + if isinstance(config["auditor"], common.CommonBlocks): + self.auditor = config["auditor"] + elif config["auditor"] is not None: + self.auditor = common.CommonBlocks(config["auditor"]) + else: + self.auditor = None + else: + self.auditor = None + + if "lastModifiedBy" in config: + if isinstance(config["lastModifiedBy"], common.CommonBlocks): + self.last_modified_by = config["lastModifiedBy"] + elif config["lastModifiedBy"] is not None: + self.last_modified_by = common.CommonBlocks(config["lastModifiedBy"]) + else: + self.last_modified_by = None + else: + self.last_modified_by = None + + if "receiver" in config: + if isinstance(config["receiver"], Receiver): + self.receiver = config["receiver"] + elif config["receiver"] is not None: + self.receiver = Receiver(config["receiver"]) + else: + self.receiver = None + else: + self.receiver = None + else: + self.id = None + self.name = None + self.state = None + self.order = None + self.rank = None + self.file_types = None + self.data_transfer_method = None + self.description = None + self.min_size = None + self.action = None + self.external_auditor_email = None + self.last_modified_time = None + self.parent_rule = None + self.severity = None + self.eun_enabled = None + self.eun_template_id = None + self.uc_template_id = None + self.network_type = None + self.without_content_inspection = None + self.dlp_engines = [] + self.users = [] + self.groups = [] + self.departments = [] + self.devices = [] + self.device_groups = [] + self.device_trust_levels = [] + self.time_windows = [] + self.labels = [] + self.end_point_applications = [] + self.end_point_application_groups = [] + self.resources = [] + self.resource_groups = [] + self.user_risk_score_levels = [] + self.notification_template = None + self.auditor = None + self.last_modified_by = None + self.receiver = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "state": self.state, + "order": self.order, + "rank": self.rank, + "fileTypes": self.file_types, + "dataTransferMethod": self.data_transfer_method, + "description": self.description, + "minSize": self.min_size, + "action": self.action, + "externalAuditorEmail": self.external_auditor_email, + "lastModifiedTime": self.last_modified_time, + "parentRule": self.parent_rule, + "severity": self.severity, + "eunEnabled": self.eun_enabled, + "eunTemplateId": self.eun_template_id, + "ucTemplateId": self.uc_template_id, + "networkType": self.network_type, + "withoutContentInspection": self.without_content_inspection, + "dlpEngines": [item.request_format() for item in (self.dlp_engines or [])], + "users": [item.request_format() for item in (self.users or [])], + "groups": [item.request_format() for item in (self.groups or [])], + "departments": [item.request_format() for item in (self.departments or [])], + "devices": [item.request_format() for item in (self.devices or [])], + "deviceGroups": [item.request_format() for item in (self.device_groups or [])], + "deviceTrustLevels": self.device_trust_levels, + "timeWindows": [item.request_format() for item in (self.time_windows or [])], + "labels": [item.request_format() for item in (self.labels or [])], + "endPointApplications": [item.request_format() for item in (self.end_point_applications or [])], + "endPointApplicationGroups": [item.request_format() for item in (self.end_point_application_groups or [])], + "resources": [item.request_format() for item in (self.resources or [])], + "resourceGroups": [item.request_format() for item in (self.resource_groups or [])], + "userRiskScoreLevels": self.user_risk_score_levels, + "notificationTemplate": self.notification_template, + "auditor": self.auditor, + "lastModifiedBy": self.last_modified_by, + "receiver": self.receiver, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/eun_feature_enablement_status.py b/zscaler/zia/models/eun_feature_enablement_status.py new file mode 100644 index 00000000..bc2a0aa7 --- /dev/null +++ b/zscaler/zia/models/eun_feature_enablement_status.py @@ -0,0 +1,73 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class EunFeatureEnablementStatus(ZscalerObject): + """ + A class for EunFeatureEnablementStatus objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EunFeatureEnablementStatus model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.inline_dlp_status = config["inlineDlpStatus"] if "inlineDlpStatus" in config else None + self.ept_dlp_status = config["eptDlpStatus"] if "eptDlpStatus" in config else None + self.cloud_app_status = config["cloudAppStatus"] if "cloudAppStatus" in config else None + self.url_filtering_status = config["urlFilteringStatus"] if "urlFilteringStatus" in config else None + self.dns_rule_status = config["dnsRuleStatus"] if "dnsRuleStatus" in config else None + self.firewall_filtering_status = config["firewallFilteringStatus"] if "firewallFilteringStatus" in config else None + self.ips_control_status = config["ipsControlStatus"] if "ipsControlStatus" in config else None + self.file_type_filtering_status = ( + config["fileTypeFilteringStatus"] if "fileTypeFilteringStatus" in config else None + ) + else: + self.inline_dlp_status = None + self.ept_dlp_status = None + self.cloud_app_status = None + self.url_filtering_status = None + self.dns_rule_status = None + self.firewall_filtering_status = None + self.ips_control_status = None + self.file_type_filtering_status = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "inlineDlpStatus": self.inline_dlp_status, + "eptDlpStatus": self.ept_dlp_status, + "cloudAppStatus": self.cloud_app_status, + "urlFilteringStatus": self.url_filtering_status, + "dnsRuleStatus": self.dns_rule_status, + "firewallFilteringStatus": self.firewall_filtering_status, + "ipsControlStatus": self.ips_control_status, + "fileTypeFilteringStatus": self.file_type_filtering_status, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/eun_template_product.py b/zscaler/zia/models/eun_template_product.py new file mode 100644 index 00000000..8a9c0c89 --- /dev/null +++ b/zscaler/zia/models/eun_template_product.py @@ -0,0 +1,201 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EunTemplateProduct(ZscalerObject): + """ + A class for EunTemplateProduct objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EunTemplateProduct model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.channel = config["channel"] if "channel" in config else None + self.product = config["product"] if "product" in config else None + self.type = config["type"] if "type" in config else None + self.caution_interval = config["cautionInterval"] if "cautionInterval" in config else None + self.default = config["default"] if "default" in config else None + self.language_templates = ZscalerCollection.form_list( + config["languageTemplates"] if "languageTemplates" in config else [], LanguageTemplate + ) + self.notification_details = ZscalerCollection.form_list( + config["notificationDetails"] if "notificationDetails" in config else [], str + ) + + if "recommendedCloudApp" in config: + if isinstance(config["recommendedCloudApp"], RecommendedCloudApp): + self.recommended_cloud_app = config["recommendedCloudApp"] + elif config["recommendedCloudApp"] is not None: + self.recommended_cloud_app = RecommendedCloudApp(config["recommendedCloudApp"]) + else: + self.recommended_cloud_app = None + else: + self.recommended_cloud_app = None + else: + self.id = None + self.name = None + self.channel = None + self.product = None + self.type = None + self.caution_interval = None + self.default = None + self.language_templates = [] + self.notification_details = [] + self.recommended_cloud_app = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "channel": self.channel, + "product": self.product, + "type": self.type, + "cautionInterval": self.caution_interval, + "default": self.default, + "languageTemplates": [item.request_format() for item in (self.language_templates or [])], + "notificationDetails": self.notification_details, + "recommendedCloudApp": self.recommended_cloud_app, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class RecommendedCloudApp(ZscalerObject): + """ + A class for RecommendedCloudApp objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the RecommendedCloudApp model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.val = config["val"] if "val" in config else None + self.web_application_class = config["webApplicationClass"] if "webApplicationClass" in config else None + self.backend_name = config["backendName"] if "backendName" in config else None + self.original_name = config["originalName"] if "originalName" in config else None + self.name = config["name"] if "name" in config else None + self.deprecated = config["deprecated"] if "deprecated" in config else None + self.misc = config["misc"] if "misc" in config else None + self.app_not_ready = config["appNotReady"] if "appNotReady" in config else None + self.under_migration = config["underMigration"] if "underMigration" in config else None + self.app_cat_modified = config["appCatModified"] if "appCatModified" in config else None + else: + self.val = None + self.web_application_class = None + self.backend_name = None + self.original_name = None + self.name = None + self.deprecated = None + self.misc = None + self.app_not_ready = None + self.under_migration = None + self.app_cat_modified = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "val": self.val, + "webApplicationClass": self.web_application_class, + "backendName": self.backend_name, + "originalName": self.original_name, + "name": self.name, + "deprecated": self.deprecated, + "misc": self.misc, + "appNotReady": self.app_not_ready, + "underMigration": self.under_migration, + "appCatModified": self.app_cat_modified, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class LanguageTemplate(ZscalerObject): + """ + A class for LanguageTemplate objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LanguageTemplate model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.language = config["language"] if "language" in config else None + self.allow_message = config["allowMessage"] if "allowMessage" in config else None + self.block_message = config["blockMessage"] if "blockMessage" in config else None + self.encrypt_message = config["encryptMessage"] if "encryptMessage" in config else None + self.readonly_message = config["readonlyMessage"] if "readonlyMessage" in config else None + self.caution_message = config["cautionMessage"] if "cautionMessage" in config else None + self.redirect_response_message = config["redirectResponseMessage"] if "redirectResponseMessage" in config else None + self.default = config["default"] if "default" in config else None + else: + self.language = None + self.allow_message = None + self.block_message = None + self.encrypt_message = None + self.readonly_message = None + self.caution_message = None + self.redirect_response_message = None + self.default = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "language": self.language, + "allowMessage": self.allow_message, + "blockMessage": self.block_message, + "encryptMessage": self.encrypt_message, + "readonlyMessage": self.readonly_message, + "cautionMessage": self.caution_message, + "redirectResponseMessage": self.redirect_response_message, + "default": self.default, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/eun_user_confirmation_product.py b/zscaler/zia/models/eun_user_confirmation_product.py new file mode 100644 index 00000000..28527dcd --- /dev/null +++ b/zscaler/zia/models/eun_user_confirmation_product.py @@ -0,0 +1,105 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class EunUserConfirmationProduct(ZscalerObject): + """ + A class for EunUserConfirmationProduct objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the EunUserConfirmationProduct model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.channel = config["channel"] if "channel" in config else None + self.product = config["product"] if "product" in config else None + self.default = config["default"] if "default" in config else None + self.language_templates = ZscalerCollection.form_list( + config["languageTemplates"] if "languageTemplates" in config else [], LanguageTemplate + ) + else: + self.id = None + self.name = None + self.channel = None + self.product = None + self.default = None + self.language_templates = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "channel": self.channel, + "product": self.product, + "default": self.default, + "languageTemplates": [item.request_format() for item in (self.language_templates or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class LanguageTemplate(ZscalerObject): + """ + A class for LanguageTemplate objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the LanguageTemplate model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.language = config["language"] if "language" in config else None + self.message = config["message"] if "message" in config else None + self.default = config["default"] if "default" in config else None + else: + self.language = None + self.message = None + self.default = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "language": self.language, + "message": self.message, + "default": self.default, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/ips_categories.py b/zscaler/zia/models/ips_categories.py new file mode 100644 index 00000000..ed93147c --- /dev/null +++ b/zscaler/zia/models/ips_categories.py @@ -0,0 +1,68 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class IpsCategories(ZscalerObject): + """ + A class for IpsCategories objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the IpsCategories model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.back_end_name = config["backEndName"] if "backEndName" in config else None + self.description = config["description"] if "description" in config else None + self.deleted = config["deleted"] if "deleted" in config else None + self.predefined = config["predefined"] if "predefined" in config else None + self.ips_signature_rules_count = config["ipsSignatureRulesCount"] if "ipsSignatureRulesCount" in config else None + else: + self.id = None + self.name = None + self.back_end_name = None + self.description = None + self.deleted = None + self.predefined = None + self.ips_signature_rules_count = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "name": self.name, + "backEndName": self.back_end_name, + "description": self.description, + "deleted": self.deleted, + "predefined": self.predefined, + "ipsSignatureRulesCount": self.ips_signature_rules_count, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/nss_collectors.py b/zscaler/zia/models/nss_collectors.py new file mode 100644 index 00000000..f38cc487 --- /dev/null +++ b/zscaler/zia/models/nss_collectors.py @@ -0,0 +1,75 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject +from zscaler.zia.models import common as common + + +class NssCollectors(ZscalerObject): + """ + A class for NssCollectors objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the NssCollectors model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.name = config["name"] if "name" in config else None + self.status = config["status"] if "status" in config else None + self.vendor = config["vendor"] if "vendor" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.id = config["id"] if "id" in config else None + + if "lastModifiedBy" in config: + if isinstance(config["lastModifiedBy"], common.CommonBlocks): + self.last_modified_by = config["lastModifiedBy"] + elif config["lastModifiedBy"] is not None: + self.last_modified_by = common.CommonBlocks(config["lastModifiedBy"]) + else: + self.last_modified_by = None + else: + self.last_modified_by = None + else: + self.name = None + self.status = None + self.vendor = None + self.last_modified_time = None + self.id = None + self.last_modified_by = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "name": self.name, + "status": self.status, + "vendor": self.vendor, + "lastModifiedTime": self.last_modified_time, + "id": self.id, + "lastModifiedBy": self.last_modified_by, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/outbound_email_dlp_rules.py b/zscaler/zia/models/outbound_email_dlp_rules.py new file mode 100644 index 00000000..eedf641e --- /dev/null +++ b/zscaler/zia/models/outbound_email_dlp_rules.py @@ -0,0 +1,392 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject +from zscaler.zia.models import common as common + + +class OutboundEmailDlpRules(ZscalerObject): + """ + A class for OutboundEmailDlpRules objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the OutboundEmailDlpRules model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.order = config["order"] if "order" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.state = config["state"] if "state" in config else None + self.action = config["action"] if "action" in config else None + self.min_size = config["minSize"] if "minSize" in config else None + self.without_content_inspection = ( + config["withoutContentInspection"] if "withoutContentInspection" in config else None + ) + self.external_auditor_email = config["externalAuditorEmail"] if "externalAuditorEmail" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.severity = config["severity"] if "severity" in config else None + self.parent_rule = config["parentRule"] if "parentRule" in config else None + self.custom_header = config["customHeader"] if "customHeader" in config else None + self.groups = ZscalerCollection.form_list(config["groups"] if "groups" in config else [], common.ResourceReference) + self.departments = ZscalerCollection.form_list( + config["departments"] if "departments" in config else [], common.ResourceReference + ) + self.users = ZscalerCollection.form_list(config["users"] if "users" in config else [], common.ResourceReference) + self.excluded_groups = ZscalerCollection.form_list( + config["excludedGroups"] if "excludedGroups" in config else [], common.ResourceReference + ) + self.excluded_departments = ZscalerCollection.form_list( + config["excludedDepartments"] if "excludedDepartments" in config else [], common.ResourceReference + ) + self.excluded_users = ZscalerCollection.form_list( + config["excludedUsers"] if "excludedUsers" in config else [], common.ResourceReference + ) + self.time_windows = ZscalerCollection.form_list( + config["timeWindows"] if "timeWindows" in config else [], common.ResourceReference + ) + self.dlp_engines = ZscalerCollection.form_list( + config["dlpEngines"] if "dlpEngines" in config else [], common.ResourceReference + ) + self.file_types = ZscalerCollection.form_list(config["fileTypes"] if "fileTypes" in config else [], str) + self.labels = ZscalerCollection.form_list(config["labels"] if "labels" in config else [], common.ResourceReference) + self.included_domain_profiles = ZscalerCollection.form_list( + config["includedDomainProfiles"] if "includedDomainProfiles" in config else [], common.ResourceReference + ) + self.user_risk_score_levels = ZscalerCollection.form_list( + config["userRiskScoreLevels"] if "userRiskScoreLevels" in config else [], str + ) + self.email_tenants = ZscalerCollection.form_list( + config["emailTenants"] if "emailTenants" in config else [], common.ResourceReference + ) + self.content_locations = ZscalerCollection.form_list( + config["contentLocations"] if "contentLocations" in config else [], str + ) + self.sub_rules = ZscalerCollection.form_list(config["subRules"] if "subRules" in config else [], SubRule) + self.email_recipient_profiles = ZscalerCollection.form_list( + config["emailRecipientProfiles"] if "emailRecipientProfiles" in config else [], common.ResourceReference + ) + + if "auditor" in config: + if isinstance(config["auditor"], common.CommonBlocks): + self.auditor = config["auditor"] + elif config["auditor"] is not None: + self.auditor = common.CommonBlocks(config["auditor"]) + else: + self.auditor = None + else: + self.auditor = None + + if "notificationTemplate" in config: + if isinstance(config["notificationTemplate"], common.CommonBlocks): + self.notification_template = config["notificationTemplate"] + elif config["notificationTemplate"] is not None: + self.notification_template = common.CommonBlocks(config["notificationTemplate"]) + else: + self.notification_template = None + else: + self.notification_template = None + + if "lastModifiedBy" in config: + if isinstance(config["lastModifiedBy"], common.CommonBlocks): + self.last_modified_by = config["lastModifiedBy"] + elif config["lastModifiedBy"] is not None: + self.last_modified_by = common.CommonBlocks(config["lastModifiedBy"]) + else: + self.last_modified_by = None + else: + self.last_modified_by = None + + if "receiver" in config: + if isinstance(config["receiver"], common.CommonIDName): + self.receiver = config["receiver"] + elif config["receiver"] is not None: + self.receiver = common.CommonIDName(config["receiver"]) + else: + self.receiver = None + else: + self.receiver = None + else: + self.id = None + self.order = None + self.name = None + self.description = None + self.state = None + self.action = None + self.min_size = None + self.without_content_inspection = None + self.external_auditor_email = None + self.last_modified_time = None + self.severity = None + self.parent_rule = None + self.custom_header = None + self.groups = [] + self.departments = [] + self.users = [] + self.excluded_groups = [] + self.excluded_departments = [] + self.excluded_users = [] + self.time_windows = [] + self.dlp_engines = [] + self.file_types = [] + self.labels = [] + self.included_domain_profiles = [] + self.user_risk_score_levels = [] + self.email_tenants = [] + self.content_locations = [] + self.sub_rules = [] + self.email_recipient_profiles = [] + self.auditor = None + self.notification_template = None + self.last_modified_by = None + self.receiver = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "order": self.order, + "name": self.name, + "description": self.description, + "state": self.state, + "action": self.action, + "minSize": self.min_size, + "withoutContentInspection": self.without_content_inspection, + "externalAuditorEmail": self.external_auditor_email, + "lastModifiedTime": self.last_modified_time, + "severity": self.severity, + "parentRule": self.parent_rule, + "customHeader": self.custom_header, + "groups": [item.request_format() for item in (self.groups or [])], + "departments": [item.request_format() for item in (self.departments or [])], + "users": [item.request_format() for item in (self.users or [])], + "excludedGroups": [item.request_format() for item in (self.excluded_groups or [])], + "excludedDepartments": [item.request_format() for item in (self.excluded_departments or [])], + "excludedUsers": [item.request_format() for item in (self.excluded_users or [])], + "timeWindows": [item.request_format() for item in (self.time_windows or [])], + "dlpEngines": [item.request_format() for item in (self.dlp_engines or [])], + "fileTypes": self.file_types, + "labels": [item.request_format() for item in (self.labels or [])], + "includedDomainProfiles": [item.request_format() for item in (self.included_domain_profiles or [])], + "userRiskScoreLevels": self.user_risk_score_levels, + "emailTenants": [item.request_format() for item in (self.email_tenants or [])], + "contentLocations": self.content_locations, + "subRules": [item.request_format() for item in (self.sub_rules or [])], + "emailRecipientProfiles": [item.request_format() for item in (self.email_recipient_profiles or [])], + "auditor": self.auditor, + "notificationTemplate": self.notification_template, + "lastModifiedBy": self.last_modified_by, + "receiver": self.receiver, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class SubRule(ZscalerObject): + """ + A class for SubRule objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the SubRule model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.id = config["id"] if "id" in config else None + self.order = config["order"] if "order" in config else None + self.name = config["name"] if "name" in config else None + self.description = config["description"] if "description" in config else None + self.state = config["state"] if "state" in config else None + self.action = config["action"] if "action" in config else None + self.min_size = config["minSize"] if "minSize" in config else None + self.without_content_inspection = ( + config["withoutContentInspection"] if "withoutContentInspection" in config else None + ) + self.external_auditor_email = config["externalAuditorEmail"] if "externalAuditorEmail" in config else None + self.last_modified_time = config["lastModifiedTime"] if "lastModifiedTime" in config else None + self.severity = config["severity"] if "severity" in config else None + self.parent_rule = config["parentRule"] if "parentRule" in config else None + self.custom_header = config["customHeader"] if "customHeader" in config else None + self.groups = ZscalerCollection.form_list(config["groups"] if "groups" in config else [], common.ResourceReference) + self.departments = ZscalerCollection.form_list( + config["departments"] if "departments" in config else [], common.ResourceReference + ) + self.users = ZscalerCollection.form_list(config["users"] if "users" in config else [], common.ResourceReference) + self.excluded_groups = ZscalerCollection.form_list( + config["excludedGroups"] if "excludedGroups" in config else [], common.ResourceReference + ) + self.excluded_departments = ZscalerCollection.form_list( + config["excludedDepartments"] if "excludedDepartments" in config else [], common.ResourceReference + ) + self.excluded_users = ZscalerCollection.form_list( + config["excludedUsers"] if "excludedUsers" in config else [], common.ResourceReference + ) + self.time_windows = ZscalerCollection.form_list( + config["timeWindows"] if "timeWindows" in config else [], common.ResourceReference + ) + self.dlp_engines = ZscalerCollection.form_list( + config["dlpEngines"] if "dlpEngines" in config else [], common.ResourceReference + ) + self.file_types = ZscalerCollection.form_list(config["fileTypes"] if "fileTypes" in config else [], str) + self.labels = ZscalerCollection.form_list(config["labels"] if "labels" in config else [], common.ResourceReference) + self.included_domain_profiles = ZscalerCollection.form_list( + config["includedDomainProfiles"] if "includedDomainProfiles" in config else [], common.ResourceReference + ) + self.user_risk_score_levels = ZscalerCollection.form_list( + config["userRiskScoreLevels"] if "userRiskScoreLevels" in config else [], str + ) + self.email_tenants = ZscalerCollection.form_list( + config["emailTenants"] if "emailTenants" in config else [], common.ResourceReference + ) + self.content_locations = ZscalerCollection.form_list( + config["contentLocations"] if "contentLocations" in config else [], str + ) + self.email_recipient_profiles = ZscalerCollection.form_list( + config["emailRecipientProfiles"] if "emailRecipientProfiles" in config else [], common.ResourceReference + ) + + if "auditor" in config: + if isinstance(config["auditor"], common.CommonBlocks): + self.auditor = config["auditor"] + elif config["auditor"] is not None: + self.auditor = common.CommonBlocks(config["auditor"]) + else: + self.auditor = None + else: + self.auditor = None + + if "notificationTemplate" in config: + if isinstance(config["notificationTemplate"], common.CommonBlocks): + self.notification_template = config["notificationTemplate"] + elif config["notificationTemplate"] is not None: + self.notification_template = common.CommonBlocks(config["notificationTemplate"]) + else: + self.notification_template = None + else: + self.notification_template = None + + if "lastModifiedBy" in config: + if isinstance(config["lastModifiedBy"], common.CommonBlocks): + self.last_modified_by = config["lastModifiedBy"] + elif config["lastModifiedBy"] is not None: + self.last_modified_by = common.CommonBlocks(config["lastModifiedBy"]) + else: + self.last_modified_by = None + else: + self.last_modified_by = None + + if "receiver" in config: + if isinstance(config["receiver"], common.CommonIDName): + self.receiver = config["receiver"] + elif config["receiver"] is not None: + self.receiver = common.CommonIDName(config["receiver"]) + else: + self.receiver = None + else: + self.receiver = None + else: + self.id = None + self.order = None + self.name = None + self.description = None + self.state = None + self.action = None + self.min_size = None + self.without_content_inspection = None + self.external_auditor_email = None + self.last_modified_time = None + self.severity = None + self.parent_rule = None + self.custom_header = None + self.groups = [] + self.departments = [] + self.users = [] + self.excluded_groups = [] + self.excluded_departments = [] + self.excluded_users = [] + self.time_windows = [] + self.dlp_engines = [] + self.file_types = [] + self.labels = [] + self.included_domain_profiles = [] + self.user_risk_score_levels = [] + self.email_tenants = [] + self.content_locations = [] + self.email_recipient_profiles = [] + self.auditor = None + self.notification_template = None + self.last_modified_by = None + self.receiver = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "id": self.id, + "order": self.order, + "name": self.name, + "description": self.description, + "state": self.state, + "action": self.action, + "minSize": self.min_size, + "withoutContentInspection": self.without_content_inspection, + "externalAuditorEmail": self.external_auditor_email, + "lastModifiedTime": self.last_modified_time, + "severity": self.severity, + "parentRule": self.parent_rule, + "customHeader": self.custom_header, + "groups": [item.request_format() for item in (self.groups or [])], + "departments": [item.request_format() for item in (self.departments or [])], + "users": [item.request_format() for item in (self.users or [])], + "excludedGroups": [item.request_format() for item in (self.excluded_groups or [])], + "excludedDepartments": [item.request_format() for item in (self.excluded_departments or [])], + "excludedUsers": [item.request_format() for item in (self.excluded_users or [])], + "timeWindows": [item.request_format() for item in (self.time_windows or [])], + "dlpEngines": [item.request_format() for item in (self.dlp_engines or [])], + "fileTypes": self.file_types, + "labels": [item.request_format() for item in (self.labels or [])], + "includedDomainProfiles": [item.request_format() for item in (self.included_domain_profiles or [])], + "userRiskScoreLevels": self.user_risk_score_levels, + "emailTenants": [item.request_format() for item in (self.email_tenants or [])], + "contentLocations": self.content_locations, + "emailRecipientProfiles": [item.request_format() for item in (self.email_recipient_profiles or [])], + "auditor": self.auditor, + "notificationTemplate": self.notification_template, + "lastModifiedBy": self.last_modified_by, + "receiver": self.receiver, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/models/web_dlp_global_options.py b/zscaler/zia/models/web_dlp_global_options.py new file mode 100644 index 00000000..c21d8149 --- /dev/null +++ b/zscaler/zia/models/web_dlp_global_options.py @@ -0,0 +1,93 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject +from zscaler.zia.models import urlcategory as urlcategory + + +class WebDlpGlobalOptions(ZscalerObject): + """ + A class for WebDlpGlobalOptions objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the WebDlpGlobalOptions model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.exempt_url_encoded_data = config["exemptUrlEncodedData"] if "exemptUrlEncodedData" in config else None + self.enable_npk_edm_templates = config["enableNpkEdmTemplates"] if "enableNpkEdmTemplates" in config else None + self.enable_npk_edm_templates_for_org = ( + config["enableNpkEdmTemplatesForOrg"] if "enableNpkEdmTemplatesForOrg" in config else None + ) + self.enable_inline_dlp_ocr = config["enableInlineDlpOcr"] if "enableInlineDlpOcr" in config else None + self.enable_casb_ocr = config["enableCasbOcr"] if "enableCasbOcr" in config else None + self.enable_email_dlp_ocr = config["enableEmailDlpOcr"] if "enableEmailDlpOcr" in config else None + self.enable_evaluate_all_dlp_rules = ( + config["enableEvaluateAllDlpRules"] if "enableEvaluateAllDlpRules" in config else None + ) + self.enable_edm_popular_format = config["enableEdmPopularFormat"] if "enableEdmPopularFormat" in config else None + self.applications = ZscalerCollection.form_list(config["applications"] if "applications" in config else [], str) + self.urls = ZscalerCollection.form_list(config["urls"] if "urls" in config else [], str) + self.url_categories = ZscalerCollection.form_list( + config["urlCategories"] if "urlCategories" in config else [], urlcategory.URLCategory + ) + self.http_get_custom_url_categories = ZscalerCollection.form_list( + config["httpGetCustomUrlCategories"] if "httpGetCustomUrlCategories" in config else [], str + ) + else: + self.exempt_url_encoded_data = None + self.enable_npk_edm_templates = None + self.enable_npk_edm_templates_for_org = None + self.enable_inline_dlp_ocr = None + self.enable_casb_ocr = None + self.enable_email_dlp_ocr = None + self.enable_evaluate_all_dlp_rules = None + self.enable_edm_popular_format = None + self.applications = [] + self.urls = [] + self.url_categories = [] + self.http_get_custom_url_categories = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "exemptUrlEncodedData": self.exempt_url_encoded_data, + "enableNpkEdmTemplates": self.enable_npk_edm_templates, + "enableNpkEdmTemplatesForOrg": self.enable_npk_edm_templates_for_org, + "enableInlineDlpOcr": self.enable_inline_dlp_ocr, + "enableCasbOcr": self.enable_casb_ocr, + "enableEmailDlpOcr": self.enable_email_dlp_ocr, + "enableEvaluateAllDlpRules": self.enable_evaluate_all_dlp_rules, + "enableEdmPopularFormat": self.enable_edm_popular_format, + "applications": self.applications, + "urls": self.urls, + "urlCategories": [item.request_format() for item in (self.url_categories or [])], + "httpGetCustomUrlCategories": self.http_get_custom_url_categories, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zia/nss_collectors.py b/zscaler/zia/nss_collectors.py new file mode 100644 index 00000000..b51c98f0 --- /dev/null +++ b/zscaler/zia/nss_collectors.py @@ -0,0 +1,92 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.nss_collectors import NssCollectors + + +class NssCollectorsAPI(APIClient): + """ + A Client object for the NSS Collectors resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_collectors(self, query_params: Optional[dict] = None) -> APIResult[List[NssCollectors]]: + """ + Lists the NSS Collector servers configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of NssCollectors instances, Response, error) + + Examples: + List NSS collectors: + + >>> collector_list, _, error = client.zia.nss_collectors.list_collectors() + >>> if error: + ... print(f"Error listing NSS collectors: {error}") + ... return + ... print(f"Total NSS collectors found: {len(collector_list)}") + ... for collector in collector_list: + ... print(collector.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /nssCollectors + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(NssCollectors(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/outbound_email_dlp_rules.py b/zscaler/zia/outbound_email_dlp_rules.py new file mode 100644 index 00000000..1b115420 --- /dev/null +++ b/zscaler/zia/outbound_email_dlp_rules.py @@ -0,0 +1,456 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.outbound_email_dlp_rules import OutboundEmailDlpRules + + +class OutboundEmailDLPRulesAPI(APIClient): + """ + A Client object for the Outbound Email DLP Rules resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def list_rules(self, query_params: Optional[dict] = None) -> APIResult[List[OutboundEmailDlpRules]]: + """ + Lists the outbound email DLP rules configured in your organization. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.org_id]`` {int}: Filters the results for the specified organization. + + Returns: + tuple: A tuple containing (list of OutboundEmailDlpRules instances, Response, error) + + Examples: + List outbound email DLP rules: + + >>> rule_list, _, error = client.zia.outbound_email_dlp_rules.list_rules() + >>> if error: + ... print(f"Error listing outbound email DLP rules: {error}") + ... return + ... print(f"Total outbound email DLP rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + List outbound email DLP rules using filters: + + >>> rule_list, _, error = client.zia.outbound_email_dlp_rules.list_rules( + ... query_params={'org_id': 'VALUE'}) + >>> if error: + ... print(f"Error listing outbound email DLP rules: {error}") + ... return + ... print(f"Total outbound email DLP rules found: {len(rule_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(OutboundEmailDlpRules(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_rule(self, rule_id: int) -> APIResult[OutboundEmailDlpRules]: + """ + Fetches a specific outbound email DLP rule by ID. + + Args: + rule_id (int): The unique identifier for the outbound email DLP rule. + + Returns: + tuple: A tuple containing (OutboundEmailDlpRules instance, Response, error). + + Examples: + Print a specific outbound email DLP rule: + + >>> fetched_rule, _, error = client.zia.outbound_email_dlp_rules.get_rule(1013) + >>> if error: + ... print(f"Error fetching outbound email DLP rule by ID: {error}") + ... return + ... print(f"Fetched outbound email DLP rule by ID: {fetched_rule.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules/{rule_id} + """) + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, OutboundEmailDlpRules) + if error: + return (None, response, error) + + try: + result = OutboundEmailDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_rules_lite(self, query_params: Optional[dict] = None) -> APIResult[List[OutboundEmailDlpRules]]: + """ + Lists a lightweight version of the outbound email DLP rules. + + Args: + query_params {dict}: Map of query parameters for the request. + + Returns: + tuple: A tuple containing (list of OutboundEmailDlpRules instances, Response, error) + + Examples: + List outbound email DLP rules: + + >>> rule_list, _, error = client.zia.outbound_email_dlp_rules.list_rules_lite() + >>> if error: + ... print(f"Error listing outbound email DLP rules: {error}") + ... return + ... print(f"Total outbound email DLP rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules/lite + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(OutboundEmailDlpRules(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_rule(self, **kwargs) -> APIResult[OutboundEmailDlpRules]: + """ + Creates a new outbound email DLP rule. + + Args: + name (str): The name of the outbound email DLP rule. + **kwargs: Optional keyword args. + + Keyword Args: + order (int): The order of the outbound email DLP rule, defaults to adding the outbound email DLP rule to the bottom of the list. + description (str): Additional information about the outbound email DLP rule. + state (str): The outbound email DLP rule state. Accepted values are 'ENABLED' or 'DISABLED'. + action (str): The action taken when traffic matches the outbound email DLP rule criteria. + min_size (int): The min size for this outbound email DLP rule. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this outbound email DLP rule. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + severity (str): The severity level assigned to the outbound email DLP rule. + parent_rule (int): The parent rule for this outbound email DLP rule. + custom_header (str): The custom header for this outbound email DLP rule. + groups (list): The IDs for the groups that this outbound email DLP rule applies to. + departments (list): The IDs for the departments that this outbound email DLP rule applies to. + users (list): The IDs for the users that this outbound email DLP rule applies to. + excluded_groups (list): The IDs for the excluded groups that this outbound email DLP rule applies to. + excluded_departments (list): The IDs for the excluded departments that this outbound email DLP rule applies to. + excluded_users (list): The IDs for the excluded users that this outbound email DLP rule applies to. + time_windows (list): The IDs for the time windows that this outbound email DLP rule applies to. + dlp_engines (list): The IDs for the dlp engines that this outbound email DLP rule applies to. + file_types (list): The list of file types for this outbound email DLP rule. + labels (list): The IDs for the labels that this outbound email DLP rule applies to. + included_domain_profiles (list): The IDs for the included domain profiles that this outbound email DLP rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this outbound email DLP rule. Accepted values include e.g. ``LOW``. + email_tenants (list): The IDs for the email tenants that this outbound email DLP rule applies to. + content_locations (list): The list of content locations for this outbound email DLP rule. Accepted values include e.g. ``ANY``. + sub_rules (list): The IDs for the sub rules that this outbound email DLP rule applies to. + email_recipient_profiles (list): The IDs for the email recipient profiles that this outbound email DLP rule applies to. + auditor (dict): The ID of the auditor for this outbound email DLP rule, e.g. ``{'id': 12345}``. + notification_template (dict): The ID of the notification template for this outbound email DLP rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this outbound email DLP rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the newly added OutboundEmailDlpRules instance, response, and error. + + Examples: + Add a new outbound email DLP rule: + + >>> added_rule, _, error = client.zia.outbound_email_dlp_rules.add_rule( + ... name=f"NewRule_{random.randint(1000, 10000)}", + ... description=f"NewRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... order=1, + ... severity='RULE_SEVERITY_HIGH', + ... user_risk_score_levels=['LOW'], + ... ) + >>> if error: + ... print(f"Error adding outbound email DLP rule: {error}") + ... return + ... print(f"Outbound email dlp rule added successfully: {added_rule.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules + """) + + body = kwargs + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body=body, + ) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, OutboundEmailDlpRules) + if error: + return (None, response, error) + + try: + result = OutboundEmailDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_rule(self, rule_id: int, **kwargs) -> APIResult[OutboundEmailDlpRules]: + """ + Updates information for the specified outbound email DLP rule. + + Args: + rule_id (int): The unique identifier for the outbound email DLP rule. + + Keyword Args: + name (str): The name of the outbound email DLP rule. + order (int): The order of the outbound email DLP rule, defaults to adding the outbound email DLP rule to the bottom of the list. + description (str): Additional information about the outbound email DLP rule. + state (str): The outbound email DLP rule state. Accepted values are 'ENABLED' or 'DISABLED'. + action (str): The action taken when traffic matches the outbound email DLP rule criteria. + min_size (int): The min size for this outbound email DLP rule. + without_content_inspection (bool): A Boolean value indicating whether without content inspection applies to this outbound email DLP rule. + external_auditor_email (str): The email address of an external auditor to whom DLP email notifications are sent. + severity (str): The severity level assigned to the outbound email DLP rule. + parent_rule (int): The parent rule for this outbound email DLP rule. + custom_header (str): The custom header for this outbound email DLP rule. + groups (list): The IDs for the groups that this outbound email DLP rule applies to. + departments (list): The IDs for the departments that this outbound email DLP rule applies to. + users (list): The IDs for the users that this outbound email DLP rule applies to. + excluded_groups (list): The IDs for the excluded groups that this outbound email DLP rule applies to. + excluded_departments (list): The IDs for the excluded departments that this outbound email DLP rule applies to. + excluded_users (list): The IDs for the excluded users that this outbound email DLP rule applies to. + time_windows (list): The IDs for the time windows that this outbound email DLP rule applies to. + dlp_engines (list): The IDs for the dlp engines that this outbound email DLP rule applies to. + file_types (list): The list of file types for this outbound email DLP rule. + labels (list): The IDs for the labels that this outbound email DLP rule applies to. + included_domain_profiles (list): The IDs for the included domain profiles that this outbound email DLP rule applies to. + user_risk_score_levels (list): The list of user risk score levels for this outbound email DLP rule. Accepted values include e.g. ``LOW``. + email_tenants (list): The IDs for the email tenants that this outbound email DLP rule applies to. + content_locations (list): The list of content locations for this outbound email DLP rule. Accepted values include e.g. ``ANY``. + sub_rules (list): The IDs for the sub rules that this outbound email DLP rule applies to. + email_recipient_profiles (list): The IDs for the email recipient profiles that this outbound email DLP rule applies to. + auditor (dict): The ID of the auditor for this outbound email DLP rule, e.g. ``{'id': 12345}``. + notification_template (dict): The ID of the notification template for this outbound email DLP rule, e.g. ``{'id': 12345}``. + receiver (dict): The ID of the receiver for this outbound email DLP rule, e.g. ``{'id': 12345}``. + + Returns: + tuple: A tuple containing the updated OutboundEmailDlpRules instance, response, and error. + + Examples: + Update an existing outbound email DLP rule: + + >>> updated_rule, _, error = client.zia.outbound_email_dlp_rules.update_rule( + ... rule_id=1013, + ... name=f"UpdatedRule_{random.randint(1000, 10000)}", + ... description=f"UpdatedRule_{random.randint(1000, 10000)}", + ... state='ENABLED', + ... action='ALLOW', + ... ) + >>> if error: + ... print(f"Error updating outbound email DLP rule: {error}") + ... return + ... print(f"Outbound email dlp rule updated successfully: {updated_rule.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules/{rule_id} + """) + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, OutboundEmailDlpRules) + if error: + return (None, response, error) + + try: + result = OutboundEmailDlpRules(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_rule(self, rule_id: int) -> APIResult[None]: + """ + Deletes the specified outbound email DLP rule. + + Args: + rule_id (int): The unique identifier for the outbound email DLP rule. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a outbound email DLP rule: + + >>> _, _, error = client.zia.outbound_email_dlp_rules.delete_rule(1013) + >>> if error: + ... print(f"Error deleting outbound email DLP rule: {error}") + ... return + ... print(f"Outbound email dlp rule deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules/{rule_id} + """) + + params = {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def get_actions(self, query_params: Optional[dict] = None, output_file: str = None) -> APIResult[bytes]: + """ + Retrieves a mapping of supported outbound email DLP rule actions for the specified email tenant applications. The response body is a CSV file. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.tenantIds]`` {list}: One or more email tenant application IDs (required + output_file (str): Optional path; when given, the downloaded bytes are written to this file. + + Returns: + tuple: A 2-tuple of (the CSV mapping of supported outbound email DLP rule actions as bytes, error). + + Examples: + >>> content, error = client.zia.outbound_email_dlp_rules.get_actions(output_file='get_actions.csv') + >>> if error: + ... print(f"Error downloading outbound email DLP rule: {error}") + ... return + ... print("Downloaded successfully.") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /emailDlpRules/actions + """) + + query_params = query_params or {} + + headers = { + "Accept": "application/octet-stream", + "Content-Type": "application/json", + } + + request, error = self._request_executor.create_request( + method=http_method, + endpoint=api_url, + body={}, + headers=headers, + params=query_params, + ) + if error: + return (None, error) + + response, error = self._request_executor.execute(request, return_raw_response=True) + if error: + return (None, f"Request failed: {error}") + + content = response.content + + if output_file: + with open(output_file, "wb") as f: + f.write(content) + + return (content, None) diff --git a/zscaler/zia/partner_integrations.py b/zscaler/zia/partner_integrations.py index 97a26e99..ab4b8327 100644 --- a/zscaler/zia/partner_integrations.py +++ b/zscaler/zia/partner_integrations.py @@ -157,8 +157,7 @@ def list_crowdstrike_endpoints(self, query_params=None) -> APIResult[List[CrowdS body = {} headers = {} - request, error = self._request_executor.create_request( - http_method, api_url, body, headers, params=query_params) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) diff --git a/zscaler/zia/rule_labels.py b/zscaler/zia/rule_labels.py index 6575d5d5..ccfc5c4d 100644 --- a/zscaler/zia/rule_labels.py +++ b/zscaler/zia/rule_labels.py @@ -25,7 +25,7 @@ class RuleLabelsAPI(APIClient): """ - A Client object for the Rule labels resource. + A Client object for the Rule Labels resource. """ _zia_base_endpoint = "/zia/api/v1" @@ -36,40 +36,43 @@ def __init__(self, request_executor: "RequestExecutor") -> None: def list_labels(self, query_params: Optional[dict] = None) -> APIResult[List[RuleLabels]]: """ - Lists rule labels in your organization with pagination. - A subset of rule labels can be returned that match a supported - filter expression or query. + Lists the rule labels configured in your organization. Args: query_params {dict}: Map of query parameters for the request. ``[query_params.page]`` {int}: Specifies the page offset. - ``[query_params.page_size]`` {int}: Page size for pagination. - ``[query_params.search]`` {str}: Search string for filtering results. Returns: - tuple: A tuple containing (list of Rule Labels instances, Response, error) + tuple: A tuple containing (list of RuleLabels instances, Response, error) Examples: - List Rule Labels using default settings: + List rule labels: - >>> label_list, _, error = client.zia.rule_labels.list_labels( - query_params={'search': updated_label.name}) + >>> label_list, _, error = client.zia.rule_labels.list_labels() >>> if error: - ... print(f"Error listing labels: {error}") + ... print(f"Error listing rule labels: {error}") ... return - ... print(f"Total labels found: {len(label_list)}") + ... print(f"Total rule labels found: {len(label_list)}") ... for label in label_list: ... print(label.as_dict()) + List rule labels using filters: + + >>> label_list, _, error = client.zia.rule_labels.list_labels( + ... query_params={'page': 'VALUE'}) + >>> if error: + ... print(f"Error listing rule labels: {error}") + ... return + ... print(f"Total rule labels found: {len(label_list)}") + Client-side filtering with JMESPath: The response object supports client-side filtering and projection via ``resp.search(expression)``. See the `JMESPath documentation `_ for expression syntax. - """ http_method = "get".upper() api_url = format_url(f""" @@ -100,25 +103,24 @@ def list_labels(self, query_params: Optional[dict] = None) -> APIResult[List[Rul return (None, response, error) return (result, response, None) - def get_label(self, label_id: int) -> APIResult[dict]: + def get_label(self, label_id: int) -> APIResult[RuleLabels]: """ - Fetches a specific rule labels by ID. + Fetches a specific rule label by ID. Args: label_id (int): The unique identifier for the rule label. Returns: - tuple: A tuple containing (Rule Label instance, Response, error). + tuple: A tuple containing (RuleLabels instance, Response, error). Examples: - Print a specific Rule Label + Print a specific rule label: - >>> fetched_label, _, error = client.zia.rule_labels.get_label( - '1254654') + >>> fetched_label, _, error = client.zia.rule_labels.get_label(1013) >>> if error: - ... print(f"Error fetching Rule Label by ID: {error}") + ... print(f"Error fetching rule label by ID: {error}") ... return - ... print(f"Fetched Rule Label by ID: {fetched_label.as_dict()}") + ... print(f"Fetched rule label by ID: {fetched_label.as_dict()}") """ http_method = "get".upper() api_url = format_url(f""" @@ -144,31 +146,33 @@ def get_label(self, label_id: int) -> APIResult[dict]: return (None, response, error) return (result, response, None) - def add_label(self, **kwargs) -> APIResult[dict]: + def add_label(self, **kwargs) -> APIResult[RuleLabels]: """ - Creates a new ZIA Rule Label. + Creates a new rule label. Args: - name (str): The name of the label. + name (str): The name of the rule label. **kwargs: Optional keyword args. Keyword Args: - description (str): Additional notes or information + description (str): Additional information about the rule label. + created_by (str): The created by for this rule label. + referenced_rule_count (str): The referenced rule count for this rule label. Returns: - tuple: A tuple containing the newly added Rule Label, response, and error. + tuple: A tuple containing the newly added RuleLabels instance, response, and error. Examples: - Add a new Rule Label : + Add a new rule label: >>> added_label, _, error = client.zia.rule_labels.add_label( ... name=f"NewLabel_{random.randint(1000, 10000)}", ... description=f"NewLabel_{random.randint(1000, 10000)}", ... ) >>> if error: - ... print(f"Error adding label: {error}") + ... print(f"Error adding rule label: {error}") ... return - ... print(f"Label added successfully: {added_label.as_dict()}") + ... print(f"Rule label added successfully: {added_label.as_dict()}") """ http_method = "post".upper() api_url = format_url(f""" @@ -197,28 +201,34 @@ def add_label(self, **kwargs) -> APIResult[dict]: return (None, response, error) return (result, response, None) - def update_label(self, label_id: int, **kwargs) -> APIResult[dict]: + def update_label(self, label_id: int, **kwargs) -> APIResult[RuleLabels]: """ - Updates information for the specified ZIA Rule Label. + Updates information for the specified rule label. Args: - label_id (int): The unique ID for the Rule Label. + label_id (int): The unique identifier for the rule label. + + Keyword Args: + name (str): The name of the rule label. + description (str): Additional information about the rule label. + created_by (str): The created by for this rule label. + referenced_rule_count (str): The referenced rule count for this rule label. Returns: - tuple: A tuple containing the updated Rule Label, response, and error. + tuple: A tuple containing the updated RuleLabels instance, response, and error. Examples: - Update an existing Rule Label : + Update an existing rule label: - >>> updated_label, _, error = client.zia.rule_labels.add_label( - label_id='1524566' - ... name=f"UpdatedRuleLabel_{random.randint(1000, 10000)}", - ... description=f"UpdatedRuleLabel_{random.randint(1000, 10000)}", + >>> updated_label, _, error = client.zia.rule_labels.update_label( + ... label_id=1013, + ... name=f"UpdatedLabel_{random.randint(1000, 10000)}", + ... description=f"UpdatedLabel_{random.randint(1000, 10000)}", ... ) >>> if error: - ... print(f"Error updating Rule Label: {error}") + ... print(f"Error updating rule label: {error}") ... return - ... print(f"Rule Label updated successfully: {updated_label.as_dict()}") + ... print(f"Rule label updated successfully: {updated_label.as_dict()}") """ http_method = "put".upper() api_url = format_url(f""" @@ -241,24 +251,24 @@ def update_label(self, label_id: int, **kwargs) -> APIResult[dict]: return (None, response, error) return (result, response, None) - def delete_label(self, label_id: int) -> APIResult[dict]: + def delete_label(self, label_id: int) -> APIResult[None]: """ - Deletes the specified Rule Label. + Deletes the specified rule label. Args: - label_id (str): The unique identifier of the Rule Label. + label_id (int): The unique identifier for the rule label. Returns: tuple: A tuple containing the response object and error (if any). Examples: - Delete a Rule Label: + Delete a rule label: - >>> _, _, error = client.zia.rule_labels.delete_label('73459') + >>> _, _, error = client.zia.rule_labels.delete_label(1013) >>> if error: - ... print(f"Error deleting Rule Label: {error}") + ... print(f"Error deleting rule label: {error}") ... return - ... print(f"Rule Label with ID {'73459' deleted successfully.") + ... print(f"Rule label deleted successfully.") """ http_method = "delete".upper() api_url = format_url(f""" @@ -277,28 +287,46 @@ def delete_label(self, label_id: int) -> APIResult[dict]: return (None, response, error) return (None, response, None) - def get_rule_type_label(self, rule_type: str) -> APIResult[List[RuleLabels]]: + def get_rule_type_label(self, rule_type: str, query_params: Optional[dict] = None) -> APIResult[List[RuleLabels]]: """ - Retrieves a list of rule labels based on the specified rule type + Retrieves a list of rule labels based on the specified rule type. Args: rule_type (str): The type of rule to retrieve labels for. - Only supported values are: URL_FILTERING, FIREWALL, CASB_DLP, CLOUD_APP_CONTROL, - DATA_PROTECTION, GENAI, INDUSTRY_PEER, NEWS_FEED, RISK_SCORE, SANDBOX + query_params {dict}: Map of query parameters for the request. + ``[query_params.page]`` {int}: Specifies the page offset. + ``[query_params.page_size]`` {int}: Page size for pagination. + ``[query_params.search]`` {str}: Search string for filtering results. Returns: - tuple: A tuple containing (list of Rule Labels instances, Response, error) + tuple: A tuple containing (list of RuleLabels instances, Response, error) Examples: - Get Rule Labels for a specific rule type: + List rule labels: - >>> fetched_labels, _, error = client.zia.rule_labels.get_rule_type_label('URL_FILTERING') + >>> label_list, _, error = client.zia.rule_labels.get_rule_type_label('URL_FILTERING') >>> if error: - ... print(f"Error listing labels: {error}") + ... print(f"Error listing rule labels: {error}") ... return - ... print(f"Total labels found: {len(fetched_labels)}") - ... for label in fetched_labels: + ... print(f"Total rule labels found: {len(label_list)}") + ... for label in label_list: ... print(label.as_dict()) + + List rule labels using filters: + + >>> label_list, _, error = client.zia.rule_labels.get_rule_type_label( + ... 'URL_FILTERING', query_params={'page': 'VALUE'}) + >>> if error: + ... print(f"Error listing rule labels: {error}") + ... return + ... print(f"Total rule labels found: {len(label_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. """ http_method = "get".upper() api_url = format_url(f""" @@ -306,10 +334,12 @@ def get_rule_type_label(self, rule_type: str) -> APIResult[List[RuleLabels]]: /ruleLabels/ruleType/{rule_type} """) + query_params = query_params or {} + body = {} headers = {} - request, error = self._request_executor.create_request(http_method, api_url, body, headers) + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) if error: return (None, None, error) diff --git a/zscaler/zia/web_dlp_global_options.py b/zscaler/zia/web_dlp_global_options.py new file mode 100644 index 00000000..0b3a5de6 --- /dev/null +++ b/zscaler/zia/web_dlp_global_options.py @@ -0,0 +1,128 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zia.models.web_dlp_global_options import WebDlpGlobalOptions + + +class WebDlpGlobalOptionsAPI(APIClient): + """ + A Client object for the web_dlp_global_options resource. + """ + + _zia_base_endpoint = "/zia/api/v1" + + def __init__(self, request_executor: "RequestExecutor") -> None: + super().__init__() + self._request_executor: RequestExecutor = request_executor + + def get_global_options(self, query_params: Optional[dict] = None) -> APIResult[List[WebDlpGlobalOptions]]: + """ + Retrieves the DLP Advanced Settings information + + Returns: + tuple: A tuple containing (list of WebDlpGlobalOptions instances, Response, error) + + Examples: + Print the fetched global options: + + >>> fetched_options, _, error = client.zia.web_dlp_global_options.get_global_options() + >>> if error: + ... print(f"Error fetching global options: {error}") + ... return + ... print(f"Fetched global options: {fetched_options.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /webDlpGlobalOptions + """) + + query_params = query_params or {} + + body = {} + headers = {} + + request, error = self._request_executor.create_request(http_method, api_url, body, headers, params=query_params) + + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(WebDlpGlobalOptions(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_option(self, **kwargs) -> APIResult[WebDlpGlobalOptions]: + """ + Updates the existing DLP Advanced Settings. + + Args: + name (str): Name to identify the time interval + **kwargs: Optional keyword args. + + Keyword Args: + applications (list): List of cloud applications exempted from DLP evaluation + url_categories (list): List of custom URL categories exempted from DLP evaluation + exempt_url_encoded_data (boolean): Indicates whether or not URL encoded data from DLP evaluation is exempted + enable_npk_edm_templates (boolean): Indicates whether EDM with No Primary Keys is enabled. + enable_npk_edm_templates_for_org (boolean): Indicates whether EDM with No Primary Keys is enabled for the organization. + enable_inline_dlp_ocr (boolean): Indicates whether optical character recognition (OCR) + for Zscaler DLP engines to scan images for text content in data in transit is enabled + enable_casb_ocr (boolean): Indicates whether SaaS Security for Zscaler DLP engines to scan images for text content in data at rest is enabled + enable_email_dlp_ocr (boolean): Indicates whether Outbound Email DLP for Zscaler DLP engines + to scan images for text content in outbound emails is sent to external domains + enable_evaluate_all_dlp_rules (boolean): Indicates whether DLP engines evaluate all rules or stop when a matching rule is found + enable_edm_popular_format (boolean): Indicates whether EDM with popular formats is enabled. + http_get_custom_url_categories (list): List of URL Categories to associate with Inspect HTTP GET Requests + + Returns: + tuple: A tuple containing the WebDlpGlobalOptions instance, response, and error. + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zia_base_endpoint} + /webDlpGlobalOptions + """) + + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, {}) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, WebDlpGlobalOptions) + if error: + return (None, response, error) + + try: + result = WebDlpGlobalOptions(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zia/zia_service.py b/zscaler/zia/zia_service.py index db46512a..7e898d6b 100644 --- a/zscaler/zia/zia_service.py +++ b/zscaler/zia/zia_service.py @@ -47,13 +47,22 @@ from zscaler.zia.device_management import DeviceManagementAPI from zscaler.zia.devices import DevicesAPI from zscaler.zia.dlp_dictionary import DLPDictionaryAPI +from zscaler.zia.dlp_endpoint_resource import DLPEndpointResourceAPI from zscaler.zia.dlp_engine import DLPEngineAPI from zscaler.zia.dlp_resources import DLPResourcesAPI from zscaler.zia.dlp_templates import DLPTemplatesAPI from zscaler.zia.dlp_web_rules import DLPWebRuleAPI +from zscaler.zia.dns_application_groups import DNSApplicationGroupsAPI from zscaler.zia.dns_gatways import DNSGatewayAPI from zscaler.zia.email_profiles import EmailProfilesAPI from zscaler.zia.end_user_notification import EndUserNotificationAPI +from zscaler.zia.end_user_notification_templates import EndUserNotificationTemplatesAPI +from zscaler.zia.endpoint_application_groups import EndpointApplicationGroupsAPI +from zscaler.zia.endpoint_applications import EndpointApplicationsAPI +from zscaler.zia.endpoint_custom_apps import EndpointCustomAppsAPI +from zscaler.zia.endpoint_dlp_resource_groups import EndpointDLPResourceGroupsAPI +from zscaler.zia.endpoint_dlp_rules import EndpointDLPRulesAPI +from zscaler.zia.endpoint_dlp_sub_rules import EndpointDLPSubRulesAPI from zscaler.zia.file_type_control_rule import FileTypeControlRuleAPI from zscaler.zia.forwarding_control import ForwardingControlAPI from zscaler.zia.ftp_control_policy import FTPControlPolicyAPI @@ -61,14 +70,17 @@ from zscaler.zia.http_header_control import HttpHeaderControlAPI from zscaler.zia.intermediate_certificates import IntermediateCertsAPI from zscaler.zia.iot_report import IOTReportAPI +from zscaler.zia.ips_categories import IpsCategoriesAPI from zscaler.zia.ips_signature_rules import IPSSignatureRulesAPI from zscaler.zia.ipv6_config import TrafficIPV6ConfigAPI from zscaler.zia.locations import LocationsAPI from zscaler.zia.malware_protection_policy import MalwareProtectionPolicyAPI from zscaler.zia.mobile_threat_settings import MobileAdvancedSettingsAPI from zscaler.zia.nat_control_policy import NatControlPolicyAPI +from zscaler.zia.nss_collectors import NssCollectorsAPI from zscaler.zia.nss_servers import NssServersAPI from zscaler.zia.organization_information import OrganizationInformationAPI +from zscaler.zia.outbound_email_dlp_rules import OutboundEmailDLPRulesAPI from zscaler.zia.pac_files import PacFilesAPI from zscaler.zia.partner_integrations import PartnerIntegrationsAPI from zscaler.zia.policy_export import PolicyExportAPI @@ -99,6 +111,7 @@ from zscaler.zia.user_management import UserManagementAPI from zscaler.zia.vzen_clusters import VZENClustersAPI from zscaler.zia.vzen_nodes import VZENNodesAPI +from zscaler.zia.web_dlp_global_options import WebDlpGlobalOptionsAPI from zscaler.zia.workload_groups import WorkloadGroupsAPI from zscaler.zia.zpa_gateway import ZPAGatewayAPI @@ -824,3 +837,107 @@ def smpc_instance(self) -> SmpcInstanceAPI: """ return SmpcInstanceAPI(self._request_executor) + + @property + def dns_application_groups(self) -> DNSApplicationGroupsAPI: + """ + The interface object for the :ref:`ZIA DNS Application Groups interface `. + + """ + return DNSApplicationGroupsAPI(self._request_executor) + + @property + def endpoint_dlp_rules(self) -> EndpointDLPRulesAPI: + """ + The interface object for the :ref:`ZIA Endpoint DLP Rules interface `. + + """ + return EndpointDLPRulesAPI(self._request_executor) + + @property + def dlp_endpoint_resource(self) -> DLPEndpointResourceAPI: + """ + The interface object for the :ref:`ZIA DLP Endpoint Resources interface `. + + """ + return DLPEndpointResourceAPI(self._request_executor) + + @property + def web_dlp_global_options(self) -> WebDlpGlobalOptionsAPI: + """ + The interface object for the :ref:`ZIA DLP Advanced Settings information interface `. + + """ + return WebDlpGlobalOptionsAPI(self._request_executor) + + @property + def end_user_notification_templates(self) -> EndUserNotificationTemplatesAPI: + """ + The interface object for the :ref:`ZIA End User Notification Templates interface `. + + """ + return EndUserNotificationTemplatesAPI(self._request_executor) + + @property + def endpoint_application_groups(self) -> EndpointApplicationGroupsAPI: + """ + The interface object for the :ref:`ZIA Endpoint Application Groups interface `. + + """ + return EndpointApplicationGroupsAPI(self._request_executor) + + @property + def endpoint_applications(self) -> EndpointApplicationsAPI: + """ + The interface object for the :ref:`ZIA Endpoint Applications interface `. + + """ + return EndpointApplicationsAPI(self._request_executor) + + @property + def endpoint_custom_apps(self) -> EndpointCustomAppsAPI: + """ + The interface object for the :ref:`ZIA Endpoint Custom Apps interface `. + + """ + return EndpointCustomAppsAPI(self._request_executor) + + @property + def endpoint_dlp_resource_groups(self) -> EndpointDLPResourceGroupsAPI: + """ + The interface object for the :ref:`ZIA Endpoint DLP Resource Groups interface `. + + """ + return EndpointDLPResourceGroupsAPI(self._request_executor) + + @property + def endpoint_dlp_sub_rules(self) -> EndpointDLPSubRulesAPI: + """ + The interface object for the :ref:`ZIA Endpoint DLP Sub-Rules interface `. + + """ + return EndpointDLPSubRulesAPI(self._request_executor) + + @property + def outbound_email_dlp_rules(self) -> OutboundEmailDLPRulesAPI: + """ + The interface object for the :ref:`ZIA Outbound Email DLP Rules interface `. + + """ + return OutboundEmailDLPRulesAPI(self._request_executor) + + @property + def ips_categories(self) -> IpsCategoriesAPI: + """ + The interface object for the :ref:`ZIA IPS Categories interface `. + + """ + return IpsCategoriesAPI(self._request_executor) + + @property + def nss_collectors(self) -> NssCollectorsAPI: + """ + The interface object for the :ref:`ZIA NSS Collectors interface `. + + """ + return NssCollectorsAPI(self._request_executor) diff --git a/zscaler/zpa/models/application_segment.py b/zscaler/zpa/models/application_segment.py index dfa5ac30..97af1a0b 100644 --- a/zscaler/zpa/models/application_segment.py +++ b/zscaler/zpa/models/application_segment.py @@ -1063,7 +1063,6 @@ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: self.ext_domain_name = None self.ext_id = None self.ext_label = None - def request_format(self) -> Dict[str, Any]: """ diff --git a/zscaler/zpa/models/policy_group.py b/zscaler/zpa/models/policy_group.py new file mode 100644 index 00000000..01c3bb18 --- /dev/null +++ b/zscaler/zpa/models/policy_group.py @@ -0,0 +1,512 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject +from zscaler.zpa.models import app_connector_groups as app_connector_groups +from zscaler.zpa.models import common as common +from zscaler.zpa.models import policyset_controller_v2 as policyset_controller_v2 +from zscaler.zpa.models import server_group as server_group +from zscaler.zpa.models import service_edge_groups as service_edge_groups + + +class PolicyGroup(ZscalerObject): + """ + A class for PolicyGroup objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyGroup model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.description = config["description"] if "description" in config else None + self.group_criteria_rule_gid = config["groupCriteriaRuleGid"] if "groupCriteriaRuleGid" in config else None + self.group_order = config["groupOrder"] if "groupOrder" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.name = config["name"] if "name" in config else None + self.policy_group_set_gid = config["policyGroupSetGid"] if "policyGroupSetGid" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.microtenant_name = config["microtenantName"] if "microtenantName" in config else None + self.type = config["type"] if "type" in config else None + + if "groupCriteriaRule" in config: + if isinstance(config["groupCriteriaRule"], GroupCriteriaRule): + self.group_criteria_rule = config["groupCriteriaRule"] + elif config["groupCriteriaRule"] is not None: + self.group_criteria_rule = GroupCriteriaRule(config["groupCriteriaRule"]) + else: + self.group_criteria_rule = None + else: + self.group_criteria_rule = None + else: + self.creation_time = None + self.description = None + self.group_criteria_rule_gid = None + self.group_order = None + self.id = None + self.modified_by = None + self.modified_time = None + self.name = None + self.policy_group_set_gid = None + self.microtenant_id = None + self.microtenant_name = None + self.type = None + self.group_criteria_rule = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "creationTime": self.creation_time, + "description": self.description, + "groupCriteriaRuleGid": self.group_criteria_rule_gid, + "groupOrder": self.group_order, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "name": self.name, + "policyGroupSetGid": self.policy_group_set_gid, + "microtenantId": self.microtenant_id, + "microtenantName": self.microtenant_name, + "type": self.type, + "groupCriteriaRule": self.group_criteria_rule, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class GroupCriteriaRule(ZscalerObject): + """ + A class for GroupCriteriaRule objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the GroupCriteriaRule model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.action = config["action"] if "action" in config else None + self.action_id = config["actionId"] if "actionId" in config else None + self.browser_posture_profile_id = ( + config["browserPostureProfileId"] if "browserPostureProfileId" in config else None + ) + self.browser_posture_profile_name = ( + config["browserPostureProfileName"] if "browserPostureProfileName" in config else None + ) + self.button_text = config["buttonText"] if "buttonText" in config else None + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.custom_msg = config["customMsg"] if "customMsg" in config else None + self.default_rule = config["defaultRule"] if "defaultRule" in config else None + self.default_rule_name = config["defaultRuleName"] if "defaultRuleName" in config else None + self.description = config["description"] if "description" in config else None + self.device_posture_failure_notification_enabled = ( + config["devicePostureFailureNotificationEnabled"] + if "devicePostureFailureNotificationEnabled" in config + else None + ) + self.disabled = config["disabled"] if "disabled" in config else None + self.extranet_enabled = config["extranetEnabled"] if "extranetEnabled" in config else None + self.group_id = config["groupId"] if "groupId" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.name = config["name"] if "name" in config else None + self.name_without_trim = config["nameWithoutTrim"] if "nameWithoutTrim" in config else None + self.operator = config["operator"] if "operator" in config else None + self.policy_group_name = config["policyGroupName"] if "policyGroupName" in config else None + self.policy_set_id = config["policySetId"] if "policySetId" in config else None + self.policy_type = config["policyType"] if "policyType" in config else None + self.post_actions = config["postActions"] if "postActions" in config else None + self.priority = config["priority"] if "priority" in config else None + self.read_only = config["readOnly"] if "readOnly" in config else None + self.reauth_idle_timeout = config["reauthIdleTimeout"] if "reauthIdleTimeout" in config else None + self.reauth_timeout = config["reauthTimeout"] if "reauthTimeout" in config else None + self.restriction_type = config["restrictionType"] if "restrictionType" in config else None + self.rule_order = config["ruleOrder"] if "ruleOrder" in config else None + self.rule_type = config["ruleType"] if "ruleType" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.microtenant_name = config["microtenantName"] if "microtenantName" in config else None + self.url = config["url"] if "url" in config else None + self.zpn_isolation_profile_id = config["zpnIsolationProfileId"] if "zpnIsolationProfileId" in config else None + self.zpn_inspection_profile_id = config["zpnInspectionProfileId"] if "zpnInspectionProfileId" in config else None + self.zpn_inspection_profile_name = ( + config["zpnInspectionProfileName"] if "zpnInspectionProfileName" in config else None + ) + self.zscaler_managed = config["zscalerManaged"] if "zscalerManaged" in config else None + self.app_server_groups = ZscalerCollection.form_list( + config["appServerGroups"] if "appServerGroups" in config else [], server_group.ServerGroup + ) + self.app_connector_groups = ZscalerCollection.form_list( + config["appConnectorGroups"] if "appConnectorGroups" in config else [], app_connector_groups.AppConnectorGroup + ) + self.conditions = ZscalerCollection.form_list(config["conditions"] if "conditions" in config else [], Condition) + self.desktop_policy_mappings = ZscalerCollection.form_list( + config["desktopPolicyMappings"] if "desktopPolicyMappings" in config else [], common.DesktopPolicyMappingsDTO + ) + self.post_action_types = ZscalerCollection.form_list( + config["postActionTypes"] if "postActionTypes" in config else [], str + ) + self.service_edge_groups = ZscalerCollection.form_list( + config["serviceEdgeGroups"] if "serviceEdgeGroups" in config else [], service_edge_groups.ServiceEdgeGroup + ) + + if "credential" in config: + if isinstance(config["credential"], policyset_controller_v2.Credential): + self.credential = config["credential"] + elif config["credential"] is not None: + self.credential = policyset_controller_v2.Credential(config["credential"]) + else: + self.credential = None + else: + self.credential = None + + if "credentialPool" in config: + if isinstance(config["credentialPool"], policyset_controller_v2.Credential): + self.credential_pool = config["credentialPool"] + elif config["credentialPool"] is not None: + self.credential_pool = policyset_controller_v2.Credential(config["credentialPool"]) + else: + self.credential_pool = None + else: + self.credential_pool = None + + if "extranetDTO" in config: + if isinstance(config["extranetDTO"], common.ExtranetDTO): + self.extranet_dto = config["extranetDTO"] + elif config["extranetDTO"] is not None: + self.extranet_dto = common.ExtranetDTO(config["extranetDTO"]) + else: + self.extranet_dto = None + else: + self.extranet_dto = None + + if "inconsistentConfigDetails" in config: + if isinstance(config["inconsistentConfigDetails"], InconsistentConfigDetails): + self.inconsistent_config_details = config["inconsistentConfigDetails"] + elif config["inconsistentConfigDetails"] is not None: + self.inconsistent_config_details = InconsistentConfigDetails(config["inconsistentConfigDetails"]) + else: + self.inconsistent_config_details = None + else: + self.inconsistent_config_details = None + + if "privilegedCapabilities" in config: + if isinstance(config["privilegedCapabilities"], common.PrivilegedCapabilitiesResource): + self.privileged_capabilities = config["privilegedCapabilities"] + elif config["privilegedCapabilities"] is not None: + self.privileged_capabilities = common.PrivilegedCapabilitiesResource(config["privilegedCapabilities"]) + else: + self.privileged_capabilities = None + else: + self.privileged_capabilities = None + + if "privilegedPortalCapabilities" in config: + if isinstance(config["privilegedPortalCapabilities"], common.PrivilegedCapabilitiesResource): + self.privileged_portal_capabilities = config["privilegedPortalCapabilities"] + elif config["privilegedPortalCapabilities"] is not None: + self.privileged_portal_capabilities = common.PrivilegedCapabilitiesResource( + config["privilegedPortalCapabilities"] + ) + else: + self.privileged_portal_capabilities = None + else: + self.privileged_portal_capabilities = None + else: + self.action = None + self.action_id = None + self.browser_posture_profile_id = None + self.browser_posture_profile_name = None + self.button_text = None + self.creation_time = None + self.custom_msg = None + self.default_rule = None + self.default_rule_name = None + self.description = None + self.device_posture_failure_notification_enabled = None + self.disabled = None + self.extranet_enabled = None + self.group_id = None + self.id = None + self.modified_by = None + self.modified_time = None + self.name = None + self.name_without_trim = None + self.operator = None + self.policy_group_name = None + self.policy_set_id = None + self.policy_type = None + self.post_actions = None + self.priority = None + self.read_only = None + self.reauth_idle_timeout = None + self.reauth_timeout = None + self.restriction_type = None + self.rule_order = None + self.rule_type = None + self.microtenant_id = None + self.microtenant_name = None + self.url = None + self.zpn_isolation_profile_id = None + self.zpn_inspection_profile_id = None + self.zpn_inspection_profile_name = None + self.zscaler_managed = None + self.app_server_groups = [] + self.app_connector_groups = [] + self.conditions = [] + self.desktop_policy_mappings = [] + self.post_action_types = [] + self.service_edge_groups = [] + self.credential = None + self.credential_pool = None + self.extranet_dto = None + self.inconsistent_config_details = None + self.privileged_capabilities = None + self.privileged_portal_capabilities = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "action": self.action, + "actionId": self.action_id, + "browserPostureProfileId": self.browser_posture_profile_id, + "browserPostureProfileName": self.browser_posture_profile_name, + "buttonText": self.button_text, + "creationTime": self.creation_time, + "customMsg": self.custom_msg, + "defaultRule": self.default_rule, + "defaultRuleName": self.default_rule_name, + "description": self.description, + "devicePostureFailureNotificationEnabled": self.device_posture_failure_notification_enabled, + "disabled": self.disabled, + "extranetEnabled": self.extranet_enabled, + "groupId": self.group_id, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "name": self.name, + "nameWithoutTrim": self.name_without_trim, + "operator": self.operator, + "policyGroupName": self.policy_group_name, + "policySetId": self.policy_set_id, + "policyType": self.policy_type, + "postActions": self.post_actions, + "priority": self.priority, + "readOnly": self.read_only, + "reauthIdleTimeout": self.reauth_idle_timeout, + "reauthTimeout": self.reauth_timeout, + "restrictionType": self.restriction_type, + "ruleOrder": self.rule_order, + "ruleType": self.rule_type, + "microtenantId": self.microtenant_id, + "microtenantName": self.microtenant_name, + "url": self.url, + "zpnIsolationProfileId": self.zpn_isolation_profile_id, + "zpnInspectionProfileId": self.zpn_inspection_profile_id, + "zpnInspectionProfileName": self.zpn_inspection_profile_name, + "zscalerManaged": self.zscaler_managed, + "appServerGroups": [item.request_format() for item in (self.app_server_groups or [])], + "appConnectorGroups": [item.request_format() for item in (self.app_connector_groups or [])], + "conditions": [item.request_format() for item in (self.conditions or [])], + "desktopPolicyMappings": [item.request_format() for item in (self.desktop_policy_mappings or [])], + "postActionTypes": self.post_action_types, + "serviceEdgeGroups": [item.request_format() for item in (self.service_edge_groups or [])], + "credential": self.credential, + "credentialPool": self.credential_pool, + "extranetDTO": self.extranet_dto, + "inconsistentConfigDetails": self.inconsistent_config_details, + "privilegedCapabilities": self.privileged_capabilities, + "privilegedPortalCapabilities": self.privileged_portal_capabilities, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Condition(ZscalerObject): + """ + A class for Condition objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Condition model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.negated = config["negated"] if "negated" in config else None + self.operator = config["operator"] if "operator" in config else None + self.policy_set_type = config["policySetType"] if "policySetType" in config else None + self.rule_gid = config["ruleGid"] if "ruleGid" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.operands = ZscalerCollection.form_list(config["operands"] if "operands" in config else [], dict) + else: + self.creation_time = None + self.id = None + self.modified_by = None + self.modified_time = None + self.negated = None + self.operator = None + self.policy_set_type = None + self.rule_gid = None + self.microtenant_id = None + self.operands = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "creationTime": self.creation_time, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "negated": self.negated, + "operator": self.operator, + "policySetType": self.policy_set_type, + "ruleGid": self.rule_gid, + "microtenantId": self.microtenant_id, + "operands": self.operands, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class InconsistentConfigDetails(ZscalerObject): + """ + A class for InconsistentConfigDetails objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the InconsistentConfigDetails model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.application = ZscalerCollection.form_list(config["application"] if "application" in config else [], dict) + self.segment_group = ZscalerCollection.form_list(config["segmentGroup"] if "segmentGroup" in config else [], dict) + self.app_connector_group = ZscalerCollection.form_list( + config["appConnectorGroup"] if "appConnectorGroup" in config else [], dict + ) + self.ba_certificate = ZscalerCollection.form_list( + config["baCertificate"] if "baCertificate" in config else [], dict + ) + self.branch_connector_group = ZscalerCollection.form_list( + config["branchConnectorGroup"] if "branchConnectorGroup" in config else [], dict + ) + self.cloud_connector_group = ZscalerCollection.form_list( + config["cloudConnectorGroup"] if "cloudConnectorGroup" in config else [], dict + ) + self.idp = ZscalerCollection.form_list(config["idp"] if "idp" in config else [], dict) + self.location = ZscalerCollection.form_list(config["location"] if "location" in config else [], dict) + self.machine_group = ZscalerCollection.form_list(config["machineGroup"] if "machineGroup" in config else [], dict) + self.posture_profile = ZscalerCollection.form_list( + config["postureProfile"] if "postureProfile" in config else [], dict + ) + self.saml_attributes = ZscalerCollection.form_list( + config["samlAttributes"] if "samlAttributes" in config else [], dict + ) + self.scim_attributes = ZscalerCollection.form_list( + config["scimAttributes"] if "scimAttributes" in config else [], dict + ) + self.server_group = ZscalerCollection.form_list(config["serverGroup"] if "serverGroup" in config else [], dict) + self.sra_application = ZscalerCollection.form_list( + config["sraApplication"] if "sraApplication" in config else [], dict + ) + self.trusted_network = ZscalerCollection.form_list( + config["trustedNetwork"] if "trustedNetwork" in config else [], dict + ) + self.user_portal = ZscalerCollection.form_list(config["userPortal"] if "userPortal" in config else [], dict) + self.workload_tag_group = ZscalerCollection.form_list( + config["workloadTagGroup"] if "workloadTagGroup" in config else [], dict + ) + else: + self.application = [] + self.segment_group = [] + self.app_connector_group = [] + self.ba_certificate = [] + self.branch_connector_group = [] + self.cloud_connector_group = [] + self.idp = [] + self.location = [] + self.machine_group = [] + self.posture_profile = [] + self.saml_attributes = [] + self.scim_attributes = [] + self.server_group = [] + self.sra_application = [] + self.trusted_network = [] + self.user_portal = [] + self.workload_tag_group = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "application": self.application, + "segmentGroup": self.segment_group, + "appConnectorGroup": self.app_connector_group, + "baCertificate": self.ba_certificate, + "branchConnectorGroup": self.branch_connector_group, + "cloudConnectorGroup": self.cloud_connector_group, + "idp": self.idp, + "location": self.location, + "machineGroup": self.machine_group, + "postureProfile": self.posture_profile, + "samlAttributes": self.saml_attributes, + "scimAttributes": self.scim_attributes, + "serverGroup": self.server_group, + "sraApplication": self.sra_application, + "trustedNetwork": self.trusted_network, + "userPortal": self.user_portal, + "workloadTagGroup": self.workload_tag_group, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zpa/models/policy_group_set.py b/zscaler/zpa/models/policy_group_set.py new file mode 100644 index 00000000..e55d741e --- /dev/null +++ b/zscaler/zpa/models/policy_group_set.py @@ -0,0 +1,83 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class PolicyGroupSet(ZscalerObject): + """ + A class for PolicyGroupSet objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyGroupSet model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.default_policy_group_gid = config["defaultPolicyGroupGid"] if "defaultPolicyGroupGid" in config else None + self.global_policy_group_gid = config["globalPolicyGroupGid"] if "globalPolicyGroupGid" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.name = config["name"] if "name" in config else None + self.policy_type = config["policyType"] if "policyType" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.microtenant_name = config["microtenantName"] if "microtenantName" in config else None + self.custom_policy_group_gids = ZscalerCollection.form_list( + config["customPolicyGroupGids"] if "customPolicyGroupGids" in config else [], int + ) + else: + self.creation_time = None + self.default_policy_group_gid = None + self.global_policy_group_gid = None + self.id = None + self.modified_by = None + self.modified_time = None + self.name = None + self.policy_type = None + self.microtenant_id = None + self.microtenant_name = None + self.custom_policy_group_gids = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "creationTime": self.creation_time, + "defaultPolicyGroupGid": self.default_policy_group_gid, + "globalPolicyGroupGid": self.global_policy_group_gid, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "name": self.name, + "policyType": self.policy_type, + "microtenantId": self.microtenant_id, + "microtenantName": self.microtenant_name, + "customPolicyGroupGids": self.custom_policy_group_gids, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zpa/models/policy_group_set_summary.py b/zscaler/zpa/models/policy_group_set_summary.py new file mode 100644 index 00000000..989a2dfd --- /dev/null +++ b/zscaler/zpa/models/policy_group_set_summary.py @@ -0,0 +1,113 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject + + +class PolicyGroupSetSummary(ZscalerObject): + """ + A class for PolicyGroupSetSummary objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyGroupSetSummary model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.group_count_excluding_global = ( + config["groupCountExcludingGlobal"] if "groupCountExcludingGlobal" in config else None + ) + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.policy_type = config["policyType"] if "policyType" in config else None + self.policy_group_summary_list = ZscalerCollection.form_list( + config["policyGroupSummaryList"] if "policyGroupSummaryList" in config else [], PolicyGroupSummaryList + ) + else: + self.group_count_excluding_global = None + self.id = None + self.name = None + self.policy_type = None + self.policy_group_summary_list = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "groupCountExcludingGlobal": self.group_count_excluding_global, + "id": self.id, + "name": self.name, + "policyType": self.policy_type, + "policyGroupSummaryList": [item.request_format() for item in (self.policy_group_summary_list or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class PolicyGroupSummaryList(ZscalerObject): + """ + A class for PolicyGroupSummaryList objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyGroupSummaryList model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.group_criteria_count = config["groupCriteriaCount"] if "groupCriteriaCount" in config else None + self.group_order = config["groupOrder"] if "groupOrder" in config else None + self.id = config["id"] if "id" in config else None + self.name = config["name"] if "name" in config else None + self.rule_count = config["ruleCount"] if "ruleCount" in config else None + self.type = config["type"] if "type" in config else None + else: + self.group_criteria_count = None + self.group_order = None + self.id = None + self.name = None + self.rule_count = None + self.type = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "groupCriteriaCount": self.group_criteria_count, + "groupOrder": self.group_order, + "id": self.id, + "name": self.name, + "ruleCount": self.rule_count, + "type": self.type, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zpa/models/policy_group_set_summary_stat.py b/zscaler/zpa/models/policy_group_set_summary_stat.py new file mode 100644 index 00000000..914937cd --- /dev/null +++ b/zscaler/zpa/models/policy_group_set_summary_stat.py @@ -0,0 +1,59 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_object import ZscalerObject + + +class PolicyGroupSetSummaryStat(ZscalerObject): + """ + A class for PolicyGroupSetSummaryStat objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyGroupSetSummaryStat model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.disabled_rules = config["disabledRules"] if "disabledRules" in config else None + self.enabled_rules = config["enabledRules"] if "enabledRules" in config else None + self.total_policy_groups = config["totalPolicyGroups"] if "totalPolicyGroups" in config else None + self.total_rules = config["totalRules"] if "totalRules" in config else None + else: + self.disabled_rules = None + self.enabled_rules = None + self.total_policy_groups = None + self.total_rules = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "disabledRules": self.disabled_rules, + "enabledRules": self.enabled_rules, + "totalPolicyGroups": self.total_policy_groups, + "totalRules": self.total_rules, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zpa/models/policy_rule.py b/zscaler/zpa/models/policy_rule.py new file mode 100644 index 00000000..f412aef1 --- /dev/null +++ b/zscaler/zpa/models/policy_rule.py @@ -0,0 +1,548 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import Any, Dict, Optional + +from zscaler.oneapi_collection import ZscalerCollection +from zscaler.oneapi_object import ZscalerObject +from zscaler.zpa.models import app_connector_groups as app_connector_groups +from zscaler.zpa.models import common as common +from zscaler.zpa.models import policyset_controller_v2 as policyset_controller_v2 +from zscaler.zpa.models import server_group as server_group +from zscaler.zpa.models import service_edge_groups as service_edge_groups + + +class PolicyRule(ZscalerObject): + """ + A class for PolicyRule objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the PolicyRule model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.action = config["action"] if "action" in config else None + self.action_id = config["actionId"] if "actionId" in config else None + self.browser_posture_profile_id = ( + config["browserPostureProfileId"] if "browserPostureProfileId" in config else None + ) + self.browser_posture_profile_name = ( + config["browserPostureProfileName"] if "browserPostureProfileName" in config else None + ) + self.button_text = config["buttonText"] if "buttonText" in config else None + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.custom_msg = config["customMsg"] if "customMsg" in config else None + self.default_rule = config["defaultRule"] if "defaultRule" in config else None + self.default_rule_name = config["defaultRuleName"] if "defaultRuleName" in config else None + self.description = config["description"] if "description" in config else None + self.device_posture_failure_notification_enabled = ( + config["devicePostureFailureNotificationEnabled"] + if "devicePostureFailureNotificationEnabled" in config + else None + ) + self.disabled = config["disabled"] if "disabled" in config else None + self.extranet_enabled = config["extranetEnabled"] if "extranetEnabled" in config else None + self.group_id = config["groupId"] if "groupId" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.name = config["name"] if "name" in config else None + self.name_without_trim = config["nameWithoutTrim"] if "nameWithoutTrim" in config else None + self.operator = config["operator"] if "operator" in config else None + self.policy_group_name = config["policyGroupName"] if "policyGroupName" in config else None + self.policy_set_id = config["policySetId"] if "policySetId" in config else None + self.policy_type = config["policyType"] if "policyType" in config else None + self.post_actions = config["postActions"] if "postActions" in config else None + self.priority = config["priority"] if "priority" in config else None + self.read_only = config["readOnly"] if "readOnly" in config else None + self.reauth_idle_timeout = config["reauthIdleTimeout"] if "reauthIdleTimeout" in config else None + self.reauth_timeout = config["reauthTimeout"] if "reauthTimeout" in config else None + self.restriction_type = config["restrictionType"] if "restrictionType" in config else None + self.rule_order = config["ruleOrder"] if "ruleOrder" in config else None + self.rule_type = config["ruleType"] if "ruleType" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.microtenant_name = config["microtenantName"] if "microtenantName" in config else None + self.url = config["url"] if "url" in config else None + self.zpn_isolation_profile_id = config["zpnIsolationProfileId"] if "zpnIsolationProfileId" in config else None + self.zpn_inspection_profile_id = config["zpnInspectionProfileId"] if "zpnInspectionProfileId" in config else None + self.zpn_inspection_profile_name = ( + config["zpnInspectionProfileName"] if "zpnInspectionProfileName" in config else None + ) + self.zscaler_managed = config["zscalerManaged"] if "zscalerManaged" in config else None + self.app_server_groups = ZscalerCollection.form_list( + config["appServerGroups"] if "appServerGroups" in config else [], server_group.ServerGroup + ) + self.app_connector_groups = ZscalerCollection.form_list( + config["appConnectorGroups"] if "appConnectorGroups" in config else [], app_connector_groups.AppConnectorGroup + ) + self.conditions = ZscalerCollection.form_list(config["conditions"] if "conditions" in config else [], Condition) + self.desktop_policy_mappings = ZscalerCollection.form_list( + config["desktopPolicyMappings"] if "desktopPolicyMappings" in config else [], common.DesktopPolicyMappingsDTO + ) + self.post_action_types = ZscalerCollection.form_list( + config["postActionTypes"] if "postActionTypes" in config else [], str + ) + self.service_edge_groups = ZscalerCollection.form_list( + config["serviceEdgeGroups"] if "serviceEdgeGroups" in config else [], service_edge_groups.ServiceEdgeGroup + ) + + if "credential" in config: + if isinstance(config["credential"], policyset_controller_v2.Credential): + self.credential = config["credential"] + elif config["credential"] is not None: + self.credential = policyset_controller_v2.Credential(config["credential"]) + else: + self.credential = None + else: + self.credential = None + + if "credentialPool" in config: + if isinstance(config["credentialPool"], policyset_controller_v2.Credential): + self.credential_pool = config["credentialPool"] + elif config["credentialPool"] is not None: + self.credential_pool = policyset_controller_v2.Credential(config["credentialPool"]) + else: + self.credential_pool = None + else: + self.credential_pool = None + + if "extranetDTO" in config: + if isinstance(config["extranetDTO"], common.ExtranetDTO): + self.extranet_dto = config["extranetDTO"] + elif config["extranetDTO"] is not None: + self.extranet_dto = common.ExtranetDTO(config["extranetDTO"]) + else: + self.extranet_dto = None + else: + self.extranet_dto = None + + if "inconsistentConfigDetails" in config: + if isinstance(config["inconsistentConfigDetails"], InconsistentConfigDetails): + self.inconsistent_config_details = config["inconsistentConfigDetails"] + elif config["inconsistentConfigDetails"] is not None: + self.inconsistent_config_details = InconsistentConfigDetails(config["inconsistentConfigDetails"]) + else: + self.inconsistent_config_details = None + else: + self.inconsistent_config_details = None + + if "privilegedCapabilities" in config: + if isinstance(config["privilegedCapabilities"], common.PrivilegedCapabilitiesResource): + self.privileged_capabilities = config["privilegedCapabilities"] + elif config["privilegedCapabilities"] is not None: + self.privileged_capabilities = common.PrivilegedCapabilitiesResource(config["privilegedCapabilities"]) + else: + self.privileged_capabilities = None + else: + self.privileged_capabilities = None + + if "privilegedPortalCapabilities" in config: + if isinstance(config["privilegedPortalCapabilities"], common.PrivilegedCapabilitiesResource): + self.privileged_portal_capabilities = config["privilegedPortalCapabilities"] + elif config["privilegedPortalCapabilities"] is not None: + self.privileged_portal_capabilities = common.PrivilegedCapabilitiesResource( + config["privilegedPortalCapabilities"] + ) + else: + self.privileged_portal_capabilities = None + else: + self.privileged_portal_capabilities = None + else: + self.action = None + self.action_id = None + self.browser_posture_profile_id = None + self.browser_posture_profile_name = None + self.button_text = None + self.creation_time = None + self.custom_msg = None + self.default_rule = None + self.default_rule_name = None + self.description = None + self.device_posture_failure_notification_enabled = None + self.disabled = None + self.extranet_enabled = None + self.group_id = None + self.id = None + self.modified_by = None + self.modified_time = None + self.name = None + self.name_without_trim = None + self.operator = None + self.policy_group_name = None + self.policy_set_id = None + self.policy_type = None + self.post_actions = None + self.priority = None + self.read_only = None + self.reauth_idle_timeout = None + self.reauth_timeout = None + self.restriction_type = None + self.rule_order = None + self.rule_type = None + self.microtenant_id = None + self.microtenant_name = None + self.url = None + self.zpn_isolation_profile_id = None + self.zpn_inspection_profile_id = None + self.zpn_inspection_profile_name = None + self.zscaler_managed = None + self.app_server_groups = [] + self.app_connector_groups = [] + self.conditions = [] + self.desktop_policy_mappings = [] + self.post_action_types = [] + self.service_edge_groups = [] + self.credential = None + self.credential_pool = None + self.extranet_dto = None + self.inconsistent_config_details = None + self.privileged_capabilities = None + self.privileged_portal_capabilities = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "action": self.action, + "actionId": self.action_id, + "browserPostureProfileId": self.browser_posture_profile_id, + "browserPostureProfileName": self.browser_posture_profile_name, + "buttonText": self.button_text, + "creationTime": self.creation_time, + "customMsg": self.custom_msg, + "defaultRule": self.default_rule, + "defaultRuleName": self.default_rule_name, + "description": self.description, + "devicePostureFailureNotificationEnabled": self.device_posture_failure_notification_enabled, + "disabled": self.disabled, + "extranetEnabled": self.extranet_enabled, + "groupId": self.group_id, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "name": self.name, + "nameWithoutTrim": self.name_without_trim, + "operator": self.operator, + "policyGroupName": self.policy_group_name, + "policySetId": self.policy_set_id, + "policyType": self.policy_type, + "postActions": self.post_actions, + "priority": self.priority, + "readOnly": self.read_only, + "reauthIdleTimeout": self.reauth_idle_timeout, + "reauthTimeout": self.reauth_timeout, + "restrictionType": self.restriction_type, + "ruleOrder": self.rule_order, + "ruleType": self.rule_type, + "microtenantId": self.microtenant_id, + "microtenantName": self.microtenant_name, + "url": self.url, + "zpnIsolationProfileId": self.zpn_isolation_profile_id, + "zpnInspectionProfileId": self.zpn_inspection_profile_id, + "zpnInspectionProfileName": self.zpn_inspection_profile_name, + "zscalerManaged": self.zscaler_managed, + "appServerGroups": [item.request_format() for item in (self.app_server_groups or [])], + "appConnectorGroups": [item.request_format() for item in (self.app_connector_groups or [])], + "conditions": [item.request_format() for item in (self.conditions or [])], + "desktopPolicyMappings": [item.request_format() for item in (self.desktop_policy_mappings or [])], + "postActionTypes": self.post_action_types, + "serviceEdgeGroups": [item.request_format() for item in (self.service_edge_groups or [])], + "credential": self.credential, + "credentialPool": self.credential_pool, + "extranetDTO": self.extranet_dto, + "inconsistentConfigDetails": self.inconsistent_config_details, + "privilegedCapabilities": self.privileged_capabilities, + "privilegedPortalCapabilities": self.privileged_portal_capabilities, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Condition(ZscalerObject): + """ + A class for Condition objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Condition model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.id = config["id"] if "id" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.negated = config["negated"] if "negated" in config else None + self.operator = config["operator"] if "operator" in config else None + self.policy_set_type = config["policySetType"] if "policySetType" in config else None + self.rule_gid = config["ruleGid"] if "ruleGid" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + self.operands = ZscalerCollection.form_list(config["operands"] if "operands" in config else [], Operand) + else: + self.creation_time = None + self.id = None + self.modified_by = None + self.modified_time = None + self.negated = None + self.operator = None + self.policy_set_type = None + self.rule_gid = None + self.microtenant_id = None + self.operands = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "creationTime": self.creation_time, + "id": self.id, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "negated": self.negated, + "operator": self.operator, + "policySetType": self.policy_set_type, + "ruleGid": self.rule_gid, + "microtenantId": self.microtenant_id, + "operands": [item.request_format() for item in (self.operands or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Operand(ZscalerObject): + """ + A class for Operand objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Operand model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.condition_id = config["conditionId"] if "conditionId" in config else None + self.creation_time = config["creationTime"] if "creationTime" in config else None + self.id = config["id"] if "id" in config else None + self.idp_id = config["idpId"] if "idpId" in config else None + self.idp_name = config["idpName"] if "idpName" in config else None + self.lhs = config["lhs"] if "lhs" in config else None + self.modified_by = config["modifiedBy"] if "modifiedBy" in config else None + self.modified_time = config["modifiedTime"] if "modifiedTime" in config else None + self.name = config["name"] if "name" in config else None + self.object_type = config["objectType"] if "objectType" in config else None + self.policy_set_type = config["policySetType"] if "policySetType" in config else None + self.referenced_object_deleted = config["referencedObjectDeleted"] if "referencedObjectDeleted" in config else None + self.rhs = config["rhs"] if "rhs" in config else None + self.microtenant_id = config["microtenantId"] if "microtenantId" in config else None + else: + self.condition_id = None + self.creation_time = None + self.id = None + self.idp_id = None + self.idp_name = None + self.lhs = None + self.modified_by = None + self.modified_time = None + self.name = None + self.object_type = None + self.policy_set_type = None + self.referenced_object_deleted = None + self.rhs = None + self.microtenant_id = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "conditionId": self.condition_id, + "creationTime": self.creation_time, + "id": self.id, + "idpId": self.idp_id, + "idpName": self.idp_name, + "lhs": self.lhs, + "modifiedBy": self.modified_by, + "modifiedTime": self.modified_time, + "name": self.name, + "objectType": self.object_type, + "policySetType": self.policy_set_type, + "referencedObjectDeleted": self.referenced_object_deleted, + "rhs": self.rhs, + "microtenantId": self.microtenant_id, + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class InconsistentConfigDetails(ZscalerObject): + """ + A class for InconsistentConfigDetails objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the InconsistentConfigDetails model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.application = ZscalerCollection.form_list( + config["application"] if "application" in config else [], Application + ) + self.segment_group = ZscalerCollection.form_list( + config["segmentGroup"] if "segmentGroup" in config else [], Application + ) + self.app_connector_group = ZscalerCollection.form_list( + config["appConnectorGroup"] if "appConnectorGroup" in config else [], Application + ) + self.ba_certificate = ZscalerCollection.form_list( + config["baCertificate"] if "baCertificate" in config else [], Application + ) + self.branch_connector_group = ZscalerCollection.form_list( + config["branchConnectorGroup"] if "branchConnectorGroup" in config else [], Application + ) + self.cloud_connector_group = ZscalerCollection.form_list( + config["cloudConnectorGroup"] if "cloudConnectorGroup" in config else [], Application + ) + self.idp = ZscalerCollection.form_list(config["idp"] if "idp" in config else [], Application) + self.location = ZscalerCollection.form_list(config["location"] if "location" in config else [], Application) + self.machine_group = ZscalerCollection.form_list( + config["machineGroup"] if "machineGroup" in config else [], Application + ) + self.posture_profile = ZscalerCollection.form_list( + config["postureProfile"] if "postureProfile" in config else [], Application + ) + self.saml_attributes = ZscalerCollection.form_list( + config["samlAttributes"] if "samlAttributes" in config else [], Application + ) + self.scim_attributes = ZscalerCollection.form_list( + config["scimAttributes"] if "scimAttributes" in config else [], Application + ) + self.server_group = ZscalerCollection.form_list( + config["serverGroup"] if "serverGroup" in config else [], Application + ) + self.sra_application = ZscalerCollection.form_list( + config["sraApplication"] if "sraApplication" in config else [], Application + ) + self.trusted_network = ZscalerCollection.form_list( + config["trustedNetwork"] if "trustedNetwork" in config else [], Application + ) + self.user_portal = ZscalerCollection.form_list(config["userPortal"] if "userPortal" in config else [], Application) + self.workload_tag_group = ZscalerCollection.form_list( + config["workloadTagGroup"] if "workloadTagGroup" in config else [], Application + ) + else: + self.application = [] + self.segment_group = [] + self.app_connector_group = [] + self.ba_certificate = [] + self.branch_connector_group = [] + self.cloud_connector_group = [] + self.idp = [] + self.location = [] + self.machine_group = [] + self.posture_profile = [] + self.saml_attributes = [] + self.scim_attributes = [] + self.server_group = [] + self.sra_application = [] + self.trusted_network = [] + self.user_portal = [] + self.workload_tag_group = [] + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "application": [item.request_format() for item in (self.application or [])], + "segmentGroup": [item.request_format() for item in (self.segment_group or [])], + "appConnectorGroup": [item.request_format() for item in (self.app_connector_group or [])], + "baCertificate": [item.request_format() for item in (self.ba_certificate or [])], + "branchConnectorGroup": [item.request_format() for item in (self.branch_connector_group or [])], + "cloudConnectorGroup": [item.request_format() for item in (self.cloud_connector_group or [])], + "idp": [item.request_format() for item in (self.idp or [])], + "location": [item.request_format() for item in (self.location or [])], + "machineGroup": [item.request_format() for item in (self.machine_group or [])], + "postureProfile": [item.request_format() for item in (self.posture_profile or [])], + "samlAttributes": [item.request_format() for item in (self.saml_attributes or [])], + "scimAttributes": [item.request_format() for item in (self.scim_attributes or [])], + "serverGroup": [item.request_format() for item in (self.server_group or [])], + "sraApplication": [item.request_format() for item in (self.sra_application or [])], + "trustedNetwork": [item.request_format() for item in (self.trusted_network or [])], + "userPortal": [item.request_format() for item in (self.user_portal or [])], + "workloadTagGroup": [item.request_format() for item in (self.workload_tag_group or [])], + } + parent_req_format.update(current_obj_format) + return parent_req_format + + +class Application(ZscalerObject): + """ + A class for Application objects. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: + """ + Initialize the Application model based on API response. + + Args: + config (dict): A dictionary representing the configuration. + """ + super().__init__(config) + + if config: + self.name = config["name"] if "name" in config else None + self.reason = config["reason"] if "reason" in config else None + else: + self.name = None + self.reason = None + + def request_format(self) -> Dict[str, Any]: + """ + Return the object as a dictionary in the format expected for API requests. + """ + parent_req_format = super().request_format() + current_obj_format = { + "name": self.name, + "reason": self.reason, + } + parent_req_format.update(current_obj_format) + return parent_req_format diff --git a/zscaler/zpa/models/policyset_controller_v2.py b/zscaler/zpa/models/policyset_controller_v2.py index 85d20b0a..16689e76 100644 --- a/zscaler/zpa/models/policyset_controller_v2.py +++ b/zscaler/zpa/models/policyset_controller_v2.py @@ -50,6 +50,12 @@ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: self.reauth_idle_timeout = config["reauthIdleTimeout"] if "reauthIdleTimeout" in config else None self.reauth_timeout = config["reauthTimeout"] if "reauthTimeout" in config else None self.custom_msg = config["customMsg"] if "customMsg" in config else None + self.button_text = config["buttonText"] if "buttonText" in config else None + self.url = config["url"] if "url" in config else None + self.browser_posture_name = config["browserPostureName"] if "browserPostureName" in config else None + self.browser_posture_profile_id = ( + config["browserPostureProfileId"] if "browserPostureProfileId" in config else None + ) self.device_posture_failure_notification_enabled = ( config["devicePostureFailureNotificationEnabled"] if "devicePostureFailureNotificationEnabled" in config @@ -184,6 +190,10 @@ def __init__(self, config: Optional[Dict[str, Any]] = None) -> None: self.read_only = None self.zscaler_managed = None self.device_posture_failure_notification_enabled = None + self.button_text = None + self.url = None + self.browser_posture_name = None + self.browser_posture_profile_id = None self.desktop_policy_mappings = [] def request_format(self) -> Dict[str, Any]: @@ -223,6 +233,10 @@ def request_format(self) -> Dict[str, Any]: "restrictionType": self.restriction_type, "readOnly": self.read_only, "zscalerManaged": self.zscaler_managed, + "buttonText": self.button_text, + "url": self.url, + "browserPostureName": self.browser_posture_name, + "browserPostureProfileId": self.browser_posture_profile_id, "conditions": [condition.request_format() for condition in self.conditions], "appConnectorGroups": [group.request_format() for group in self.app_connector_groups], "appServerGroups": [group.request_format() for group in self.app_server_groups], diff --git a/zscaler/zpa/policy_group.py b/zscaler/zpa/policy_group.py new file mode 100644 index 00000000..8c1efaa2 --- /dev/null +++ b/zscaler/zpa/policy_group.py @@ -0,0 +1,399 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zpa.models.common import CommonFilterSearch +from zscaler.zpa.models.policy_group import PolicyGroup + + +class PolicyGroupAPI(APIClient): + """ + A Client object for the Policy Group resource. + """ + + def __init__(self, request_executor, config): + super().__init__() + self._request_executor: RequestExecutor = request_executor + customer_id = config["client"].get("customerId") + self._zpa_base_endpoint = f"/zpa/mgmtconfig/v1/admin/customers/{customer_id}" + + def add_group(self, group_set_id: str, **kwargs) -> APIResult[PolicyGroup]: + """ + Add a new Policy Group to a Policy Group Set. + + Args: + group_set_id (str): The group set id. + name (str): The name of the policy group. + **kwargs: Optional keyword args. + + Keyword Args: + description (str): Additional information about the policy group. + group_criteria_rule_gid (int): The group criteria rule gid for this policy group. + group_order (int): The group order for this policy group. + policy_group_set_gid (int): The policy group set gid for this policy group. + microtenant_name (str): The microtenant name for this policy group. + type (str): The type for this policy group. Accepted values include e.g. ``GLOBAL``. + group_criteria_rule (dict): The ID of the group criteria rule for this policy group, e.g. ``{'id': 12345}``. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. + + Returns: + tuple: A tuple containing the newly added PolicyGroup instance, response, and error. + + Examples: + Add a new policy group: + + >>> added_group, _, error = client.zpa.policy_group.add_group( + ... 'VALUE', + ... name=f"NewGroup_{random.randint(1000, 10000)}", + ... description=f"NewGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error adding policy group: {error}") + ... return + ... print(f"Policy group added successfully: {added_group.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group + """) + + body = kwargs + + microtenant_id = body.get("microtenant_id", None) + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, body=body, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroup) + if error: + return (None, response, error) + + try: + result = PolicyGroup(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_groups(self, group_set_id: str, query_params: Optional[dict] = None) -> APIResult[List[PolicyGroup]]: + """ + Get All Policy Groups within a Policy Group Set. + + Args: + group_set_id (str): The group set id. + query_params {dict}: Map of query parameters for the request. + ``[query_params.page]`` {str}: Specifies the page number. + ``[query_params.page_size]`` {int}: Page size for pagination. + ``[query_params.search]`` {str}: Search string for filtering results. + ``[query_params.microtenant_id]`` {str}: ID of the microtenant, if applicable. + + Returns: + tuple: A tuple containing (list of PolicyGroup instances, Response, error) + + Examples: + List policy groups: + + >>> group_list, _, error = client.zpa.policy_group.list_groups('VALUE') + >>> if error: + ... print(f"Error listing policy groups: {error}") + ... return + ... print(f"Total policy groups found: {len(group_list)}") + ... for group in group_list: + ... print(group.as_dict()) + + List policy groups using filters: + + >>> group_list, _, error = client.zpa.policy_group.list_groups( + ... 'VALUE', query_params={'page': 'VALUE'}) + >>> if error: + ... print(f"Error listing policy groups: {error}") + ... return + ... print(f"Total policy groups found: {len(group_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/all + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroup) + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(PolicyGroup(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def search_groups(self, group_set_id: str, **kwargs) -> APIResult[CommonFilterSearch]: + """ + Get All Policy Groups within a Policy Group Set with advanced search and pagination. + + Args: + group_set_id (str): The group set id. + **kwargs: The advanced filter/page/sort payload, e.g. ``filter_and_sort_dto`` with ``filter_by`` / ``page_by`` / ``sort_by``. + + Returns: + tuple: A tuple containing the CommonFilterSearch instance (filter results, paging, sorting), response, and error. + + Examples: + >>> result, _, error = client.zpa.policy_group.search_groups( + ... filter_and_sort_dto={ + ... "filter_by": [{"filter_name": "name", "operator": "LIKE", "values": ["Test"]}], + ... "page_by": {"page": 1, "page_size": 20}, + ... }, + ... ) + >>> if error: + ... print(f"Error searching policy groups: {error}") + ... return + ... print(result.as_dict()) + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/search + """) + + body = kwargs + + request, error = self._request_executor.create_request(http_method, api_url, body=body) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, CommonFilterSearch) + if error: + return (None, response, error) + + try: + result = CommonFilterSearch(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_group(self, group_set_id: str, group_id: str, microtenant_id: str = None) -> APIResult[None]: + """ + Delete a Policy Group. + + Args: + group_set_id (str): The group set id. + group_id (str): The unique identifier for the policy group. + microtenant_id (str, optional): The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a policy group: + + >>> _, _, error = client.zpa.policy_group.delete_group('VALUE', '216196257331370181') + >>> if error: + ... print(f"Error deleting policy group: {error}") + ... return + ... print(f"Policy group deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id} + """) + + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def get_group(self, group_set_id: str, group_id: str, query_params: Optional[dict] = None) -> APIResult[PolicyGroup]: + """ + Get a specific Policy Group by ID within a Policy Group Set. + + Args: + group_set_id (str): The group set id. + group_id (str): The unique identifier for the policy group. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyGroup instance, Response, error). + + Examples: + Print a specific policy group: + + >>> fetched_group, _, error = client.zpa.policy_group.get_group('VALUE', '216196257331370181') + >>> if error: + ... print(f"Error fetching policy group by ID: {error}") + ... return + ... print(f"Fetched policy group by ID: {fetched_group.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id} + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroup) + if error: + return (None, response, error) + + try: + result = PolicyGroup(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def update_group(self, group_set_id: str, group_id: str, **kwargs) -> APIResult[PolicyGroup]: + """ + Update an existing Policy Group. + + Args: + group_set_id (str): The group set id. + group_id (str): The unique identifier for the policy group. + + Keyword Args: + name (str): The name of the policy group. + description (str): Additional information about the policy group. + group_criteria_rule_gid (int): The group criteria rule gid for this policy group. + group_order (int): The group order for this policy group. + policy_group_set_gid (int): The policy group set gid for this policy group. + microtenant_name (str): The microtenant name for this policy group. + type (str): The type for this policy group. Accepted values include e.g. ``GLOBAL``. + group_criteria_rule (dict): The ID of the group criteria rule for this policy group, e.g. ``{'id': 12345}``. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. + + Returns: + tuple: A tuple containing the updated PolicyGroup instance, response, and error. + + Examples: + Update an existing policy group: + + >>> updated_group, _, error = client.zpa.policy_group.update_group( + ... 'VALUE', + ... group_id='216196257331370181', + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... ) + >>> if error: + ... print(f"Error updating policy group: {error}") + ... return + ... print(f"Policy group updated successfully: {updated_group.as_dict()}") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id} + """) + + body = {} + + body.update(kwargs) + + microtenant_id = body.get("microtenant_id", None) + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, body, {}, params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroup) + if error: + return (None, response, error) + + # Handle 204 No Content - response exists but body is empty + if response is None or not response.get_body(): + return (PolicyGroup({"id": group_id}), response, None) + + try: + result = PolicyGroup(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def reorder_group(self, group_set_id: str, group_id: str, new_order: str, microtenant_id: str = None) -> APIResult[None]: + """ + Update an existing Policy Group Order. + + Args: + group_set_id (str): The group set id. + group_id (str): The unique identifier for the policy group. + new_order (str): The new order position for the policy group. + microtenant_id (str, optional): The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + >>> _, _, error = client.zpa.policy_group.reorder_group('VALUE', '216196257331370181', '2') + >>> if error: + ... print(f"Error reordering policy group: {error}") + ... return + ... print(f"Policy group reordered successfully.") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/reorder/{new_order} + """) + + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, body={}, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zpa/policy_group_rule.py b/zscaler/zpa/policy_group_rule.py new file mode 100644 index 00000000..d860bbdd --- /dev/null +++ b/zscaler/zpa/policy_group_rule.py @@ -0,0 +1,332 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zpa.models.policy_rule import PolicyRule + + +class PolicyGroupRuleAPI(APIClient): + """ + A Client object for the Policy Group Rule resource. + """ + + def __init__(self, request_executor, config): + super().__init__() + self._request_executor: RequestExecutor = request_executor + customer_id = config["client"].get("customerId") + self._zpa_base_endpoint = f"/zpa/mgmtconfig/v1/admin/customers/{customer_id}" + + def list_rules(self, group_set_id: str, group_id: str, query_params: Optional[dict] = None) -> APIResult[List[PolicyRule]]: + """ + Get All Policy Groups Rules within a Policy Group with advanced search and pagination. + + Args: + group_set_id (str): The group set id. + group_id (str): The group id. + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {string}: The search string used to support search by features and fields for the API. + ``[query_params.page]`` {integer}: Specifies the page number. + ``[query_params.pagesize]`` {integer}: Specifies the page size. If not provided, the default page size is 20. The max page size is 500. + + Returns: + tuple: A tuple containing (list of PolicyRule instances, Response, error) + + Examples: + List policy group rules: + + >>> rule_list, _, error = client.zpa.policy_group_rule.list_rules('VALUE', 'VALUE') + >>> if error: + ... print(f"Error listing policy group rules: {error}") + ... return + ... print(f"Total policy group rules found: {len(rule_list)}") + ... for rule in rule_list: + ... print(rule.as_dict()) + + List policy group rules using filters: + + >>> rule_list, _, error = client.zpa.policy_group_rule.list_rules( + ... 'VALUE', 'VALUE', query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing policy group rules: {error}") + ... return + ... print(f"Total policy group rules found: {len(rule_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/rule + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyRule) + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(PolicyRule(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def add_rule(self, group_set_id: str, group_id: str, **kwargs) -> APIResult[PolicyRule]: + """ + Add a new policy rule for a given policy group. + + Args: + group_set_id (str): The group set id. + group_id (str): The group id. + name (str): The name of the policy group rule. + **kwargs: Optional keyword args. + + Keyword Args: + action (str): The action taken when traffic matches the policy group rule criteria. + action_id (int): The action id for this policy group rule. + browser_posture_profile_id (str): The browser posture profile id for this policy group rule. + browser_posture_profile_name (str): The browser posture profile name for this policy group rule. + button_text (str): The button text for this policy group rule. + custom_msg (str): The custom msg for this policy group rule. + default_rule (bool): A Boolean value indicating whether default rule applies to this policy group rule. + default_rule_name (str): The default rule name for this policy group rule. + description (str): Additional information about the policy group rule. + device_posture_failure_notification_enabled (bool): A Boolean value indicating whether device posture failure notification is enabled for this policy group rule. + disabled (int): The disabled for this policy group rule. + extranet_enabled (bool): A Boolean value indicating whether extranet is enabled for this policy group rule. + group_id (int): The group id for this policy group rule. + name_without_trim (str): The name without trim for this policy group rule. + operator (str): The operator for this policy group rule. Accepted values include e.g. ``AND``. + policy_group_name (str): The policy group name for this policy group rule. + policy_set_id (int): The policy set id for this policy group rule. + policy_type (int): The policy type for this policy group rule. + post_actions (dict): The post actions configuration for this policy group rule. + priority (int): The priority for this policy group rule. + read_only (bool): A Boolean value indicating whether read only applies to this policy group rule. + reauth_idle_timeout (int): The reauth idle timeout for this policy group rule. + reauth_timeout (int): The reauth timeout for this policy group rule. + restriction_type (str): The restriction type for this policy group rule. + rule_order (int): The rule order for this policy group rule. + rule_type (str): The rule type for this policy group rule. Accepted values include e.g. ``STANDARD``. + microtenant_name (str): The microtenant name for this policy group rule. + url (str): The url for this policy group rule. + zpn_isolation_profile_id (int): The zpn isolation profile id for this policy group rule. + zpn_inspection_profile_id (int): The zpn inspection profile id for this policy group rule. + zpn_inspection_profile_name (str): The zpn inspection profile name for this policy group rule. + zscaler_managed (bool): A Boolean value indicating whether zscaler managed applies to this policy group rule. + app_server_groups (list): The IDs for the app server groups that this policy group rule applies to. + app_connector_groups (list): The IDs for the app connector groups that this policy group rule applies to. + conditions (list): The IDs for the conditions that this policy group rule applies to. + desktop_policy_mappings (list): The IDs for the desktop policy mappings that this policy group rule applies to. + post_action_types (list): The list of post action types for this policy group rule. + service_edge_groups (list): The IDs for the service edge groups that this policy group rule applies to. + credential (dict): The ID of the credential for this policy group rule, e.g. ``{'id': 12345}``. + credential_pool (dict): The ID of the credential pool for this policy group rule, e.g. ``{'id': 12345}``. + extranet_dto (dict): The ID of the extranet dto for this policy group rule, e.g. ``{'id': 12345}``. + inconsistent_config_details (dict): The inconsistent config details configuration for this policy group rule. + privileged_capabilities (dict): The privileged capabilities configuration for this policy group rule. + privileged_portal_capabilities (dict): The privileged portal capabilities configuration for this policy group rule. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. + + Returns: + tuple: A tuple containing the newly added PolicyRule instance, response, and error. + + Examples: + Add a new policy group rule: + + >>> added_rule, _, error = client.zpa.policy_group_rule.add_rule( + ... 'VALUE', 'VALUE', + ... name=f"NewRule_{random.randint(1000, 10000)}", + ... description=f"NewRule_{random.randint(1000, 10000)}", + ... action='ALLOW', + ... ) + >>> if error: + ... print(f"Error adding policy group rule: {error}") + ... return + ... print(f"Policy group rule added successfully: {added_rule.as_dict()}") + """ + http_method = "post".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/rule + """) + + body = kwargs + + microtenant_id = body.get("microtenant_id", None) + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, body=body, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyRule) + if error: + return (None, response, error) + + try: + result = PolicyRule(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def delete_rule(self, group_set_id: str, group_id: str, rule_id: str, microtenant_id: str = None) -> APIResult[None]: + """ + Delete a policy rule within a policy group + + Args: + group_set_id (str): The group set id. + group_id (str): The group id. + rule_id (str): The unique identifier for the policy group rule. + microtenant_id (str, optional): The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a policy group rule: + + >>> _, _, error = client.zpa.policy_group_rule.delete_rule('VALUE', 'VALUE', '216196257331370181') + >>> if error: + ... print(f"Error deleting policy group rule: {error}") + ... return + ... print(f"Policy group rule deleted successfully.") + """ + http_method = "delete".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/rule/{rule_id} + """) + + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) + + def get_rule( + self, group_set_id: str, group_id: str, rule_id: str, query_params: Optional[dict] = None + ) -> APIResult[PolicyRule]: + """ + Get a policy rule within a policy group + + Args: + group_set_id (str): The group set id. + group_id (str): The group id. + rule_id (str): The unique identifier for the policy group rule. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyRule instance, Response, error). + + Examples: + Print a specific policy group rule: + + >>> fetched_rule, _, error = client.zpa.policy_group_rule.get_rule('VALUE', 'VALUE', '216196257331370181') + >>> if error: + ... print(f"Error fetching policy group rule by ID: {error}") + ... return + ... print(f"Fetched policy group rule by ID: {fetched_rule.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/rule/{rule_id} + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyRule) + if error: + return (None, response, error) + + try: + result = PolicyRule(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def reorder_rule( + self, group_set_id: str, group_id: str, rule_id: str, new_order: str, microtenant_id: str = None + ) -> APIResult[None]: + """ + Update rule order of a rule within policy group + + Args: + group_set_id (str): The group set id. + group_id (str): The group id. + rule_id (str): The unique identifier for the policy group rule. + new_order (str): The new order position for the policy group rule. + microtenant_id (str, optional): The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing the response object and error (if any). + + Examples: + >>> _, _, error = client.zpa.policy_group_rule.reorder_rule('VALUE', 'VALUE', '216196257331370181', '2') + >>> if error: + ... print(f"Error reordering policy group rule: {error}") + ... return + ... print(f"Policy group rule reordered successfully.") + """ + http_method = "put".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id}/group/{group_id}/rule/{rule_id}/reorder/{new_order} + """) + + params = {"microtenantId": microtenant_id} if microtenant_id else {} + + request, error = self._request_executor.create_request(http_method, api_url, body={}, params=params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request) + if error: + return (None, response, error) + return (None, response, None) diff --git a/zscaler/zpa/policy_group_set.py b/zscaler/zpa/policy_group_set.py new file mode 100644 index 00000000..3b606e3e --- /dev/null +++ b/zscaler/zpa/policy_group_set.py @@ -0,0 +1,359 @@ +""" +Copyright (c) 2023, Zscaler Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + +from typing import List, Optional + +from zscaler.api_client import APIClient +from zscaler.request_executor import RequestExecutor +from zscaler.types import APIResult +from zscaler.utils import format_url +from zscaler.zpa.models.policy_group_set import PolicyGroupSet +from zscaler.zpa.models.policy_group_set_summary import PolicyGroupSetSummary +from zscaler.zpa.models.policy_group_set_summary_stat import PolicyGroupSetSummaryStat +from zscaler.zpa.models.policy_rule import PolicyRule + + +class PolicyGroupSetAPI(APIClient): + """ + A Client object for the Policy Group Set resource. + """ + + def __init__(self, request_executor, config): + super().__init__() + self._request_executor: RequestExecutor = request_executor + customer_id = config["client"].get("customerId") + self._zpa_base_endpoint = f"/zpa/mgmtconfig/v1/admin/customers/{customer_id}" + + def list_sets(self, query_params: Optional[dict] = None) -> APIResult[List[PolicyGroupSetSummary]]: + """ + Get all Policy Group Sets for a customer. + + Args: + query_params {dict}: Map of query parameters for the request. + ``[query_params.create_if_not_exist]`` {boolean}: create resource if missing + + Returns: + tuple: A tuple containing (list of PolicyGroupSetSummary instances, Response, error) + + Examples: + List policy group sets: + + >>> set_list, _, error = client.zpa.policy_group_set.list_sets() + >>> if error: + ... print(f"Error listing policy group sets: {error}") + ... return + ... print(f"Total policy group sets found: {len(set_list)}") + ... for set in set_list: + ... print(set.as_dict()) + + List policy group sets using filters: + + >>> set_list, _, error = client.zpa.policy_group_set.list_sets( + ... query_params={'create_if_not_exist': 'VALUE'}) + >>> if error: + ... print(f"Error listing policy group sets: {error}") + ... return + ... print(f"Total policy group sets found: {len(set_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroupSetSummary) + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(PolicyGroupSetSummary(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_set_by_policy_type( + self, policy_type: str, query_params: Optional[dict] = None + ) -> APIResult[PolicyGroupSetSummary]: + """ + Get Policy Group Set fo a customer for policy type. + + Args: + policy_type (str): The policy type. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyGroupSetSummary instance, Response, error). + + Examples: + Print a specific policy group set: + + >>> fetched_set, _, error = client.zpa.policy_group_set.get_set_by_policy_type('VALUE') + >>> if error: + ... print(f"Error fetching policy group set: {error}") + ... return + ... print(f"Fetched policy group set: {fetched_set.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/policyType/{policy_type} + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroupSetSummary) + if error: + return (None, response, error) + + try: + result = PolicyGroupSetSummary(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def list_rules(self, policy_type: str, query_params: Optional[dict] = None) -> APIResult[List[PolicyRule]]: + """ + Get paginated rules across groups within a Policy Group Set. + + Args: + policy_type (str): The policy type. + query_params {dict}: Map of query parameters for the request. + ``[query_params.search]`` {string}: The search string used to support search by features and fields for the API. + ``[query_params.page]`` {integer}: Specifies the page number. + ``[query_params.pagesize]`` {integer}: Specifies the page size. If not provided, the default page size is 20. The max page size is 500. + + Returns: + tuple: A tuple containing (list of PolicyRule instances, Response, error) + + Examples: + List policy group sets: + + >>> set_list, _, error = client.zpa.policy_group_set.list_rules('VALUE') + >>> if error: + ... print(f"Error listing policy group sets: {error}") + ... return + ... print(f"Total policy group sets found: {len(set_list)}") + ... for set in set_list: + ... print(set.as_dict()) + + List policy group sets using filters: + + >>> set_list, _, error = client.zpa.policy_group_set.list_rules( + ... 'VALUE', query_params={'search': 'Example'}) + >>> if error: + ... print(f"Error listing policy group sets: {error}") + ... return + ... print(f"Total policy group sets found: {len(set_list)}") + + Client-side filtering with JMESPath: + + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/policyType/{policy_type}/rules + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyRule) + if error: + return (None, response, error) + + try: + result = [] + for item in response.get_results(): + result.append(PolicyRule(self.form_response_body(item))) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_set_summary(self, policy_type: str, query_params: Optional[dict] = None) -> APIResult[PolicyGroupSetSummary]: + """ + Get Policy Group Set Summary fo a customer for policy type. + + Args: + policy_type (str): The policy type. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyGroupSetSummary instance, Response, error). + + Examples: + Print a specific policy group set: + + >>> fetched_set, _, error = client.zpa.policy_group_set.get_set_summary('VALUE') + >>> if error: + ... print(f"Error fetching policy group set: {error}") + ... return + ... print(f"Fetched policy group set: {fetched_set.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/policyType/{policy_type}/summary + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroupSetSummary) + if error: + return (None, response, error) + + try: + result = PolicyGroupSetSummary(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_set_summary_stats( + self, policy_type: str, query_params: Optional[dict] = None + ) -> APIResult[PolicyGroupSetSummaryStat]: + """ + Get summary stats for groups and rules within a Policy Group Set. + + Args: + policy_type (str): The policy type. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyGroupSetSummaryStat instance, Response, error). + + Examples: + Print a specific policy group set: + + >>> fetched_set, _, error = client.zpa.policy_group_set.get_set_summary_stats('VALUE') + >>> if error: + ... print(f"Error fetching policy group set: {error}") + ... return + ... print(f"Fetched policy group set: {fetched_set.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/policyType/{policy_type}/summaryStats + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroupSetSummaryStat) + if error: + return (None, response, error) + + try: + result = PolicyGroupSetSummaryStat(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) + + def get_set(self, group_set_id: str, query_params: Optional[dict] = None) -> APIResult[PolicyGroupSet]: + """ + Get a specific Policy Group Set by ID. + + Args: + group_set_id (str): The unique identifier for the policy group set. + query_params (dict, optional): Map of query parameters for the request. + ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. + + Returns: + tuple: A tuple containing (PolicyGroupSet instance, Response, error). + + Examples: + Print a specific policy group set: + + >>> fetched_set, _, error = client.zpa.policy_group_set.get_set('216196257331370181') + >>> if error: + ... print(f"Error fetching policy group set by ID: {error}") + ... return + ... print(f"Fetched policy group set by ID: {fetched_set.as_dict()}") + """ + http_method = "get".upper() + api_url = format_url(f""" + {self._zpa_base_endpoint} + /policyGroupSet/{group_set_id} + """) + + query_params = query_params or {} + microtenant_id = query_params.get("microtenant_id", None) + if microtenant_id: + query_params["microtenantId"] = microtenant_id + + request, error = self._request_executor.create_request(http_method, api_url, params=query_params) + if error: + return (None, None, error) + + response, error = self._request_executor.execute(request, PolicyGroupSet) + if error: + return (None, response, error) + + try: + result = PolicyGroupSet(self.form_response_body(response.get_body())) + except Exception as error: + return (None, response, error) + return (result, response, None) diff --git a/zscaler/zpa/segment_groups.py b/zscaler/zpa/segment_groups.py index f3108f24..6a61a1a5 100644 --- a/zscaler/zpa/segment_groups.py +++ b/zscaler/zpa/segment_groups.py @@ -25,7 +25,7 @@ class SegmentGroupsAPI(APIClient): """ - A client object for the Segment Groups resource. + A Client object for the Segment Groups resource. """ def __init__(self, request_executor, config): @@ -37,9 +37,7 @@ def __init__(self, request_executor, config): def list_groups(self, query_params: Optional[dict] = None) -> APIResult[List[SegmentGroup]]: """ - Enumerates segment groups in your organization with pagination. - A subset of segment groups can be returned that match a supported - filter expression or query. + Lists the segment groups configured in your organization. Args: query_params {dict}: Map of query parameters for the request. @@ -49,33 +47,34 @@ def list_groups(self, query_params: Optional[dict] = None) -> APIResult[List[Seg ``[query_params.microtenant_id]`` {str}: ID of the microtenant, if applicable. Returns: - :obj:`Tuple`: A tuple containing (list of SegmentGroup instances, Response, error) + tuple: A tuple containing (list of SegmentGroup instances, Response, error) - Example: - Fetch all segment groups without filtering + Examples: + List segment groups: - >>> group_list, _, err = client.zpa.segment_groups.list_groups() - ... if err: - ... print(f"Error listing segment groups: {err}") + >>> group_list, _, error = client.zpa.segment_groups.list_groups() + >>> if error: + ... print(f"Error listing segment groups: {error}") ... return ... print(f"Total segment groups found: {len(group_list)}") ... for group in group_list: ... print(group.as_dict()) - Fetch segment groups with query_params filters - >>> group_list, _, err = client.zpa.segment_groups.list_groups( - ... query_params={'search': 'Group01', 'page': '1', 'page_size': '100'}) - ... if err: - ... print(f"Error listing segment groups: {err}") + List segment groups using filters: + + >>> group_list, _, error = client.zpa.segment_groups.list_groups( + ... query_params={'page': 'VALUE'}) + >>> if error: + ... print(f"Error listing segment groups: {error}") ... return ... print(f"Total segment groups found: {len(group_list)}") - ... for group in group_list: - ... print(group.as_dict()) - Use JMESPath to filter results client-side: + Client-side filtering with JMESPath: - >>> groups, resp, err = client.zpa.segment_groups.list_groups() - >>> enabled = resp.search("list[?enabled==`true`].{name: name, id: id}") + The response object supports client-side filtering and + projection via ``resp.search(expression)``. See the + `JMESPath documentation `_ for + expression syntax. """ http_method = "get".upper() api_url = format_url(f""" @@ -104,24 +103,24 @@ def list_groups(self, query_params: Optional[dict] = None) -> APIResult[List[Seg return (None, response, error) return (result, response, None) - def get_group(self, group_id: str, query_params: Optional[dict] = None) -> APIResult[dict]: + def get_group(self, group_id: str, query_params: Optional[dict] = None) -> APIResult[SegmentGroup]: """ - Gets information on the specified segment group. + Fetches a specific segment group by ID. Args: - group_id (str): The unique identifier of the segment group. + group_id (str): The unique identifier for the segment group. query_params (dict, optional): Map of query parameters for the request. ``[query_params.microtenant_id]`` {str}: The microtenant ID, if applicable. Returns: - :obj:`Tuple`: SegmentGroup: The corresponding segment group object. + tuple: A tuple containing (SegmentGroup instance, Response, error). - Example: - Retrieve details of a specific segment group + Examples: + Print a specific segment group: - >>> fetched_group, _, err = client.zpa.segment_groups.get_group('999999') - ... if err: - ... print(f"Error fetching segment group by ID: {err}") + >>> fetched_group, _, error = client.zpa.segment_groups.get_group('216196257331370181') + >>> if error: + ... print(f"Error fetching segment group by ID: {error}") ... return ... print(f"Fetched segment group by ID: {fetched_group.as_dict()}") """ @@ -150,33 +149,39 @@ def get_group(self, group_id: str, query_params: Optional[dict] = None) -> APIRe return (None, response, error) return (result, response, None) - def add_group(self, **kwargs) -> APIResult[dict]: + def add_group(self, **kwargs) -> APIResult[SegmentGroup]: """ - Adds a new segment group. + Creates a new segment group. Args: name (str): The name of the segment group. - description (str): The description of the segment group. - enabled (bool): Enable the segment group. Defaults to True. + **kwargs: Optional keyword args. + + Keyword Args: + description (str): Additional information about the segment group. + enabled (bool): Indicates whether the segment group is enabled. + policy_migrated (str): The policy migrated for this segment group. + config_space (str): The config space for this segment group. + tcp_keep_alive_enabled (str): The tcp keep alive enabled for this segment group. + microtenant_name (str): The microtenant name for this segment group. + skip_detailed_app_info (str): The skip detailed app info for this segment group. + applications (str): The applications for this segment group. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. Returns: - :obj:`Tuple`: SegmentGroup: The created segment group object. - - Example: - # Basic example: Add a new segment group - >>> added_group, _, err = client.zpa.segment_groups.add_group( - ... name="Example Group", - ... description="This is an example segment group.", - ... enabled=True - ... ) + tuple: A tuple containing the newly added SegmentGroup instance, response, and error. + + Examples: + Add a new segment group: - # Adding a new segment group for a specific microtenant - >>> added_group, _, err = zpa.segment_groups.add_group( - ... name="Example Group", - ... description="Segment group for microtenant", - ... enabled=True, - ... microtenant_id="216196257331380392" + >>> added_group, _, error = client.zpa.segment_groups.add_group( + ... name=f"NewGroup_{random.randint(1000, 10000)}", + ... description=f"NewGroup_{random.randint(1000, 10000)}", ... ) + >>> if error: + ... print(f"Error adding segment group: {error}") + ... return + ... print(f"Segment group added successfully: {added_group.as_dict()}") """ http_method = "post".upper() api_url = format_url(f""" @@ -203,35 +208,40 @@ def add_group(self, **kwargs) -> APIResult[dict]: return (None, response, error) return (result, response, None) - def update_group(self, group_id: str, **kwargs) -> APIResult[dict]: + def update_group(self, group_id: str, **kwargs) -> APIResult[SegmentGroup]: """ - Updates the specified segment group. + Updates information for the specified segment group. Args: - group_id (str): The unique identifier for the segment group being updated. + group_id (str): The unique identifier for the segment group. + + Keyword Args: + name (str): The name of the segment group. + description (str): Additional information about the segment group. + enabled (bool): Indicates whether the segment group is enabled. + policy_migrated (str): The policy migrated for this segment group. + config_space (str): The config space for this segment group. + tcp_keep_alive_enabled (str): The tcp keep alive enabled for this segment group. + microtenant_name (str): The microtenant name for this segment group. + skip_detailed_app_info (str): The skip detailed app info for this segment group. + applications (str): The applications for this segment group. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. Returns: - :obj:`Tuple`: SegmentGroup: The updated segment group object. - - Example: - # Basic example: Update an existing segment group - >>> group_id = "216196257331370181" - >>> updated_group, _, err = zpa.segment_groups.update_group( - ... group_id, - ... name="Updated Group Name", - ... description="Updated description for the segment group", - ... enabled=False - ... ) + tuple: A tuple containing the updated SegmentGroup instance, response, and error. + + Examples: + Update an existing segment group: - # Updating a segment group for a specific microtenant - >>> group_id = "216196257331370181" - >>> updated_group, _, err = zpa.segment_groups.update_group( - ... group_id, - ... name="Tenant-Specific Group Update", - ... description="Updated segment group for microtenant", - ... enabled=True, - ... microtenant_id="216196257331380392" + >>> updated_group, _, error = client.zpa.segment_groups.update_group( + ... group_id='216196257331370181', + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", ... ) + >>> if error: + ... print(f"Error updating segment group: {error}") + ... return + ... print(f"Segment group updated successfully: {updated_group.as_dict()}") """ http_method = "put".upper() api_url = format_url(f""" @@ -264,35 +274,40 @@ def update_group(self, group_id: str, **kwargs) -> APIResult[dict]: return (None, response, error) return (result, response, None) - def update_group_v2(self, group_id: str, **kwargs) -> APIResult[dict]: + def update_group_v2(self, group_id: str, **kwargs) -> APIResult[SegmentGroup]: """ - Updates the specified segment group. + Updates the specified segment group (v2 endpoint). Args: - group_id (str): The unique identifier for the segment group being updated. + group_id (str): The unique identifier for the segment group. + + Keyword Args: + name (str): The name of the segment group. + description (str): Additional information about the segment group. + enabled (bool): Indicates whether the segment group is enabled. + policy_migrated (str): The policy migrated for this segment group. + config_space (str): The config space for this segment group. + tcp_keep_alive_enabled (str): The tcp keep alive enabled for this segment group. + microtenant_name (str): The microtenant name for this segment group. + skip_detailed_app_info (str): The skip detailed app info for this segment group. + applications (str): The applications for this segment group. + microtenant_id (str): The unique identifier of the Microtenant for the ZPA tenant. Returns: - :obj:`Tuple`: SegmentGroup: The updated segment group object. - - Example: - # Basic example: Update an existing segment group - >>> group_id = "216196257331370181" - >>> updated_group, response, err = zpa.segment_groups.update_group_v2( - ... group_id, - ... name="Updated Group Name", - ... description="Updated description for the segment group", - ... enabled=False - ... ) + tuple: A tuple containing the updated SegmentGroup instance, response, and error. + + Examples: + Update an existing segment group: - # Updating a segment group for a specific microtenant - >>> group_id = "216196257331370181" - >>> updated_group, response, err = zpa.segment_groups.update_group_v2( - ... group_id, - ... name="Tenant-Specific Group Update", - ... description="Updated segment group for microtenant", - ... enabled=True, - ... microtenant_id="216196257331380392" + >>> updated_group, _, error = client.zpa.segment_groups.update_group_v2( + ... group_id='216196257331370181', + ... name=f"UpdatedGroup_{random.randint(1000, 10000)}", + ... description=f"UpdatedGroup_{random.randint(1000, 10000)}", ... ) + >>> if error: + ... print(f"Error updating segment group: {error}") + ... return + ... print(f"Segment group updated successfully: {updated_group.as_dict()}") """ http_method = "put".upper() api_url = format_url(f""" @@ -330,18 +345,20 @@ def delete_group(self, group_id: str, microtenant_id: str = None) -> APIResult[N Deletes the specified segment group. Args: - group_id (str): The unique identifier for the segment group to be deleted. + group_id (str): The unique identifier for the segment group. + microtenant_id (str, optional): The microtenant ID, if applicable. Returns: - int: Status code of the delete operation. + tuple: A tuple containing the response object and error (if any). + + Examples: + Delete a segment group: - Example: - # Delete a segment group by ID - >>> _, _, err = client.zpa.segment_groups.delete_group(updated_group_v2.id) - ... if err: - ... print(f"Error deleting group: {err}") + >>> _, _, error = client.zpa.segment_groups.delete_group('216196257331370181') + >>> if error: + ... print(f"Error deleting segment group: {error}") ... return - ... print(f"Group with ID {updated_group_v2.id} deleted successfully.") + ... print(f"Segment group deleted successfully.") """ http_method = "delete".upper() api_url = format_url(f""" @@ -353,10 +370,9 @@ def delete_group(self, group_id: str, microtenant_id: str = None) -> APIResult[N request, error = self._request_executor.create_request(http_method, api_url, params=params) if error: - return (None, error) + return (None, None, error) response, error = self._request_executor.execute(request) - if error: return (None, response, error) - return (None, response, error) + return (None, response, None) diff --git a/zscaler/zpa/zpa_service.py b/zscaler/zpa/zpa_service.py index f8974ed4..4cd426ff 100644 --- a/zscaler/zpa/zpa_service.py +++ b/zscaler/zpa/zpa_service.py @@ -48,10 +48,9 @@ from zscaler.zpa.oauth2_user_code import OAuth2UserCodeAPI from zscaler.zpa.one_identity import OneIdentityAPI from zscaler.zpa.policies import PolicySetControllerAPI - -# from zscaler.zpa.policy_group import PolicyGroupAPI -# from zscaler.zpa.policy_group_rule import PolicyGroupRuleAPI -# from zscaler.zpa.policy_group_set import PolicyGroupSetAPI +from zscaler.zpa.policy_group import PolicyGroupAPI +from zscaler.zpa.policy_group_rule import PolicyGroupRuleAPI +from zscaler.zpa.policy_group_set import PolicyGroupSetAPI from zscaler.zpa.posture_profiles import PostureProfilesAPI from zscaler.zpa.pra_approval import PRAApprovalAPI from zscaler.zpa.pra_console import PRAConsoleAPI @@ -478,30 +477,6 @@ def one_identity(self) -> OneIdentityAPI: """ return OneIdentityAPI(self._request_executor, self._config) - # @property - # def policy_group(self) -> PolicyGroupAPI: - # """ - # The interface object for the :ref:`ZPA policy-group-controller interface `. - - # """ - # return PolicyGroupAPI(self._request_executor, self._config) - - # @property - # def policy_group_rule(self) -> PolicyGroupRuleAPI: - # """ - # The interface object for the :ref:`ZPA policy-group-rule-controller interface `. - - # """ - # return PolicyGroupRuleAPI(self._request_executor, self._config) - - # @property - # def policy_group_set(self) -> PolicyGroupSetAPI: - # """ - # The interface object for the :ref:`ZPA policy-group-set-controller interface `. - - # """ - # return PolicyGroupSetAPI(self._request_executor, self._config) - @property def tenant_federation_provisioning(self) -> TenantFederationProvisioningAPI: """ @@ -525,3 +500,18 @@ def application_federation(self) -> ApplicationFederationAPI: """ return ApplicationFederationAPI(self._request_executor, self._config) + + @property + def policy_group(self) -> PolicyGroupAPI: + """The interface object for the :ref:`ZPA Policy Group interface `.""" + return PolicyGroupAPI(self._request_executor, self._config) + + @property + def policy_group_rule(self) -> PolicyGroupRuleAPI: + """The interface object for the :ref:`ZPA Policy Group Rule interface `.""" + return PolicyGroupRuleAPI(self._request_executor, self._config) + + @property + def policy_group_set(self) -> PolicyGroupSetAPI: + """The interface object for the :ref:`ZPA Policy Group Set interface `.""" + return PolicyGroupSetAPI(self._request_executor, self._config) From 99b50ec2dbdd9a135bcad167df061d151574776f Mon Sep 17 00:00:00 2001 From: William Guilherme Date: Mon, 27 Jul 2026 11:33:53 -0700 Subject: [PATCH 2/2] chore: Constrain ruff to 0.15.x to keep lint rules stable The dev dependency allowed any ruff >=0.15.0 and the lock file had picked up 0.16.0, which significantly expands the set of rules enabled by default. Because no explicit rule selection is configured, that upgrade silently turned on several new rule families and caused the lint job to report over 15,000 findings across the repository, nearly all of them in files unrelated to any recent change. Cap the dependency at <0.16 and relock, which returns ruff to the 0.15 series used on master. Both lint steps pass again with no source changes. --- poetry.lock | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7ac0cc53..f17c0b0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1309,30 +1309,30 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "ruff" -version = "0.16.0" +version = "0.15.22" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e"}, - {file = "ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522"}, - {file = "ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed"}, - {file = "ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb"}, - {file = "ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472"}, - {file = "ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d"}, - {file = "ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982"}, + {file = "ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8"}, + {file = "ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697"}, + {file = "ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb"}, + {file = "ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74"}, + {file = "ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c"}, + {file = "ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296"}, + {file = "ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262"}, + {file = "ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64"}, + {file = "ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf"}, + {file = "ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde"}, + {file = "ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661"}, + {file = "ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809"}, ] [[package]] @@ -2076,4 +2076,4 @@ dev = [] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "7b597fecf95de98181e08414b881c102e2aa265bdec7a256af694a0c1069653b" +content-hash = "3d47da97c3af7266055e7d4b3d8fc8f81d5a645849d0a0802713cda87e67f386" diff --git a/pyproject.toml b/pyproject.toml index e1d27beb..bcce8804 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ jmespath = ">=1.0.0" [tool.poetry.group.dev.dependencies] black = ">=24.3.0" python-dotenv = ">=1.0.0" -ruff = ">=0.15.0" +ruff = ">=0.15.0,<0.16" pytest = ">=8.3.5" pytest-mock = ">=3.12.0" pytest-asyncio = ">=0.23.0"