From f144d90b03cd2f27127b7ca0b05fd59264d7bafd Mon Sep 17 00:00:00 2001 From: William Guilherme Date: Fri, 24 May 2024 17:08:27 -0700 Subject: [PATCH] feat: Added support the zpa policy set v2 endpoints (#96) * feat: Added support the zpa policy set v2 endpoints --- docsrc/dev_requirements.txt | 10 +- docsrc/zs/zpa/policiesv2.rst | 13 + poetry.lock | 84 +- pyproject.toml | 13 +- requirements.txt | 169 +++- zscaler/constants.py | 3 + zscaler/zpa/__init__.py | 16 +- zscaler/zpa/app_segments.py | 34 + zscaler/zpa/policiesv2.py | 1667 ++++++++++++++++++++++++++++++++++ 9 files changed, 1941 insertions(+), 68 deletions(-) create mode 100644 docsrc/zs/zpa/policiesv2.rst create mode 100644 zscaler/zpa/policiesv2.py diff --git a/docsrc/dev_requirements.txt b/docsrc/dev_requirements.txt index c479359c..7f2081dd 100644 --- a/docsrc/dev_requirements.txt +++ b/docsrc/dev_requirements.txt @@ -1,10 +1,10 @@ -furo==2023.5.20 +furo==2024.5.6 pre-commit==3.7.1 -pytest==8.2.1 -python-box==7.0.1 +pytest==8.2.0 +python-box==7.1.1 restfly==1.4.7 -requests==2.32.1 -responses==0.23.1 +requests==2.31.0 +responses==0.25.0 sphinx==7.0.1 toml==0.10.2 urllib3<2 diff --git a/docsrc/zs/zpa/policiesv2.rst b/docsrc/zs/zpa/policiesv2.rst new file mode 100644 index 00000000..af36d237 --- /dev/null +++ b/docsrc/zs/zpa/policiesv2.rst @@ -0,0 +1,13 @@ +policiesv2 +------------------ + +The following methods allow for interaction with the ZPA Policy Sets V2 API endpoints. + +Methods are accessible via ``zpa.policiesv2`` + +.. _zpa-policiesv2: + +.. automodule:: zscaler.zpa.policiesv2 + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 36147155..a3114490 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aenum" @@ -61,33 +61,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "24.4.2" +version = "24.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] @@ -788,13 +788,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -957,13 +957,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] @@ -1146,13 +1146,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.2" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"}, + {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"}, ] [package.dependencies] @@ -1442,13 +1442,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -1712,18 +1712,18 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.18.1" +version = "3.18.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, + {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] dev = ["aenum", "black", "pydash"] @@ -1731,4 +1731,4 @@ dev = ["aenum", "black", "pydash"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "9283f25b495d791d58cd4ac6c57199d5413409fe90558bf29e7d9a08e29708d9" +content-hash = "7986979224235e94aee0a1a7beb928850e530d5d7e0ff04eb1f6af219580f22c" diff --git a/pyproject.toml b/pyproject.toml index 9f86d159..00c72ba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zscaler-sdk-python" -version = "0.2.0" +version = "0.30.0" description = "Official Python SDK for the Zscaler Products (Beta)" authors = ["Zscaler, Inc. "] license = "MIT" @@ -35,11 +35,10 @@ charset-normalizer = "*" idna = "*" python-box = "^7.1.1" python-dateutil = "*" -requests = "*" +requests = ">=2.32.0" responses = "*" restfly = "*" six = "*" -urllib3 = "*" flatdict = "*" pyyaml = "*" xmltodict = "*" @@ -49,13 +48,13 @@ aenum = "*" pydash = "*" flake8 = "*" pytz = "*" -black = "^24.4.1" +black = "*" cryptography = ">=3.4,<43.0" [tool.poetry.dev-dependencies] -black = "*" -pytest = "*" -pytest-asyncio = "*" +black = "24.3.0" +pytest = "^8.2.1" +pytest-asyncio = ">=0.23.7" pytest-mock = "*" pytest-recording = "*" pytest-cov = "*" diff --git a/requirements.txt b/requirements.txt index 0f6ecb22..802bd771 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,9 +5,85 @@ aenum==3.1.15 ; python_version >= "3.8" and python_version < "4.0" \ arrow==1.3.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 \ --hash=sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85 +black==24.3.0 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f \ + --hash=sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93 \ + --hash=sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11 \ + --hash=sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0 \ + --hash=sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9 \ + --hash=sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5 \ + --hash=sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213 \ + --hash=sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d \ + --hash=sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7 \ + --hash=sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837 \ + --hash=sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f \ + --hash=sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395 \ + --hash=sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995 \ + --hash=sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f \ + --hash=sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597 \ + --hash=sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959 \ + --hash=sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5 \ + --hash=sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb \ + --hash=sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4 \ + --hash=sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7 \ + --hash=sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd \ + --hash=sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7 certifi==2024.2.2 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 +cffi==1.16.0 ; python_version >= "3.8" and python_version < "4.0" and platform_python_implementation != "PyPy" \ + --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ + --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ + --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ + --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ + --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ + --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ + --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ + --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ + --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ + --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ + --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ + --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ + --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ + --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ + --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ + --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ + --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ + --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ + --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ + --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ + --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ + --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ + --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ + --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ + --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ + --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ + --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ + --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ + --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ + --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ + --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ + --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ + --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ + --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ + --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ + --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ + --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ + --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ + --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ + --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ + --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ + --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ + --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ + --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ + --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ + --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ + --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ + --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ + --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ + --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ + --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ + --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ @@ -99,6 +175,45 @@ charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 +click==8.1.7 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de +colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +cryptography==42.0.7 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55 \ + --hash=sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785 \ + --hash=sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b \ + --hash=sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886 \ + --hash=sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82 \ + --hash=sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1 \ + --hash=sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda \ + --hash=sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f \ + --hash=sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68 \ + --hash=sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60 \ + --hash=sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7 \ + --hash=sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd \ + --hash=sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582 \ + --hash=sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc \ + --hash=sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858 \ + --hash=sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b \ + --hash=sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2 \ + --hash=sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678 \ + --hash=sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13 \ + --hash=sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4 \ + --hash=sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8 \ + --hash=sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604 \ + --hash=sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477 \ + --hash=sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e \ + --hash=sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a \ + --hash=sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9 \ + --hash=sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14 \ + --hash=sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda \ + --hash=sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da \ + --hash=sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562 \ + --hash=sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2 \ + --hash=sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9 flake8==5.0.4 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db \ --hash=sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248 @@ -201,9 +316,24 @@ multidict==6.0.5 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24 \ --hash=sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423 \ --hash=sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef +mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ + --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 +packaging==24.0 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ + --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 +pathspec==0.12.1 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 +platformdirs==4.2.2 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 pycodestyle==2.9.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785 \ --hash=sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b +pycparser==2.22 ; python_version >= "3.8" and python_version < "4.0" and platform_python_implementation != "PyPy" \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc pycryptodomex==3.20.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1 \ --hash=sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305 \ @@ -237,15 +367,27 @@ pycryptodomex==3.20.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea \ --hash=sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b \ --hash=sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913 -pydash==8.0.0 ; python_version >= "3.8" and python_version < "4.0" \ - --hash=sha256:5a90d98b5f370bb9620f786221579df8f83d54f1f58de6a66f52b1bdba7175d1 \ - --hash=sha256:be2c35df332473a5a939d485422c71a03b19b1a98d6559efb832eadb4a2cfc36 +pydash==8.0.1 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:60265bab97fd00d5afd27dfbff9b6abfa22d0a965e166476b9066d84cd44c940 \ + --hash=sha256:a24619643d3c054bfd56a9ae1cb7bd00e9774eaf369d7bb8d62b3daa2462bdbd pyflakes==2.5.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2 \ --hash=sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3 -python-box==5.4.1 ; python_version >= "3.8" and python_version < "4.0" \ - --hash=sha256:60ae9156de34cf92b899bd099580950df70a5b0813e67a3310a1cdd1976457fa \ - --hash=sha256:b68e0f8abc86f3deda751b3390f64df64a0989459de51ba4db949662a7b4d8ac +python-box==7.1.1 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:0036fd47d388deaca8ebd65aea905f88ee6ef91d1d8ce34898b66f1824afbe80 \ + --hash=sha256:1b3f346e332dba16df0b0543d319d9e7ce07d93e5ae152175302894352aa2d28 \ + --hash=sha256:24c4ec0ee0278f66321100aaa9c615413da27a14ff43d376a2a3b4665e1d9494 \ + --hash=sha256:2a3df244a5a79ac8f8447b5d11b5be0f2747d7b141cb2866060081ae9b53cc50 \ + --hash=sha256:4ea13c98e05a3ec0ff26f254986a17290b69b5ade209fad081fd628f8fcfaa08 \ + --hash=sha256:63b609555554d7a9d4b6e725f8e78ef1717c67e7d386200e03422ad612338df8 \ + --hash=sha256:81ed1ec0f0ff2370227fc07277c5baca46d190a4747631bad7eb6ab1630fb7d9 \ + --hash=sha256:8891735b4148e84d348c6eadd2f127152f751c9603e35d43a1f496183a291ac4 \ + --hash=sha256:a0f1333c42e81529b6f68c192050df9d4505b803be7ac47f114036b98707f7cf \ + --hash=sha256:aabf8b9ae5dbc8ba431d8cbe0d4cfe737a25d52d68b0f5f2ff34915c21a2c1db \ + --hash=sha256:c046608337e723ae4de3206db5d1e1202ed166da2dfdc70c1f9361e72ace5633 \ + --hash=sha256:d95e5eec4fc8f3fc5c9cc7347fc2eb4f9187c853d34c90b1658d1eff96cd4eac \ + --hash=sha256:f76b5b7f0cdc07bfdd4200dc24e6e33189bb2ae322137a2b7110fd41891a3157 \ + --hash=sha256:f9266795e9c233874fb5b34fa994054b4fb0371881678e6ec45aec17fc95feac python-dateutil==2.9.0.post0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 @@ -304,9 +446,9 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f -requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0" \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.2 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \ + --hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c responses==0.25.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66 \ --hash=sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a @@ -316,12 +458,15 @@ restfly==1.4.7 ; python_version >= "3.8" and python_version < "4.0" \ six==1.16.0 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11" \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f types-python-dateutil==2.9.0.20240316 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202 \ --hash=sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b -typing-extensions==4.11.0 ; python_version >= "3.8" and python_version < "4.0" \ - --hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \ - --hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a +typing-extensions==4.12.0 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8 \ + --hash=sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594 urllib3==2.2.1 ; python_version >= "3.8" and python_version < "4.0" \ --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \ --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19 diff --git a/zscaler/constants.py b/zscaler/constants.py index d5a94a50..14545513 100644 --- a/zscaler/constants.py +++ b/zscaler/constants.py @@ -9,8 +9,11 @@ "PREVIEW": "https://config.zpapreview.net", "QA": "https://config.qa.zpath.net", "QA2": "https://pdx2-zpa-config.qa2.zpath.net", + "DEV": "https://public-api.dev.zpath.net", } +DEV_AUTH_URL = "https://authn1.dev.zpath.net/authn/v1/oauth/token" + RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504} MAX_RETRIES = 5 BACKOFF_FACTOR = 1 diff --git a/zscaler/zpa/__init__.py b/zscaler/zpa/__init__.py index 553ff1a3..b9280300 100644 --- a/zscaler/zpa/__init__.py +++ b/zscaler/zpa/__init__.py @@ -11,7 +11,7 @@ from zscaler import __version__ from zscaler.cache.no_op_cache import NoOpCache from zscaler.cache.zscaler_cache import ZscalerCache -from zscaler.constants import ZPA_BASE_URLS +from zscaler.constants import ZPA_BASE_URLS, DEV_AUTH_URL from zscaler.errors.http_error import HTTPError, ZscalerAPIError from zscaler.exceptions.exceptions import HTTPException, ZscalerAPIException from zscaler.logger import setup_logging @@ -40,6 +40,7 @@ from zscaler.zpa.lss import LSSConfigControllerAPI from zscaler.zpa.machine_groups import MachineGroupsAPI from zscaler.zpa.policies import PolicySetsAPI +from zscaler.zpa.policiesv2 import PolicySetsV2API from zscaler.zpa.posture_profiles import PostureProfilesAPI from zscaler.zpa.privileged_remote_access import PrivilegedRemoteAccessAPI from zscaler.zpa.provisioning import ProvisioningKeyAPI @@ -151,7 +152,7 @@ def refreshToken(self): @retry_with_backoff(retries=5) def login(self): """Log in to the ZPA API and set the access token for subsequent requests.""" - data = urllib.parse.urlencode({"client_id": self.client_id, "client_secret": self.client_secret}) + params = {"client_id": self.client_id, "client_secret": self.client_secret} headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", @@ -159,6 +160,9 @@ def login(self): } try: url = f"{self.baseurl}/signin" + if self.cloud == "DEV": + url = DEV_AUTH_URL + "?grant_type=CLIENT_CREDENTIALS" + data = urllib.parse.urlencode(params) resp = requests.post(url, data=data, headers=headers, timeout=self.timeout) # Avoid logging all data from the response, focus on the status and a summary instead logger.info("Login attempt with status: %d", resp.status_code) @@ -571,6 +575,14 @@ def policies(self): """ return PolicySetsAPI(self) + @property + def policiesv2(self): + """ + The interface object for the :ref:`ZPA Policy Sets V2 interface `. + + """ + return PolicySetsV2API(self) + @property def posture_profiles(self): """ diff --git a/zscaler/zpa/app_segments.py b/zscaler/zpa/app_segments.py index 55d1550c..d35149a2 100644 --- a/zscaler/zpa/app_segments.py +++ b/zscaler/zpa/app_segments.py @@ -91,6 +91,40 @@ def get_segment_by_name(self, name): return app return None + def get_segments_by_type(self, application_type: str, expand_all: bool = False, **kwargs) -> Box: + """ + Retrieve all configured application segments of a specified type, optionally expanding all related data. + + Args: + application_type (str): Type of application segment to retrieve. Must be one of "BROWSER_ACCESS", "INSPECT", "SECURE_REMOTE_ACCESS". + expand_all (bool, optional): Whether to expand all related data. Defaults to False. + + Keyword Args: + max_items (int, optional): The maximum number of items to request before stopping iteration. + max_pages (int, optional): The maximum number of pages to request before stopping iteration. + pagesize (int, optional): Specifies the page size. The default size is 20, but the maximum size is 500. + page (int, optional): Specifies the page number to begin fetching from. + search (str, optional): The search string used to match against features and fields. + + Returns: + BoxList: List of application segments. + + Examples: + >>> app_type = 'BROWSER_ACCESS' + >>> expand_all = True + >>> search = "ba_server01" + >>> app_segments = zpa.app_segments.get_segments_by_type(app_type, expand_all, search=search) + """ + params = {"applicationType": application_type, "expandAll": "true" if expand_all else "false"} + # Include additional search parameters if specified + if "search" in kwargs: + params["search"] = kwargs["search"] + + result, error = self.rest.get_paginated_data(path="/application/getAppsByType", params=params, **kwargs) + if error: + return BoxList([]) # Return an empty BoxList on failure due to the error + return result + def delete_segment(self, segment_id: str, force_delete: bool = False) -> int: """ Delete an application segment. diff --git a/zscaler/zpa/policiesv2.py b/zscaler/zpa/policiesv2.py new file mode 100644 index 00000000..e48bb81a --- /dev/null +++ b/zscaler/zpa/policiesv2.py @@ -0,0 +1,1667 @@ +import functools + +from box import Box, BoxList +from requests import Response + +from zscaler.utils import add_id_groups, convert_keys, snake_to_camel +from zscaler.zpa.client import ZPAClient + + +class PolicySetsV2API: + def __init__(self, client: ZPAClient): + self.rest = client + + POLICY_MAP = { + "access": "ACCESS_POLICY", + "capabilities": "CAPABILITIES_POLICY", + "client_forwarding": "CLIENT_FORWARDING_POLICY", + "clientless": "CLIENTLESS_SESSION_PROTECTION_POLICY", + "credential": "CREDENTIAL_POLICY", + "inspection": "INSPECTION_POLICY", + "isolation": "ISOLATION_POLICY", + "redirection": "REDIRECTION_POLICY", + "siem": "SIEM_POLICY", + "timeout": "TIMEOUT_POLICY", + } + + reformat_params = [ + ("app_server_group_ids", "appServerGroups"), + ("app_connector_group_ids", "appConnectorGroups"), + ("service_edge_group_ids", "serviceEdgeGroups"), + ] + + def _create_conditions(self, conditions: list) -> list: + """ + Creates a dict template for feeding conditions into the ZPA Policies API when adding or updating a policy. + + Args: + conditions (list): List of condition tuples where each tuple represents a specific policy condition. + + Returns: + :obj:`list`: List containing the conditions formatted for the ZPA Policies API. + """ + + grouped_conditions = {"app_and_app_group": []} # Specific group for APP and APP_GROUP + template = [] + + for condition in conditions: + object_type, values = condition[0], condition[1] + + if object_type in ["app", "app_group"]: + # Group APP and APP_GROUP together in the same operands block + grouped_conditions["app_and_app_group"].append({"objectType": object_type.upper(), "values": values}) + elif object_type in [ + "console", + "machine_grp", + "location", + "branch_connector_group", + "edge_connector_group", + "client_type", + ]: + # Each of these object types must be under individual operands blocks + template.append({"operands": [{"objectType": object_type.upper(), "values": values}]}) + elif object_type in ["saml", "scim", "scim_group"]: + # These types use "entryValues" with "lhs" and "rhs" + template.append( + { + "operands": [ + {"objectType": object_type.upper(), "entryValues": [{"lhs": v[0], "rhs": v[1]} for v in values]} + ] + } + ) + elif object_type in ["posture", "trusted_network", "country_code", "platform"]: + # These types use "entryValues" with "lhs" as unique ID and "rhs" as "true"/"false" + template.append( + {"operands": [{"objectType": object_type.upper(), "entryValues": [{"lhs": values[0], "rhs": values[1]}]}]} + ) + else: + # Handle other possible object types if needed in the future + template.append({"operands": [{"objectType": object_type.upper(), "values": values}]}) + + # Add the grouped APP and APP_GROUP conditions if any were specified + if grouped_conditions["app_and_app_group"]: + template.append({"operands": grouped_conditions["app_and_app_group"]}) + + return template + + def get_policy(self, policy_type: str) -> Box: + """ + Returns the policy and rule sets for the given policy type. + + Args: + policy_type (str): The type of policy to be returned. Accepted values are: + + | ``access`` - returns the Access Policy + | ``capabilities`` - returns the Capabilities Policy + | ``client_forwarding`` - returns the Client Forwarding Policy + | ``clientless`` - returns the Clientlesss Session Protection Policy + | ``credential`` - returns the Credential Policy + | ``inspection`` - returns the Inspection Policy + | ``isolation`` - returns the Isolation Policy + | ``redirection`` - returns the Redirection Policy + | ``siem`` - returns the SIEM Policy + | ``timeout`` - returns the Timeout Policy + + Returns: + :obj:`Box`: The resource record of the specified policy type. + + Examples: + Request the specified Policy. + + >>> pprint(zpa.policies.get_policy('access')) + + """ + # Map the simplified policy_type name to the name expected by the Zscaler API + mapped_policy_type = self.POLICY_MAP.get(policy_type, None) + + # If the user provided an incorrect name, raise an error + if not mapped_policy_type: + raise ValueError( + f"Incorrect policy type provided: {policy_type}\n " + f"Policy type must be 'access', 'timeout', 'client_forwarding' or 'siem'." + ) + + return self.rest.get(f"policySet/policyType/{mapped_policy_type}") + + def get_rule(self, policy_type: str, rule_id: str) -> Box: + """ + Returns the specified policy rule. + + Args: + policy_type (str): The type of policy to be returned. Accepted values are: + + | ``access`` + | ``capabilities`` + | ``client_forwarding`` + | ``clientless`` + | ``credential`` + | ``inspection`` + | ``isolation`` + | ``redirection`` + | ``siem`` + | ``timeout`` + + rule_id (str): The unique identifier for the policy rule. + + Returns: + :obj:`Box`: The resource record for the requested rule. + + Examples: + >>> policy_rule = zpa.policies.get_rule(policy_id='99999', + ... rule_id='88888') + + """ + # Get the policy id for the supplied policy_type + policy_id = self.get_policy(policy_type).id + + return self.rest.get(f"policySet/{policy_id}/rule/{rule_id}") + + def get_rule_by_name(self, policy_type: str, rule_name: str) -> Box: + """ + Returns the specified policy rule by its name. + + Args: + policy_type (str): The type of policy to be returned. + Accepted values are: ``access``, ``timeout``, ``client_forwarding``, ``siem`` + rule_name (str): The name of the policy rule. + + Returns: + :obj:`Box`: The resource record for the requested rule. + + Examples: + >>> policy_rule = zpa.policies.get_rule_by_name(policy_type='access', rule_name='MyRule') + + """ + all_rules = self.list_rules(policy_type) + for rule in all_rules: + if rule.name == rule_name: + return rule + return None + + def list_rules(self, policy_type: str, **kwargs) -> BoxList: + """ + Returns policy rules for a given policy type. + + Args: + policy_type (str): + The policy type. Accepted values are: + + | ``access`` - returns Access Policy rules + | ``timeout`` - returns Timeout Policy rules + | ``client_forwarding`` - returns Client Forwarding Policy rules + | ``isolation`` - returns Isolation Policy rules + | ``inspection`` - returns Inspection Policy rules + | ``redirection`` - returns Redirection Policy rules + | ``credential`` - returns Credential Policy rules + | ``capabilities`` - returns Capabilities Policy rules + | ``siem`` - returns SIEM Policy rules + Returns: + :obj:`list`: A list of all policy rules that match the requested type. + + Examples: + >>> for policy in zpa.policies.list_type('type') + ... pprint(policy) + + """ + + # Map the simplified policy_type name to the name expected by the Zscaler API + mapped_policy_type = self.POLICY_MAP.get(policy_type, None) + + # If the user provided an incorrect name, raise an error + if not mapped_policy_type: + raise ValueError( + f"Incorrect policy type provided: {policy_type}\n " + f"Policy type must be 'access', 'timeout', 'client_forwarding' or 'siem'." + ) + list, _ = self.rest.get_paginated_data( + path=f"policySet/rules/policyType/{mapped_policy_type}", + **kwargs, + api_version="v1", + ) + return list + + def add_access_rule( + self, + name: str, + action: str, + app_connector_group_ids: list = [], + app_server_group_ids: list = [], + **kwargs, + ) -> Box: + """ + Add a new Access Policy rule. + + See the `ZPA Access Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + action (str): + The action for the policy. Accepted values are: + + | ``allow`` + | ``deny`` + **kwargs: + Optional keyword args. + + Keyword Args: + custom_msg (str): + A custom message. + description (str): + A description for the rule. + app_connector_group_ids (:obj:`list` of :obj:`str`): + A list of application connector IDs that will be attached to the access policy rule. + app_server_group_ids (:obj:`list` of :obj:`str`): + A list of application server group IDs that will be attached to the access policy rule. + + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '99999'), + ('app', 'id', '88888'), + ('app_group', 'id', '77777), + ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'), + ('trusted_network', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx', True)] + + Returns: + :obj:`Box`: The resource record of the newly created access policy rule. + + """ + + # Initialise the payload + payload = { + "name": name, + "action": action.upper(), + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + if app_connector_group_ids: + payload["appConnectorGroups"] = [{"id": group_id} for group_id in app_connector_group_ids] + + if app_server_group_ids: + payload["appServerGroups"] = [{"id": group_id} for group_id in app_server_group_ids] + + add_id_groups(self.reformat_params, kwargs, payload) + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("access").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_access_rule( + self, + policy_type: str, + rule_id: str, + app_connector_group_ids: list = None, + app_server_group_ids: list = None, + **kwargs, + ) -> Box: + """ + Update an existing policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + policy_type (str): + The policy type. Accepted values are: + | ``access`` + rule_id (str): + The unique identifier for the rule to be updated. + + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + | ``allow`` + | ``deny`` + app_connector_group_ids (:obj:`list` of :obj:`str`): + A list of application connector IDs that will be attached to the access policy rule. Defaults to an empty list. + app_server_group_ids (:obj:`list` of :obj:`str`): + A list of server group IDs that will be attached to the access policy rule. Defaults to an empty list. + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + ... + """ + # Handle default values for app_connector_group_ids and app_server_group_ids + app_connector_group_ids = app_connector_group_ids or [] + app_server_group_ids = app_server_group_ids or [] + + # Get policy id for specified policy type + policy_id = self.get_policy(policy_type).id + + payload = convert_keys(self.get_rule(policy_type, rule_id)) + + # Update kwargs with app_connector_group_ids and app_server_group_ids for processing with add_id_groups + kwargs["app_connector_group_ids"] = app_connector_group_ids + kwargs["app_server_group_ids"] = app_server_group_ids + + add_id_groups(self.reformat_params, kwargs, payload) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + resp = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2").status_code + + # Return the object if it was updated successfully + if not isinstance(resp, Response): + return self.get_rule(policy_type, rule_id) + + def add_timeout_rule(self, name: str, **kwargs) -> Box: + """ + Add a new Timeout Policy rule. + + See the `ZPA Timeout Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + **kwargs: + Optional parameters. + + Keyword Args: + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'), + ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)] + custom_msg (str): + A custom message. + description (str): + A description for the rule. + re_auth_idle_timeout (int): + The re-authentication idle timeout value in seconds. + re_auth_timeout (int): + The re-authentication timeout value in seconds. + + Returns: + :obj:`Box`: The resource record of the newly created Timeout Policy rule. + + """ + + # Initialise the payload + payload = { + "name": name, + "action": "RE_AUTH", + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("timeout").id + + # Use specified timeouts or default to UI values + payload["reauthTimeout"] = kwargs.get("re_auth_timeout", 172800) + payload["reauthIdleTimeout"] = kwargs.get("re_auth_idle_timeout", 600) + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_timeout_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + policy_type (str): + The policy type. Accepted values are: + + | ``timeout`` + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + + | ``bypass`` + | ``intercept`` + | ``intercept_accessible`` + + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'), + ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)] + custom_msg (str): + A custom message. + description (str): + A description for the rule. + re_auth_idle_timeout (int): + The re-authentication idle timeout value in seconds. + re_auth_timeout (int): + The re-authentication timeout value in seconds. + + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + Updates the name only for an Timeout Policy rule: + + >>> zpa.policies.update_rule('timeout', '99999', name='new_rule_name') + + Updates the action only for a Timeout Policy rule: + + >>> zpa.policies.update_rule('timeout', '888888', action='BYPASS') + + """ + # Get policy id for specified policy type + policy_id = self.get_policy("timeout").id + + payload = convert_keys(self.get_rule(policy_id, rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + resp = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2").status_code + + # Return the object if it was updated successfully + if not isinstance(resp, Response): + return self.get_rule(policy_id, rule_id) + + def add_client_forwarding_rule(self, name: str, action: str, **kwargs) -> Box: + """ + Add a new Client Forwarding Policy rule. + + See the + `ZPA Client Forwarding Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + action (str): + The action for the policy. Accepted values are: + + | ``bypass`` + | ``intercept`` + | ``intercept_accessible`` + **kwargs: + Optional keyword args. + + Keyword Args: + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter', 'zpn_client_type_zapp'), + ('trusted_network', 'b15e4cad-fa6e-8182-9fc3-8125ee6a65e1', True)] + custom_msg (str): + A custom message. + description (str): + A description for the rule. + + Returns: + :obj:`Box`: The resource record of the newly created Client Forwarding Policy rule. + + """ + + # Initialise the payload + payload = { + "name": name, + "action": action.upper(), + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("client_forwarding").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_client_forwarding_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing client forwarding policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + + | ``bypass`` + | ``intercept`` + | ``intercept_accessible`` + description (str): + Additional information about the client forwarding policy rule. + enabled (bool): + Whether or not the client forwarding policy rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + ("client_type", ['zpn_client_type_edge_connector', 'zpn_client_type_branch_connector', 'zpn_client_type_machine_tunnel', 'zpn_client_type_zapp', 'zpn_client_type_zapp_partner']), + + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + Updates the name only for an Access Policy rule: + + >>> zpa.policiesv2.update_client_forwarding_rule( + ... rule_id='216199618143320419', + ... name='Update_Redirection_Rule_v2', + ... description='Update_Redirection_Rule_v2', + ... action='redirect_default', + ... conditions=[ + ... ("client_type", ['zpn_client_type_edge_connector', 'zpn_client_type_branch_connector', 'zpn_client_type_machine_tunnel', 'zpn_client_type_zapp', 'zpn_client_type_zapp_partner']), + ... ], + ... ) + """ + + # Ensure the action is provided and convert to uppercase + if "action" not in kwargs: + raise ValueError("The 'action' attribute is mandatory.") + + action = kwargs.pop("action").upper() + + # Get policy id for specified policy type + policy_id = self.get_policy("client_forwarding").id + + payload = convert_keys(self.get_rule("client_forwarding", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + # Set the action in the payload + payload["action"] = action + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("client_forwarding", rule_id) + + def add_isolation_rule(self, name: str, action: str, zpn_isolation_profile_id: str, **kwargs) -> Box: + """ + Add a new Isolation Policy rule. + + See the + `ZPA Isolation Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + action (str): + The action for the policy. Accepted values are: + + | ``isolate`` + | ``bypass_isolate`` + **kwargs: + Optional keyword args. + + Keyword Args: + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter')] + zpn_isolation_profile_id (str): + The isolation profile ID associated with the rule + description (str): + A description for the rule. + + Returns: + :obj:`Box`: The resource record of the newly created Client Isolation Policy rule. + + """ + + # Initialise the payload + payload = { + "name": name, + "action": action.upper(), + "zpnIsolationProfileId": zpn_isolation_profile_id, + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Pre-configure client_type to 'zpn_client_type_exporter' + payload["conditions"].append({"operands": [{"objectType": "CLIENT_TYPE", "values": ["zpn_client_type_exporter"]}]}) + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("isolation").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_isolation_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing client isolation policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + + | ``isolate`` + | ``bypass_isolate`` + description (str): + Additional information about the client forwarding policy rule. + enabled (bool): + Whether or not the client forwarding policy rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + zpn_isolation_profile_id (str): + The unique identifier of the inspection profile. This field is applicable only for inspection policies. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter')] + + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + Updates the name only for an Isolation Policy rule: + + >>> zpa.policiesv2.update_isolation_rule( + ... rule_id='216199618143320419', + ... name='Update_Isolation_Rule_v2', + ... description='Update_Isolation_Rule_v2', + ... action='isolate', + ... conditions=[ + ... ("app", ["216199618143361683"]), + ... ("app_group", ["216199618143360301"]), + ... ("scim_group", [("216199618143191058", "2079468"), ("216199618143191058", "2079446")]), + ... ], + ... ) + """ + + # Ensure the action is provided and convert to uppercase + if "action" not in kwargs: + raise ValueError("The 'action' attribute is mandatory.") + + action = kwargs.pop("action").upper() + + # Get policy id for specified policy type + policy_id = self.get_policy("isolation").id + + payload = convert_keys(self.get_rule("isolation", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + # Pre-configure client_type to 'zpn_client_type_exporter' + payload["conditions"].append({"operands": [{"objectType": "CLIENT_TYPE", "values": ["zpn_client_type_exporter"]}]}) + + # Set the action in the payload + payload["action"] = action + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("isolation", rule_id) + + def add_app_protection_rule(self, name: str, action: str, zpn_inspection_profile_id: str, **kwargs) -> Box: + """ + Add a new AppProtection Policy rule. + + See the + `ZPA AppProtection Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + action (str): + The action for the policy. Accepted values are: + + | ``inspect`` + | ``bypass_inspect`` + **kwargs: + Optional keyword args. + + Keyword Args: + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter')] + zpn_inspection_profile_id (str): + The AppProtection profile ID associated with the rule + description (str): + A description for the rule. + + Returns: + :obj:`Box`: The resource record of the newly created Client Inspection Policy rule. + + """ + + # Initialise the payload + payload = { + "name": name, + "action": action.upper(), + "zpnInspectionProfileId": zpn_inspection_profile_id, + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("inspection").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_app_protection_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing app protection policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + + | ``isolate`` + | ``bypass_isolate`` + description (str): + Additional information about the app protection policy rule. + enabled (bool): + Whether or not the app protection policy rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + zpn_inspection_profile_id (str): + The unique identifier of the inspection profile. This field is applicable only for inspection policies. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + [('app', 'id', '926196382959075416'), + ('app', 'id', '926196382959075417'), + ('app_group', 'id', '926196382959075332), + ('client_type', 'zpn_client_type_exporter')] + + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + Updates the name only for an Inspection Policy rule: + + >>> zpa.policiesv2.update_app_protection_rule( + ... rule_id='216199618143320419', + ... name='Update_Inspection_Rule_v2', + ... description='Update_Inspection_Rule_v2', + ... action='inspect', + ... zpn_inspection_profile_id='216199618143363055' + ... conditions=[ + ... ("app", ["216199618143361683"]), + ... ("app_group", ["216199618143360301"]), + ... ("scim_group", [("216199618143191058", "2079468"), ("216199618143191058", "2079446")]), + ... ], + ... ) + """ + + # Ensure the action is provided and convert to uppercase + if "action" not in kwargs: + raise ValueError("The 'action' attribute is mandatory.") + + action = kwargs.pop("action").upper() + + # Get policy id for specified policy type + policy_id = self.get_policy("inspection").id + + payload = convert_keys(self.get_rule("inspection", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + # Set the action in the payload + payload["action"] = action + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("inspection", rule_id) + + def add_privileged_credential_rule(self, name: str, credential_id: str, **kwargs) -> Box: + """ + Add a new Privileged Remote Access Credential Policy rule. + + See the + `ZPA Privileged Policies API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new rule. + credential_id (str): + The ID of the privileged credential for the rule. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the rule. Accepted value is: ``inject_credentials`` + description (str): + Additional information about the credential rule. + enabled (bool): + Whether or not the credential rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, `RHS value`. + If you are adding multiple values for the same object type then you will need a new entry for each value. + + * `conditions`: This is for providing the set of conditions for the policy + * `object_type`: This is for specifying the policy criteria. The following values are supported: "app", "app_group", "saml", "scim", "scim_group" + * `saml`: The unique Identity Provider ID and SAML attribute ID + * `scim`: The unique Identity Provider ID and SCIM attribute ID + * `scim_group`: The unique Identity Provider ID and SCIM_GROUP ID + + .. code-block:: python + + zpa.policiesv2.add_privileged_credential_rule( + name='new_pra_credential_rule', + description='new_pra_credential_rule', + credential_id='credential_id', + conditions=[ + ("scim_group", [("idp_id", "scim_group_id"), ("idp_id", "scim_group_id")]) + ("console", ["console_id"]), + ], + ) + + Returns: + :obj:`Box`: The resource record of the newly created Privileged Remote Access Credential rule. + """ + + # Initialise the payload + payload = { + "name": name, + "action": "INJECT_CREDENTIALS", + "credential": {"id": credential_id}, + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("credential").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # This is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_privileged_credential_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing privileged credential policy rule. + + Args: + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: Optional keyword args. + + Keyword Args: + action (str): + The action for the rule. Accepted value is: ``inject_credentials`` + description (str): + Additional information about the credential rule. + enabled (bool): + Whether or not the credential rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + credential_id (str): + The ID of the privileged credential for the rule. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + + Examples: + + .. code-block:: python + + [('saml', 'id', '926196382959075416'), + ('scim', 'id', '926196382959075417'), + ('scim_group', 'id', '926196382959075332), + 'credential_id', '926196382959075332, 'zpn_client_type_zapp'), + + Returns: + :obj:`Box`: The updated policy-credential-rule resource record. + + Examples: + Updates the name only for an Credential Policy rule: + + >>> zpa.policiesv2.update_privileged_credential_rule( + ... rule_id='888888', + ... name='credential_rule_new_name') + + """ + # Get the policy id for the specified policy type + policy_id = self.get_policy("credential").id + + # Retrieve the existing rule to update + payload = convert_keys(self.get_rule("credential", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + elif key == "credential_id": + payload["credential"] = {"id": value} + else: + payload[snake_to_camel(key)] = value + + # Ensure the action is set to CHECK_CAPABILITIES + payload["action"] = "INJECT_CREDENTIALS" + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("credential", rule_id) + + def add_capabilities_rule(self, name: str, **kwargs) -> Box: + """ + Add a new Capability Access rule. + + See the + `ZPA Capabilities Policies API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new capability rule. + action (str): + The action for the policy. Accepted value is: ``CHECK_CAPABILITIES`` + + **kwargs: + Optional keyword args. + + Keyword Args: + rule_order (str): + The new order for the rule. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, `RHS value`. + If you are adding multiple values for the same object type then you will need a new entry for each value. + + - `conditions`: This is for providing the set of conditions for the policy + - `object_type`: This is for specifying the policy criteria. The following values are supported: "app", "app_group", "saml", "scim", "scim_group" + - `app`: The unique Application Segment ID + - `app_group`: The unique Segment Group ID + - `saml`: The unique Identity Provider ID and SAML attribute ID + - `scim`: The unique Identity Provider ID and SCIM attribute ID + - `scim_group`: The unique Identity Provider ID and SCIM_GROUP ID + + privileged_capabilities (dict): A dictionary specifying the privileged capabilities with boolean values. The supported capabilities are: + + - clipboard_copy (bool): Indicates the PRA Clipboard Copy function. + - clipboard_paste (bool): Indicates the PRA Clipboard Paste function. + - file_upload (bool): Indicates the PRA File Transfer capabilities that enables the File Upload function. + - file_download (bool): Indicates the PRA File Transfer capabilities that enables the File Download function. + - inspect_file_upload (bool): Inspects the file via ZIA sandbox and uploads the file following the inspection. + - inspect_file_download (bool): Inspects the file via ZIA sandbox and downloads the file following the inspection. + - monitor_session (bool): Indicates the PRA Monitoring Capabilities to enable the PRA Session Monitoring function. + - record_session (bool): Indicates the PRA Session Recording capabilities to enable PRA Session Recording. + - share_session (bool): Indicates the PRA Session Control and Monitoring capabilities to enable PRA Session Monitoring. + + Returns: + :obj:`Box`: The resource record of the newly created Capabilities rule. + + Example: + Add a new capability rule with various capabilities and conditions: + + .. code-block:: python + + zpa.policiesv2.add_capabilities_rule( + name='New_Capability_Rule', + description='New_Capability_Rule', + action='check_capabilities', + conditions=[ + ("app", ["app_segment_id"]), + ("app_group", ["segment_group_id"]), + ("scim_group", [("idp_id", "scim_group_id"), ("idp_id", "scim_group_id")]) + ], + privileged_capabilities={ + "clipboard_copy": True, + "clipboard_paste": True, + "file_download": True, + "file_upload": True, # This will add "FILE_UPLOAD" to the capabilities list + "record_session": True, + # To handle the edge case, set file_upload to None to disable it + "file_upload": None + } + ) + """ + + # Initialise the payload + payload = { + "name": name, + "action": "CHECK_CAPABILITIES", + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + # Process privileged capabilities + if "privileged_capabilities" in kwargs: + capabilities = [] + priv_caps_map = kwargs.pop("privileged_capabilities") + + if priv_caps_map.get("clipboard_copy", False): + capabilities.append("CLIPBOARD_COPY") + if priv_caps_map.get("clipboard_paste", False): + capabilities.append("CLIPBOARD_PASTE") + if priv_caps_map.get("file_download", False): + capabilities.append("FILE_DOWNLOAD") + + # Handling the edge case for file_upload + if priv_caps_map.get("file_upload") == True: + capabilities.append("FILE_UPLOAD") + elif priv_caps_map.get("file_upload") == False: + capabilities.append("INSPECT_FILE_UPLOAD") + # If file_upload is not present or set to None, do not append either capability + + if priv_caps_map.get("inspect_file_download", False): + capabilities.append("INSPECT_FILE_DOWNLOAD") + if priv_caps_map.get("inspect_file_upload", False): + capabilities.append("INSPECT_FILE_UPLOAD") + if priv_caps_map.get("monitor_session", False): + capabilities.append("MONITOR_SESSION") + if priv_caps_map.get("record_session", False): + capabilities.append("RECORD_SESSION") + if priv_caps_map.get("share_session", False): + capabilities.append("SHARE_SESSION") + + payload["privilegedCapabilities"] = {"capabilities": capabilities} + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("capabilities").id + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # This is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_capabilities_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing capabilities policy rule. + + See the + `ZPA Capabilities Policies API reference `_ + for further detail on optional keyword parameter structures. + + Args: + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + rule_order (str): + The new order for the rule. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, `RHS value`. + If you are adding multiple values for the same object type then you will need a new entry for each value. + + - `conditions`: This is for providing the set of conditions for the policy + - `object_type`: This is for specifying the policy criteria. The following values are supported: "app", "app_group", "saml", "scim", "scim_group" + - `app`: The unique Application Segment ID + - `app_group`: The unique Segment Group ID + - `saml`: The unique Identity Provider ID and SAML attribute ID + - `scim`: The unique Identity Provider ID and SCIM attribute ID + - `scim_group`: The unique Identity Provider ID and SCIM_GROUP ID + + privileged_capabilities (dict): A dictionary specifying the privileged capabilities with boolean values. The supported capabilities are: + + - clipboard_copy (bool): Indicates the PRA Clipboard Copy function. + - clipboard_paste (bool): Indicates the PRA Clipboard Paste function. + - file_upload (bool): Indicates the PRA File Transfer capabilities that enables the File Upload function. + - file_download (bool): Indicates the PRA File Transfer capabilities that enables the File Download function. + - inspect_file_upload (bool): Inspects the file via ZIA sandbox and uploads the file following the inspection. + - inspect_file_download (bool): Inspects the file via ZIA sandbox and downloads the file following the inspection. + - monitor_session (bool): Indicates the PRA Monitoring Capabilities to enable the PRA Session Monitoring function. + - record_session (bool): Indicates the PRA Session Recording capabilities to enable PRA Session Recording. + - share_session (bool): Indicates the PRA Session Control and Monitoring capabilities to enable PRA Session Monitoring. + + Returns: + :obj:`Box`: The updated policy-capability-rule resource record. + + Examples: + Updates the name and capabilities for an existing Capability Policy rule: + + >>> zpa.policiesv2.update_capabilities_rule( + ... rule_id='888888', + ... name='Updated_Capability_Rule', + ... conditions=[ + ... ("app", ["216199618143361683"]), + ... ("app_group", ["216199618143360301"]), + ... ("scim_group", [("216199618143191058", "2079468"), ("216199618143191058", "2079446")]) + ... ], + ... privileged_capabilities={ + ... "clipboard_copy": True, + ... "clipboard_paste": True, + ... "file_download": True, + ... "file_upload": None + ... } + ... ) + """ + # Get the policy id for the specified policy type + policy_id = self.get_policy("capabilities").id + + # Retrieve the existing rule to update + payload = convert_keys(self.get_rule("capabilities", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + elif key == "privileged_capabilities": + capabilities = [] + priv_caps_map = value + + if priv_caps_map.get("clipboard_copy", False): + capabilities.append("CLIPBOARD_COPY") + if priv_caps_map.get("clipboard_paste", False): + capabilities.append("CLIPBOARD_PASTE") + if priv_caps_map.get("file_download", False): + capabilities.append("FILE_DOWNLOAD") + + # Handling the edge case for file_upload + if priv_caps_map.get("file_upload") == True: + capabilities.append("FILE_UPLOAD") + elif priv_caps_map.get("file_upload") == False: + capabilities.append("INSPECT_FILE_UPLOAD") + # If file_upload is not present or set to None, do not append either capability + + if priv_caps_map.get("inspect_file_download", False): + capabilities.append("INSPECT_FILE_DOWNLOAD") + if priv_caps_map.get("inspect_file_upload", False): + capabilities.append("INSPECT_FILE_UPLOAD") + if priv_caps_map.get("monitor_session", False): + capabilities.append("MONITOR_SESSION") + if priv_caps_map.get("record_session", False): + capabilities.append("RECORD_SESSION") + if priv_caps_map.get("share_session", False): + capabilities.append("SHARE_SESSION") + + payload["privilegedCapabilities"] = {"capabilities": capabilities} + else: + payload[snake_to_camel(key)] = value + + payload["action"] = "CHECK_CAPABILITIES" + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("capabilities", rule_id) + + def add_redirection_rule(self, name: str, action: str, service_edge_group_ids: list = [], **kwargs) -> Box: + """ + Add a new Redirection Policy rule. + + See the + `ZPA Redirection Policy API reference `_ + for further detail on optional keyword parameter structures. + + Args: + name (str): + The name of the new redirection rule. + action (str): + The action for the policy. Accepted values are: + + | ``redirect_default`` + | ``redirect_preferred`` + | ``redirect_always`` + **kwargs: + Optional keyword args. + + Keyword Args: + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + + * `conditions`: This is for providing the set of conditions for the policy + * `object_type`: This is for specifying the policy criteria. The following values are supported: "app", "app_group", "saml", "scim", "scim_group", "client_type" + * `client_type`: The client type, must be one of the following: + - 'zpn_client_type_edge_connector' + - 'zpn_client_type_branch_connector' + - 'zpn_client_type_machine_tunnel' + - 'zpn_client_type_zapp' + - 'zpn_client_type_zapp_partner' + + Returns: + :obj:`Box`: The resource record of the newly created Redirection Policy rule. + + Example: + Add a new redirection rule with various conditions and service edge group IDs: + + .. code-block:: python + + zpa.policiesv2.add_redirection_rule( + name='New_Redirection_Rule', + action='redirect_preferred', + service_edge_group_ids=['12345', '67890'], + conditions=[ + ("client_type", 'zpn_client_type_edge_connector', 'zpn_client_type_branch_connector', 'zpn_client_type_machine_tunnel', 'zpn_client_type_zapp', 'zpn_client_type_zapp_partner'), + ] + ) + """ + # Validate action and service_edge_group_ids based on action type + if action.lower() == "redirect_default" and service_edge_group_ids: + raise ValueError("service_edge_group_ids cannot be set when action is 'redirect_default'.") + elif action.lower() in ["redirect_preferred", "redirect_always"] and not service_edge_group_ids: + raise ValueError("service_edge_group_ids must be set when action is 'redirect_preferred' or 'redirect_always'.") + + # Initialise the payload + payload = { + "name": name, + "action": action.upper(), + "conditions": self._create_conditions(kwargs.pop("conditions", [])), + } + + if service_edge_group_ids: + payload["serviceEdgeGroups"] = [{"id": group_id} for group_id in service_edge_group_ids] + + # Validate client_type values within conditions + valid_client_types = [ + "zpn_client_type_edge_connector", + "zpn_client_type_branch_connector", + "zpn_client_type_machine_tunnel", + "zpn_client_type_zapp", + "zpn_client_type_zapp_partner", + ] + + for condition in payload["conditions"]: + for operand in condition.get("operands", []): + if operand["objectType"] == "CLIENT_TYPE" and operand["values"][0] not in valid_client_types: + raise ValueError(f"Invalid client_type value: {operand['values'][0]}. Must be one of {valid_client_types}") + + # Get the policy id of the provided policy type for the URL. + policy_id = self.get_policy("redirection").id + + # Add optional parameters to payload + for key, value in kwargs.items(): + payload[snake_to_camel(key)] = value + + response = self.rest.post(f"policySet/{policy_id}/rule", json=payload, api_version="v2") + if isinstance(response, Response): + # this is only true when the creation failed (status code is not 2xx) + status_code = response.status_code + # Handle error response + raise Exception(f"API call failed with status {status_code}: {response.json()}") + return response + + def update_redirection_rule(self, rule_id: str, **kwargs) -> Box: + """ + Update an existing policy rule. + + Ensure you are using the correct arguments for the policy type that you want to update. + + Args: + + rule_id (str): + The unique identifier for the rule to be updated. + **kwargs: + Optional keyword args. + + Keyword Args: + action (str): + The action for the policy. Accepted values are: + + | ``redirect_default`` + | ``redirect_preferred`` + | ``redirect_always`` + description (str): + Additional information about the redirection rule. + enabled (bool): + Whether or not the redirection rule is enabled. + rule_order (str): + The rule evaluation order number of the rule. + conditions (list): + A list of conditional rule tuples. Tuples must follow the convention: `Object Type`, `LHS value`, + `RHS value`. If you are adding multiple values for the same object type then you will need + a new entry for each value. + E.g. + + .. code-block:: python + + ("client_type", ['zpn_client_type_edge_connector', 'zpn_client_type_branch_connector', 'zpn_client_type_machine_tunnel', 'zpn_client_type_zapp', 'zpn_client_type_zapp_partner']), + + Returns: + :obj:`Box`: The updated policy-rule resource record. + + Examples: + Updates the name only for an Access Policy rule: + + >>> zpa.policiesv2.update_redirection_rule( + ... rule_id='216199618143320419', + ... name='Update_Redirection_Rule_v2', + ... description='Update_Redirection_Rule_v2', + ... action='redirect_default', + ... conditions=[ + ... ("client_type", ['zpn_client_type_edge_connector', 'zpn_client_type_branch_connector', 'zpn_client_type_machine_tunnel', 'zpn_client_type_zapp', 'zpn_client_type_zapp_partner']), + ... ], + ... ) + """ + + # Ensure the action is provided and convert to uppercase + if "action" not in kwargs: + raise ValueError("The 'action' attribute is mandatory.") + + action = kwargs.pop("action").upper() + + # Get policy id for specified policy type + policy_id = self.get_policy("redirection").id + + payload = convert_keys(self.get_rule("redirection", rule_id)) + + # Add optional parameters to payload + for key, value in kwargs.items(): + if key == "conditions": + payload["conditions"] = self._create_conditions(value) + else: + payload[snake_to_camel(key)] = value + + # Set the action in the payload + payload["action"] = action + + # Make the PUT request to update the rule + response = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}", json=payload, api_version="v2") + # Return the object if it was updated successfully + if not isinstance(response, Response): + return self.get_rule("redirection", rule_id) + + def reorder_rule(self, policy_type: str, rule_id: str, rule_order: str) -> Box: + """ + Change the order of an existing policy rule. + + Args: + rule_id (str): + The unique id of the rule that will be reordered. + rule_order (str): + The new order for the rule. + policy_type (str): + The policy type. Accepted values are: + + | ``access`` - returns the Access Policy + | ``capabilities`` - returns the Capabilities Policy + | ``client_forwarding`` - returns the Client Forwarding Policy + | ``clientless`` - returns the Clientlesss Session Protection Policy + | ``credential`` - returns the Credential Policy + | ``inspection`` - returns the Inspection Policy + | ``isolation`` - returns the Isolation Policy + | ``redirection`` - returns the Redirection Policy + | ``siem`` - returns the SIEM Policy + | ``timeout`` - returns the Timeout Policy + + + Returns: + :obj:`Box`: The updated policy rule resource record. + + Examples: + Updates the order for an existing policy rule: + + >>> zpa.policies.reorder_rule(policy_type='access', + ... rule_id='88888', + ... rule_order='2') + + """ + # Get policy id for specified policy type + policy_id = self.get_policy(policy_type).id + + resp = self.rest.put(f"policySet/{policy_id}/rule/{rule_id}/reorder/{rule_order}").status_code + + if resp == 204: + return self.get_rule(policy_type, rule_id) + + def sort_key(self, rules_orders: dict[str, int]): + def key(a, b): + if a.id in rules_orders and b.id in rules_orders: + if rules_orders[a.id] < rules_orders[b.id]: + return -1 + return 1 + if a.id in rules_orders: + return -1 + elif b.id in rules_orders: + return 1 + + if a.rule_order < b.rule_order: + return -1 + return 1 + + return key + + def bulk_reorder_rules(self, policy_type: str, rules_orders: dict[str, int]) -> Box: + """ + Bulk change the order of policy rules. + + Args: + rules_orders (dict(rule_id=>order)): + A map of rule IDs and orders + policy_type (str): + The policy type. Accepted values are: + + | ``access`` - returns the Access Policy + | ``capabilities`` - returns the Capabilities Policy + | ``client_forwarding`` - returns the Client Forwarding Policy + | ``clientless`` - returns the Clientlesss Session Protection Policy + | ``credential`` - returns the Credential Policy + | ``inspection`` - returns the Inspection Policy + | ``isolation`` - returns the Isolation Policy + | ``redirection`` - returns the Redirection Policy + | ``siem`` - returns the SIEM Policy + | ``timeout`` - returns the Timeout Policy + + + """ + # Get policy id for specified policy type + policy_set = self.get_policy(policy_type).id + all = self.list_rules(policy_type) + all.sort(key=functools.cmp_to_key(self.sort_key(rules_orders=rules_orders))) + orderedRules = [r.id for r in all] + + # Construct the URL pathx + path = f"policySet/{policy_set}/reorder" + + # Create a new PUT request + resp = self.rest.put(path, json=orderedRules) + if resp.status_code <= 299: + # Return the updated rule information + return None + else: + # Handle the case when the request fails (modify as needed) + return resp + + def delete_rule(self, policy_type: str, rule_id: str) -> int: + """ + Deletes the specified policy rule. + + Args: + policy_type (str): + The type of policy the rule belongs to. Accepted values are: + + | ``access`` - returns the Access Policy + | ``capabilities`` - returns the Capabilities Policy + | ``client_forwarding`` - returns the Client Forwarding Policy + | ``clientless`` - returns the Clientlesss Session Protection Policy + | ``credential`` - returns the Credential Policy + | ``inspection`` - returns the Inspection Policy + | ``isolation`` - returns the Isolation Policy + | ``redirection`` - returns the Redirection Policy + | ``siem`` - returns the SIEM Policy + | ``timeout`` - returns the Timeout Policy + + rule_id (str): + The unique identifier for the policy rule. + + Returns: + :obj:`int`: The response code for the operation. + + Examples: + >>> zpa.policies.delete_rule(policy_id='99999', + ... rule_id='88888') + + """ + + # Get policy id for specified policy type + policy_id = self.get_policy(policy_type).id + + return self.rest.delete(f"policySet/{policy_id}/rule/{rule_id}").status_code