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..f17c0b0d 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.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.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.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]] @@ -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]] @@ -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 35556ca4..bcce8804 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" @@ -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" 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)