diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5c6fe4a..dc89c79 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,4 +8,4 @@ build: post_create_environment: - python -m pip install poetry post_install: - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install -E docs + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install diff --git a/README.md b/README.md index e0e7f50..1289d8e 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,24 @@ https://github.com/marcospereirampj/python-keycloak/issues The documentation for python-keycloak is available on [readthedocs](http://python-keycloak.readthedocs.io). -## Example of Using Keycloak OpenID +## Keycloak version support + +The library strives to always support Keycloak's latest version. Additionally to that, we also support 5 latest major versions of Keycloak, +in order to give our user base more time for smoother upgrades. + +Current list of supported Keycloak versions: +- 24.X +- 23.X +- 22.X +- 21.X +- 20.X +## Python version support + +We only support Python versions which have active or security support by the Python Software Foundation. You find the list of active python versions [here](https://endoflife.date/python). + +## Example of Using Keycloak OpenID ```python from keycloak import KeycloakOpenID diff --git a/docs/source/modules/openid_client.rst b/docs/source/modules/openid_client.rst index 265bf55..fc61697 100644 --- a/docs/source/modules/openid_client.rst +++ b/docs/source/modules/openid_client.rst @@ -116,9 +116,9 @@ Decode token .. code-block:: python - KEYCLOAK_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + keycloak_openid.public_key() + "\n-----END PUBLIC KEY-----" - options = {"verify_signature": True, "verify_aud": True, "verify_exp": True} - token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options) + token_info = keycloak_openid.decode_token(token['access_token']) + # Without validation + token_info = keycloak_openid.decode_token(token['access_token'], validate=False) Get UMA-permissions by token diff --git a/poetry.lock b/poetry.lock index 2b80e2f..14f0126 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4,7 +4,7 @@ name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -optional = true +optional = false python-versions = ">=3.6" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, @@ -15,7 +15,7 @@ files = [ name = "anyascii" version = "0.3.2" description = "Unicode to ASCII transliteration" -optional = true +optional = false python-versions = ">=3.3" files = [ {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"}, @@ -24,13 +24,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.2.3" +version = "3.3.0" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.2.3-py3-none-any.whl", hash = "sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c"}, - {file = "argcomplete-3.2.3.tar.gz", hash = "sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23"}, + {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, + {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, ] [package.extras] @@ -40,7 +40,7 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "astroid" version = "3.1.0" description = "An abstract syntax tree for Python with inference support." -optional = true +optional = false python-versions = ">=3.8.0" files = [ {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, @@ -54,7 +54,7 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} name = "babel" version = "2.14.0" description = "Internationalization utilities" -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, @@ -69,48 +69,48 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "backports-tarfile" -version = "1.0.0" +version = "1.1.1" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" files = [ - {file = "backports.tarfile-1.0.0-py3-none-any.whl", hash = "sha256:bcd36290d9684beb524d3fe74f4a2db056824c47746583f090b8e55daf0776e4"}, - {file = "backports.tarfile-1.0.0.tar.gz", hash = "sha256:2688f159c21afd56a07b75f01306f9f52c79aebcc5f4a117fb8fbb4445352c75"}, + {file = "backports.tarfile-1.1.1-py3-none-any.whl", hash = "sha256:73e0179647803d3726d82e76089d01d8549ceca9bace469953fcb4d97cf2d417"}, + {file = "backports_tarfile-1.1.1.tar.gz", hash = "sha256:9c2ef9696cb73374f7164e17fc761389393ca76777036f5aad42e8b93fcd8009"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] +testing = ["jaraco.test", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] [[package]] name = "black" -version = "24.3.0" +version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {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"}, + {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"}, ] [package.dependencies] @@ -379,17 +379,17 @@ files = [ [[package]] name = "commitizen" -version = "3.21.3" +version = "3.24.0" description = "Python commitizen client tool" optional = false python-versions = ">=3.8" files = [ - {file = "commitizen-3.21.3-py3-none-any.whl", hash = "sha256:1c23a9b0e02fadfb1f586649ed7f57745679af24be2f8158d2e746472f115246"}, - {file = "commitizen-3.21.3.tar.gz", hash = "sha256:dc23147e77376cced87f2aedd3693afa05832da88bf9e08c0baaa9d242d9549f"}, + {file = "commitizen-3.24.0-py3-none-any.whl", hash = "sha256:d9e28b1dcd97cea64dcb50be25292ceb730470d933f1da37131f9540f762df36"}, + {file = "commitizen-3.24.0.tar.gz", hash = "sha256:088e01ae8265f1d6fa5a4d11a05e4fd7092d958c881837c35f6c65aad27331a9"}, ] [package.dependencies] -argcomplete = ">=1.12.1,<3.3" +argcomplete = ">=1.12.1,<3.4" charset-normalizer = ">=2.1.0,<4" colorama = ">=0.4.1,<0.5.0" decli = ">=0.6.0,<0.7.0" @@ -405,7 +405,7 @@ tomlkit = ">=0.5.3,<1.0.0" name = "commonmark" version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" -optional = true +optional = false python-versions = "*" files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, @@ -417,63 +417,63 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"}, + {file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"}, + {file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"}, + {file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"}, + {file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"}, + {file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"}, + {file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"}, + {file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"}, + {file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"}, + {file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"}, + {file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"}, + {file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"}, + {file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"}, ] [package.dependencies] @@ -588,21 +588,18 @@ name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, -] +python-versions = "*" +files = [] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -610,13 +607,13 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.13.3" +version = "3.13.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.3-py3-none-any.whl", hash = "sha256:5ffa845303983e7a0b7ae17636509bc97997d58afeafa72fb141a17b152284cb"}, - {file = "filelock-3.13.3.tar.gz", hash = "sha256:a79895a25bbefdf55d1a2a0a80968f7dbb28edcd6d4234a0afb3f37ecde4b546"}, + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] [package.extras] @@ -657,13 +654,13 @@ pydocstyle = ">=2.1" [[package]] name = "freezegun" -version = "1.4.0" +version = "1.5.0" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ - {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, - {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, + {file = "freezegun-1.5.0-py3-none-any.whl", hash = "sha256:ec3f4ba030e34eb6cf7e1e257308aee2c60c3d038ff35996d7475760c9ff3719"}, + {file = "freezegun-1.5.0.tar.gz", hash = "sha256:200a64359b363aa3653d8aac289584078386c7c3da77339d257e46a01fb5c77c"}, ] [package.dependencies] @@ -671,13 +668,13 @@ python-dateutil = ">=2.7" [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -685,20 +682,20 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, @@ -805,13 +802,13 @@ testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytes [[package]] name = "jaraco-functools" -version = "4.0.0" +version = "4.0.1" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.functools-4.0.0-py3-none-any.whl", hash = "sha256:daf276ddf234bea897ef14f43c4e1bf9eefeac7b7a82a4dd69228ac20acff68d"}, - {file = "jaraco.functools-4.0.0.tar.gz", hash = "sha256:c279cb24c93d694ef7270f970d499cab4d3813f4e08273f95398651a634f0925"}, + {file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"}, + {file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"}, ] [package.dependencies] @@ -819,7 +816,7 @@ more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["jaraco.classes", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jeepney" @@ -870,13 +867,13 @@ typing-extensions = ">=4.5.0" [[package]] name = "keyring" -version = "25.1.0" +version = "25.2.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.1.0-py3-none-any.whl", hash = "sha256:26fc12e6a329d61d24aa47b22a7c5c3f35753df7d8f2860973cf94f4e1fb3427"}, - {file = "keyring-25.1.0.tar.gz", hash = "sha256:7230ea690525133f6ad536a9b5def74a4bd52642abe594761028fc044d7c7893"}, + {file = "keyring-25.2.0-py3-none-any.whl", hash = "sha256:19f17d40335444aab84b19a0d16a77ec0758a9c384e3446ae2ed8bd6d53b67a5"}, + {file = "keyring-25.2.0.tar.gz", hash = "sha256:7045f367268ce42dba44745050164b431e46f6e92f99ef2937dfadaef368d8cf"}, ] [package.dependencies] @@ -892,13 +889,13 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "m2r2" version = "0.3.2" description = "Markdown and reStructuredText in a single file." -optional = true +optional = false python-versions = "*" files = [ {file = "m2r2-0.3.2-py3-none-any.whl", hash = "sha256:d3684086b61b4bebe2307f15189495360f05a123c9bda2a66462649b7ca236aa"}, @@ -1028,29 +1025,13 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -optional = true +optional = false python-versions = "*" files = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] -[[package]] -name = "mock" -version = "4.0.3" -description = "Rolling backport of unittest.mock for all Pythons" -optional = true -python-versions = ">=3.6" -files = [ - {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, - {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, -] - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest (<5.4)", "pytest-cov"] - [[package]] name = "more-itertools" version = "10.2.0" @@ -1150,28 +1131,29 @@ testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +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.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1296,13 +1278,13 @@ testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytes [[package]] name = "pytest" -version = "8.1.1" +version = "8.1.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.1.2-py3-none-any.whl", hash = "sha256:6c06dc309ff46a05721e6fd48e492a775ed8165d2ecdf57f156a80c7e95bb142"}, + {file = "pytest-8.1.2.tar.gz", hash = "sha256:f3c45d1d5eed96b01a2aea70dee6a4a366d51d38f9957768083e4fecfc77f3ef"}, ] [package.dependencies] @@ -1352,7 +1334,7 @@ six = ">=1.5" name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" -optional = true +optional = false python-versions = "*" files = [ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, @@ -1467,7 +1449,7 @@ md = ["cmarkgfm (>=0.8.0)"] name = "readthedocs-sphinx-ext" version = "2.2.5" description = "Sphinx extension for Read the Docs overrides" -optional = true +optional = false python-versions = "*" files = [ {file = "readthedocs-sphinx-ext-2.2.5.tar.gz", hash = "sha256:ee5fd5b99db9f0c180b2396cbce528aa36671951b9526bb0272dbfce5517bd27"}, @@ -1483,7 +1465,7 @@ requests = "*" name = "recommonmark" version = "0.7.1" description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." -optional = true +optional = false python-versions = "*" files = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, @@ -1580,18 +1562,18 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1618,20 +1600,20 @@ files = [ [[package]] name = "sphinx" -version = "6.2.1" +version = "7.1.2" description = "Python documentation generator" -optional = true +optional = false python-versions = ">=3.8" files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" +docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" @@ -1655,7 +1637,7 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-autoapi" version = "3.0.0" description = "Sphinx API documentation generator" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de"}, @@ -1677,18 +1659,18 @@ docs = ["furo", "sphinx", "sphinx-design"] [[package]] name = "sphinx-rtd-theme" -version = "1.3.0" +version = "2.0.0" description = "Read the Docs theme for Sphinx" -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +optional = false +python-versions = ">=3.6" files = [ - {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, - {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, + {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, + {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, ] [package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<8" +docutils = "<0.21" +sphinx = ">=5,<8" sphinxcontrib-jquery = ">=4,<5" [package.extras] @@ -1698,7 +1680,7 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, @@ -1713,7 +1695,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = true +optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, @@ -1728,7 +1710,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, @@ -1743,7 +1725,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" -optional = true +optional = false python-versions = ">=2.7" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, @@ -1757,7 +1739,7 @@ Sphinx = ">=1.8" name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = true +optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, @@ -1771,7 +1753,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -optional = true +optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, @@ -1786,7 +1768,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = true +optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, @@ -1835,13 +1817,13 @@ files = [ [[package]] name = "tox" -version = "4.14.2" +version = "4.15.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"}, - {file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"}, + {file = "tox-4.15.0-py3-none-any.whl", hash = "sha256:300055f335d855b2ab1b12c5802de7f62a36d4fd53f30bd2835f6a201dda46ea"}, + {file = "tox-4.15.0.tar.gz", hash = "sha256:7a0beeef166fbe566f54f795b4906c31b428eddafc0102ac00d20998dd1933f6"}, ] [package.dependencies] @@ -1912,13 +1894,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, + {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, ] [package.dependencies] @@ -1927,7 +1909,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -1970,10 +1952,7 @@ files = [ 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)"] -[extras] -docs = ["Sphinx", "alabaster", "commonmark", "m2r2", "mock", "readthedocs-sphinx-ext", "recommonmark", "sphinx-autoapi", "sphinx-rtd-theme"] - [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "fe45fda91997f6001207b828d4c17c2364b7c4b403ec3d82ef8154252e2a8f4c" +content-hash = "7594e5a7a562c246e5c6ab3df0282095bb70ee23e9ce748adf57ce67cb0f9413" diff --git a/pyproject.toml b/pyproject.toml index 7dff4b3..783d404 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,31 +31,19 @@ Documentation = "https://python-keycloak.readthedocs.io/en/latest/" [tool.poetry.dependencies] python = ">=3.8,<4.0" requests = ">=2.20.0" -mock = {version = "^4.0.3", optional = true} -alabaster = {version = "^0.7.12", optional = true} -commonmark = {version = "^0.9.1", optional = true} -recommonmark = {version = "^0.7.1", optional = true} -Sphinx = {version = "^6.1.0", optional = true} -sphinx-rtd-theme = {version = "^1.0.0", optional = true} -readthedocs-sphinx-ext = {version = "^2.1.9", optional = true} -m2r2 = {version = "^0.3.2", optional = true} -sphinx-autoapi = {version = "^3.0.0", optional = true} requests-toolbelt = ">=0.6.0" deprecation = ">=2.1.0" jwcrypto = "^1.5.4" -[tool.poetry.extras] -docs = [ - "mock", - "alabaster", - "commonmark", - "recommonmark", - "sphinx", - "sphinx-rtd-theme", - "readthedocs-sphinx-ext", - "m2r2", - "sphinx-autoapi", -] +[tool.poetry.group.docs.dependencies] +alabaster = ">=0.7.12" +commonmark = ">=0.9.1" +recommonmark = ">=0.7.1" +Sphinx = ">=6.1.0" +sphinx-rtd-theme = ">=1.0.0" +readthedocs-sphinx-ext = ">=2.1.9" +m2r2 = ">=0.3.2" +sphinx-autoapi = ">=3.0.0" [tool.poetry.group.dev.dependencies] tox = ">=4.0.0" diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 3cbcaf1..575ee6d 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -31,11 +31,9 @@ import json from builtins import isinstance from typing import Optional -import deprecation from requests_toolbelt import MultipartEncoder from . import urls_patterns -from ._version import __version__ from .exceptions import ( KeycloakDeleteError, KeycloakGetError, @@ -73,9 +71,6 @@ class KeycloakAdmin: :type custom_headers: dict :param user_realm_name: The realm name of the user, if different from realm_name :type user_realm_name: str - :param auto_refresh_token: list of methods that allows automatic token refresh. - Ex: ['get', 'put', 'post', 'delete'] - :type auto_refresh_token: list :param timeout: connection timeout in seconds :type timeout: int :param connection: A KeycloakOpenIDConnection as an alternative to individual params. @@ -84,9 +79,6 @@ class KeycloakAdmin: PAGE_SIZE = 100 - _auto_refresh_token = None - _connection: Optional[KeycloakOpenIDConnection] = None - def __init__( self, server_url=None, @@ -100,7 +92,6 @@ class KeycloakAdmin: client_secret_key=None, custom_headers=None, user_realm_name=None, - auto_refresh_token=None, timeout=60, connection: Optional[KeycloakOpenIDConnection] = None, ): @@ -130,9 +121,6 @@ class KeycloakAdmin: :type custom_headers: dict :param user_realm_name: The realm name of the user, if different from realm_name :type user_realm_name: str - :param auto_refresh_token: list of methods that allows automatic token refresh. - Ex: ['get', 'put', 'post', 'delete'] - :type auto_refresh_token: list :param timeout: connection timeout in seconds :type timeout: int :param connection: An OpenID Connection as an alternative to individual params. @@ -152,58 +140,6 @@ class KeycloakAdmin: custom_headers=custom_headers, timeout=timeout, ) - if auto_refresh_token is not None: - self.auto_refresh_token = auto_refresh_token - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.server_url property instead", - ) - def server_url(self): - """Get server url. - - :returns: Keycloak server url - :rtype: str - """ - return self.connection.server_url - - @server_url.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.server_url property instead", - ) - def server_url(self, value): - self.connection.server_url = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.realm_name property instead", - ) - def realm_name(self): - """Get realm name. - - :returns: Realm name - :rtype: str - """ - return self.connection.realm_name - - @realm_name.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.realm_name property instead", - ) - def realm_name(self, value): - self.connection.realm_name = value @property def connection(self) -> KeycloakOpenIDConnection: @@ -218,256 +154,6 @@ class KeycloakAdmin: def connection(self, value: KeycloakOpenIDConnection) -> None: self._connection = value - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.client_id property instead", - ) - def client_id(self): - """Get client id. - - :returns: Client id - :rtype: str - """ - return self.connection.client_id - - @client_id.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.client_id property instead", - ) - def client_id(self, value): - self.connection.client_id = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.client_secret_key property instead", - ) - def client_secret_key(self): - """Get client secret key. - - :returns: Client secret key - :rtype: str - """ - return self.connection.client_secret_key - - @client_secret_key.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.client_secret_key property instead", - ) - def client_secret_key(self, value): - self.connection.client_secret_key = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.verify property instead", - ) - def verify(self): - """Get verify. - - :returns: Verify indicator - :rtype: bool - """ - return self.connection.verify - - @verify.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.verify property instead", - ) - def verify(self, value): - self.connection.verify = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.username property instead", - ) - def username(self): - """Get username. - - :returns: Admin username - :rtype: str - """ - return self.connection.username - - @username.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.username property instead", - ) - def username(self, value): - self.connection.username = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.password property instead", - ) - def password(self): - """Get password. - - :returns: Admin password - :rtype: str - """ - return self.connection.password - - @password.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.password property instead", - ) - def password(self, value): - self.connection.password = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.totp property instead", - ) - def totp(self): - """Get totp. - - :returns: TOTP - :rtype: str - """ - return self.connection.totp - - @totp.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.totp property instead", - ) - def totp(self, value): - self.connection.totp = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.token property instead", - ) - def token(self): - """Get token. - - :returns: Access and refresh token - :rtype: dict - """ - return self.connection.token - - @token.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.token property instead", - ) - def token(self, value): - self.connection.token = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.user_realm_name property instead", - ) - def user_realm_name(self): - """Get user realm name. - - :returns: User realm name - :rtype: str - """ - return self.connection.user_realm_name - - @user_realm_name.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.user_realm_name property instead", - ) - def user_realm_name(self, value): - self.connection.user_realm_name = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.custom_headers property instead", - ) - def custom_headers(self): - """Get custom headers. - - :returns: Custom headers - :rtype: dict - """ - return self.connection.custom_headers - - @custom_headers.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.custom_headers property instead", - ) - def custom_headers(self, value): - self.connection.custom_headers = value - - @property - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Auto-refresh will be implicitly set for all requests", - ) - def auto_refresh_token(self): - """Get auto refresh token. - - :returns: List of methods for automatic token refresh - :rtype: list - """ - return self._auto_refresh_token - - @auto_refresh_token.setter - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Auto-refresh will be implicitly set for all requests", - ) - def auto_refresh_token(self, value): - self._auto_refresh_token = value or [] - def __fetch_all(self, url, query=None): """Paginate over get requests. @@ -1268,7 +954,7 @@ class KeycloakAdmin: data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) return raise_error_from_response(data_raw, KeycloakGetError) - def get_groups(self, query=None): + def get_groups(self, query=None, full_hierarchy=False): """Get groups. Returns a list of groups belonging to the realm @@ -1276,8 +962,15 @@ class KeycloakAdmin: GroupRepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation + Notice that when using full_hierarchy=True, the response will be a nested structure + containing all the children groups. If used with query parameters, the full_hierarchy + will be applied to the received groups only. + :param query: Additional query options :type query: dict + :param full_hierarchy: If True, return all of the nested children groups, otherwise only + the first level children are returned + :type full_hierarchy: bool :return: array GroupRepresentation :rtype: list """ @@ -1293,11 +986,13 @@ class KeycloakAdmin: # For version +23.0.0 for group in groups: if group.get("subGroupCount"): - group["subGroups"] = self.get_group_children(group.get("id")) + group["subGroups"] = self.get_group_children( + group_id=group.get("id"), full_hierarchy=full_hierarchy + ) return groups - def get_group(self, group_id): + def get_group(self, group_id, full_hierarchy=False): """Get group by id. Returns full group details @@ -1307,6 +1002,9 @@ class KeycloakAdmin: :param group_id: The group id :type group_id: str + :param full_hierarchy: If True, return all of the nested children groups, otherwise only + the first level children are returned + :type full_hierarchy: bool :return: Keycloak server response (GroupRepresentation) :rtype: dict """ @@ -1319,7 +1017,9 @@ class KeycloakAdmin: # For version +23.0.0 group = response.json() if group.get("subGroupCount"): - group["subGroups"] = self.get_group_children(group.get("id")) + group["subGroups"] = self.get_group_children( + group.get("id"), full_hierarchy=full_hierarchy + ) return group @@ -1349,7 +1049,7 @@ class KeycloakAdmin: # went through the tree without hits return None - def get_group_children(self, group_id, query=None): + def get_group_children(self, group_id, query=None, full_hierarchy=False): """Get group children by parent id. Returns full group children details @@ -1358,15 +1058,32 @@ class KeycloakAdmin: :type group_id: str :param query: Additional query options :type query: dict + :param full_hierarchy: If True, return all of the nested children groups + :type full_hierarchy: bool :return: Keycloak server response (GroupRepresentation) :rtype: dict + :raises ValueError: If both query and full_hierarchy parameters are used """ query = query or {} + if query and full_hierarchy: + raise ValueError("Cannot use both query and full_hierarchy parameters") + params_path = {"realm-name": self.connection.realm_name, "id": group_id} url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path) if "first" in query or "max" in query: return self.__fetch_paginated(url, query) - return self.__fetch_all(url, query) + res = self.__fetch_all(url, query) + + if not full_hierarchy: + return res + + for group in res: + if group.get("subGroupCount"): + group["subGroups"] = self.get_group_children( + group_id=group.get("id"), full_hierarchy=full_hierarchy + ) + + return res def get_group_members(self, group_id, query=None): """Get members by group id. @@ -4095,120 +3812,6 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204]) - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.raw_get function instead", - ) - def raw_get(self, *args, **kwargs): - """Call connection.raw_get. - - If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token - and try *get* once more. - - :param args: Additional arguments - :type args: tuple - :param kwargs: Additional keyword arguments - :type kwargs: dict - :returns: Response - :rtype: Response - """ - return self.connection.raw_get(*args, **kwargs) - - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.raw_post function instead", - ) - def raw_post(self, *args, **kwargs): - """Call connection.raw_post. - - If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token - and try *post* once more. - - :param args: Additional arguments - :type args: tuple - :param kwargs: Additional keyword arguments - :type kwargs: dict - :returns: Response - :rtype: Response - """ - return self.connection.raw_post(*args, **kwargs) - - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.raw_put function instead", - ) - def raw_put(self, *args, **kwargs): - """Call connection.raw_put. - - If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token - and try *put* once more. - - :param args: Additional arguments - :type args: tuple - :param kwargs: Additional keyword arguments - :type kwargs: dict - :returns: Response - :rtype: Response - """ - return self.connection.raw_put(*args, **kwargs) - - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.raw_delete function instead", - ) - def raw_delete(self, *args, **kwargs): - """Call connection.raw_delete. - - If auto_refresh is set for *delete* and *access_token* is expired, - it will refresh the token and try *delete* once more. - - :param args: Additional arguments - :type args: tuple - :param kwargs: Additional keyword arguments - :type kwargs: dict - :returns: Response - :rtype: Response - """ - return self.connection.raw_delete(*args, **kwargs) - - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.get_token function instead", - ) - def get_token(self): - """Get admin token. - - The admin token is then set in the `token` attribute. - - :returns: token - :rtype: dict - """ - return self.connection.get_token() - - @deprecation.deprecated( - deprecated_in="2.13.0", - removed_in="4.0.0", - current_version=__version__, - details="Use the connection.refresh_token function instead", - ) - def refresh_token(self): - """Refresh the token. - - :returns: token - :rtype: dict - """ - return self.connection.refresh_token() - def get_client_all_sessions(self, client_id): """Get sessions associated with the client. @@ -4368,8 +3971,8 @@ class KeycloakAdmin: :return: Keycloak server response :rtype: bytes """ - params_path = {"realm-name": self.realm_name, "id": client_id} - data_raw = self.raw_post( + params_path = {"realm-name": self.connection.realm_name, "id": client_id} + data_raw = self.connection.raw_post( urls_patterns.URL_ADMIN_ADD_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path), data=json.dumps(payload), ) diff --git a/src/keycloak/keycloak_openid.py b/src/keycloak/keycloak_openid.py index 8fdbfd9..0c14709 100644 --- a/src/keycloak/keycloak_openid.py +++ b/src/keycloak/keycloak_openid.py @@ -212,7 +212,7 @@ class KeycloakOpenID: :type token: str :param method_token_info: Token info method to use :type method_token_info: str - :param kwargs: Additional keyword arguments + :param kwargs: Additional keyword arguments passed to the decode_token method :type kwargs: dict :returns: Token info :rtype: dict @@ -516,7 +516,7 @@ class KeycloakOpenID: data_raw = self.connection.raw_post(URL_INTROSPECT.format(**params_path), data=payload) return raise_error_from_response(data_raw, KeycloakPostError) - def decode_token(self, token, key, algorithms=["RS256"], **kwargs): + def decode_token(self, token, validate: bool = True, **kwargs): """Decode user token. A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data @@ -530,25 +530,30 @@ class KeycloakOpenID: :param token: Keycloak token :type token: str - :param key: Decode key - :type key: str - :param algorithms: Algorithms to use for decoding - :type algorithms: list[str] - :param kwargs: Keyword arguments + :param validate: Determines whether the token should be validated with the public key. + Defaults to True. + :type validate: bool + :param kwargs: Additional keyword arguments for jwcrypto's JWT object :type kwargs: dict :returns: Decoded token :rtype: dict """ - # To keep the same API, we map the python-jose options to our claims for jwcrypto - # Per the jwcrypto dev, `exp` and `nbf` are always checked - options = kwargs.get("options", {}) - check_claims = {} - if options.get("verify_aud") is True: - check_claims["aud"] = self.client_id - - k = jwk.JWK.from_pem(key.encode("utf-8")) - full_jwt = jwt.JWT(jwt=token, key=k, algs=algorithms, check_claims=check_claims) - return jwt.json_decode(full_jwt.claims) + if validate: + if "key" not in kwargs: + key = ( + "-----BEGIN PUBLIC KEY-----\n" + + self.public_key() + + "\n-----END PUBLIC KEY-----" + ) + key = jwk.JWK.from_pem(key.encode("utf-8")) + kwargs["key"] = key + + full_jwt = jwt.JWT(jwt=token, **kwargs) + return jwt.json_decode(full_jwt.claims) + else: + full_jwt = jwt.JWT(jwt=token, **kwargs) + full_jwt.token.objects["valid"] = True + return json.loads(full_jwt.token.payload.decode("utf-8")) def load_authorization_config(self, path): """Load Keycloak settings (authorization). diff --git a/tests/conftest.py b/tests/conftest.py index 8c43c77..af1f9af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import ipaddress import os import uuid from datetime import datetime, timedelta -from typing import Tuple +from typing import Generator, Tuple import freezegun import pytest @@ -337,7 +337,69 @@ def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: Keycloak @pytest.fixture -def realm(admin: KeycloakAdmin) -> str: +def oid_with_credentials_device(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin): + """Fixture for an initialized KeycloakOpenID class and a random user credentials. + + :param env: Keycloak test environment + :type env: KeycloakTestEnv + :param realm: Keycloak realm + :type realm: str + :param admin: Keycloak admin + :type admin: KeycloakAdmin + :yields: Keycloak OpenID client with user credentials + :rtype: Tuple[KeycloakOpenID, str, str] + """ + # Set the realm + admin.change_current_realm(realm) + # Create client + client = str(uuid.uuid4()) + secret = str(uuid.uuid4()) + client_id = admin.create_client( + payload={ + "name": client, + "clientId": client, + "enabled": True, + "publicClient": False, + "protocol": "openid-connect", + "secret": secret, + "clientAuthenticatorType": "client-secret", + "attributes": {"oauth2.device.authorization.grant.enabled": True}, + } + ) + # Create user + username = str(uuid.uuid4()) + password = str(uuid.uuid4()) + user_id = admin.create_user( + payload={ + "username": username, + "email": f"{username}@test.test", + "enabled": True, + "firstName": "first", + "lastName": "last", + "emailVerified": True, + "requiredActions": [], + "credentials": [{"type": "password", "value": password, "temporary": False}], + } + ) + + yield ( + KeycloakOpenID( + server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", + realm_name=realm, + client_id=client, + client_secret_key=secret, + ), + username, + password, + ) + + # Cleanup + admin.delete_client(client_id=client_id) + admin.delete_user(user_id=user_id) + + +@pytest.fixture +def realm(admin: KeycloakAdmin) -> Generator[str, None, None]: """Fixture for a new random realm. :param admin: Keycloak admin @@ -352,7 +414,7 @@ def realm(admin: KeycloakAdmin) -> str: @pytest.fixture -def user(admin: KeycloakAdmin, realm: str) -> str: +def user(admin: KeycloakAdmin, realm: str) -> Generator[str, None, None]: """Fixture for a new random user. :param admin: Keycloak admin @@ -370,7 +432,7 @@ def user(admin: KeycloakAdmin, realm: str) -> str: @pytest.fixture -def group(admin: KeycloakAdmin, realm: str) -> str: +def group(admin: KeycloakAdmin, realm: str) -> Generator[str, None, None]: """Fixture for a new random group. :param admin: Keycloak admin @@ -388,7 +450,7 @@ def group(admin: KeycloakAdmin, realm: str) -> str: @pytest.fixture -def client(admin: KeycloakAdmin, realm: str) -> str: +def client(admin: KeycloakAdmin, realm: str) -> Generator[str, None, None]: """Fixture for a new random client. :param admin: Keycloak admin @@ -406,7 +468,7 @@ def client(admin: KeycloakAdmin, realm: str) -> str: @pytest.fixture -def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str: +def client_role(admin: KeycloakAdmin, realm: str, client: str) -> Generator[str, None, None]: """Fixture for a new random client role. :param admin: Keycloak admin @@ -426,7 +488,9 @@ def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str: @pytest.fixture -def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_role: str) -> str: +def composite_client_role( + admin: KeycloakAdmin, realm: str, client: str, client_role: str +) -> Generator[str, None, None]: """Fixture for a new random composite client role. :param admin: Keycloak admin diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 856830d..cfd724e 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1,12 +1,14 @@ """Test the keycloak admin object.""" import copy +import os import uuid from typing import Tuple import freezegun import pytest from dateutil import parser as datetime_parser +from packaging.version import Version import keycloak from keycloak import KeycloakAdmin, KeycloakOpenID, KeycloakOpenIDConnection @@ -46,19 +48,21 @@ def test_keycloak_admin_init(env): username=env.KEYCLOAK_ADMIN, password=env.KEYCLOAK_ADMIN_PASSWORD, ) - assert admin.server_url == f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", admin.server_url - assert admin.realm_name == "master", admin.realm_name + assert ( + admin.connection.server_url == f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}" + ), admin.connection.server_url + assert admin.connection.realm_name == "master", admin.connection.realm_name assert isinstance(admin.connection, ConnectionManager), type(admin.connection) - assert admin.client_id == "admin-cli", admin.client_id - assert admin.client_secret_key is None, admin.client_secret_key - assert admin.verify, admin.verify - assert admin.username == env.KEYCLOAK_ADMIN, admin.username - assert admin.password == env.KEYCLOAK_ADMIN_PASSWORD, admin.password - assert admin.totp is None, admin.totp - assert admin.token is not None, admin.token - assert admin.user_realm_name is None, admin.user_realm_name - assert admin.custom_headers is None, admin.custom_headers - assert admin.token + assert admin.connection.client_id == "admin-cli", admin.connection.client_id + assert admin.connection.client_secret_key is None, admin.connection.client_secret_key + assert admin.connection.verify, admin.connection.verify + assert admin.connection.username == env.KEYCLOAK_ADMIN, admin.connection.username + assert admin.connection.password == env.KEYCLOAK_ADMIN_PASSWORD, admin.connection.password + assert admin.connection.totp is None, admin.connection.totp + assert admin.connection.token is not None, admin.connection.token + assert admin.connection.user_realm_name is None, admin.connection.user_realm_name + assert admin.connection.custom_headers is None, admin.connection.custom_headers + assert admin.connection.token admin = KeycloakAdmin( server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", @@ -67,7 +71,7 @@ def test_keycloak_admin_init(env): realm_name=None, user_realm_name="master", ) - assert admin.token + assert admin.connection.token admin = KeycloakAdmin( server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", username=env.KEYCLOAK_ADMIN, @@ -75,19 +79,19 @@ def test_keycloak_admin_init(env): realm_name=None, user_realm_name=None, ) - assert admin.token + assert admin.connection.token - token = admin.token + token = admin.connection.token admin = KeycloakAdmin( server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}", token=token, realm_name=None, user_realm_name=None, ) - assert admin.token == token + assert admin.connection.token == token admin.create_realm(payload={"realm": "authz", "enabled": True}) - admin.realm_name = "authz" + admin.connection.realm_name = "authz" admin.create_client( payload={ "name": "authz-client", @@ -107,7 +111,7 @@ def test_keycloak_admin_init(env): user_realm_name="authz", client_id="authz-client", client_secret_key=secret["value"], - ).token + ).connection.token admin.delete_realm(realm_name="authz") assert ( @@ -117,7 +121,7 @@ def test_keycloak_admin_init(env): password=None, client_secret_key=None, custom_headers={"custom": "header"}, - ).token + ).connection.token is None ) @@ -130,7 +134,7 @@ def test_keycloak_admin_init(env): verify=True, ) keycloak_admin = KeycloakAdmin(connection=keycloak_connection) - assert keycloak_admin.token + assert keycloak_admin.connection.token def test_realms(admin: KeycloakAdmin): @@ -333,6 +337,16 @@ def test_users(admin: KeycloakAdmin, realm: str): admin.update_user(user_id=user_id, payload={"wrong": "payload"}) assert err.match('400: b\'{"error":"Unrecognized field') + # Test disable user + res = admin.disable_user(user_id=user_id) + assert res == {}, res + assert not admin.get_user(user_id=user_id)["enabled"] + + # Test enable user + res = admin.enable_user(user_id=user_id) + assert res == {}, res + assert admin.get_user(user_id=user_id)["enabled"] + # Test get users again users = admin.get_users() usernames = [x["username"] for x in users] @@ -386,6 +400,43 @@ def test_users(admin: KeycloakAdmin, realm: str): assert err.match(USER_NOT_FOUND_REGEX) +def test_enable_disable_all_users(admin: KeycloakAdmin, realm: str): + """Test enable and disable all users. + + :param admin: Keycloak Admin client + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + """ + admin.change_current_realm(realm) + + user_id_1 = admin.create_user( + payload={"username": "test", "email": "test@test.test", "enabled": True} + ) + user_id_2 = admin.create_user( + payload={"username": "test2", "email": "test2@test.test", "enabled": True} + ) + user_id_3 = admin.create_user( + payload={"username": "test3", "email": "test3@test.test", "enabled": True} + ) + + assert admin.get_user(user_id_1)["enabled"] + assert admin.get_user(user_id_2)["enabled"] + assert admin.get_user(user_id_3)["enabled"] + + admin.disable_all_users() + + assert not admin.get_user(user_id_1)["enabled"] + assert not admin.get_user(user_id_2)["enabled"] + assert not admin.get_user(user_id_3)["enabled"] + + admin.enable_all_users() + + assert admin.get_user(user_id_1)["enabled"] + assert admin.get_user(user_id_2)["enabled"] + assert admin.get_user(user_id_3)["enabled"] + + def test_users_roles(admin: KeycloakAdmin, realm: str): """Test users roles. @@ -745,6 +796,36 @@ def test_groups(admin: KeycloakAdmin, user: str): assert res is not None, res assert res["id"] == subsubgroup_id_1 + # Test nested search from main group + res = admin.get_subgroups( + group=admin.get_group(group_id=group_id, full_hierarchy=True), + path="/main-group/subgroup-2/subsubgroup-1", + ) + assert res["id"] == subsubgroup_id_1 + + # Test nested search from all groups + res = admin.get_groups(full_hierarchy=True) + assert len(res) == 1 + assert len(res[0]["subGroups"]) == 2 + assert len([x for x in res[0]["subGroups"] if x["id"] == subgroup_id_1][0]["subGroups"]) == 0 + assert len([x for x in res[0]["subGroups"] if x["id"] == subgroup_id_2][0]["subGroups"]) == 1 + + # Test that query params are not allowed for full hierarchy + with pytest.raises(ValueError) as err: + admin.get_group_children(group_id=group_id, full_hierarchy=True, query={"max": 10}) + + # Test that query params are passed + if os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] == "latest" or Version( + os.environ["KEYCLOAK_DOCKER_IMAGE_TAG"] + ) >= Version("23"): + res = admin.get_group_children(group_id=group_id, query={"max": 1}) + assert len(res) == 1 + + assert err.match("Cannot use both query and full_hierarchy parameters") + + main_group_id_2 = admin.create_group(payload={"name": "main-group-2"}) + assert len(admin.get_groups(full_hierarchy=True)) == 2 + # Test empty search res = admin.get_subgroups(group=main_group, path="/none") assert res is None, res @@ -816,6 +897,8 @@ def test_groups(admin: KeycloakAdmin, user: str): # Test delete res = admin.delete_group(group_id=group_id) assert res == dict(), res + res = admin.delete_group(group_id=main_group_id_2) + assert res == dict(), res assert len(admin.get_groups()) == 0 # Test delete fail @@ -1352,6 +1435,10 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): admin.get_realm_role_groups(role_name="non-existent-role") assert err.match(COULD_NOT_FIND_ROLE_REGEX) + # Test with query params + res = admin.get_realm_role_groups(role_name="test-realm-role-update", query={"max": 1}) + assert len(res) == 1 + # Test delete realm role res = admin.delete_realm_role(role_name=composite_role) assert res == dict(), res @@ -1990,7 +2077,7 @@ def test_get_sessions(admin: KeycloakAdmin): :param admin: Keycloak Admin client :type admin: KeycloakAdmin """ - sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.username)) + sessions = admin.get_sessions(user_id=admin.get_user_id(username=admin.connection.username)) assert len(sessions) >= 1 with pytest.raises(KeycloakGetError) as err: admin.get_sessions(user_id="bad") @@ -2862,7 +2949,7 @@ def test_realm_default_roles(admin: KeycloakAdmin, realm: str) -> None: assert {x["name"] for x in roles} == {"offline_access", "uma_authorization"} with pytest.raises(KeycloakGetError) as err: - admin.realm_name = "doesnotexist" + admin.change_current_realm("doesnotexist") admin.get_realm_default_roles() assert err.match('404: b\'{"error":"Realm not found.".*}\'') admin.change_current_realm(realm) @@ -2964,3 +3051,14 @@ def test_initial_access_token( new_secret = str(uuid.uuid4()) res = oid.update_client(res["registrationAccessToken"], client, payload={"secret": new_secret}) assert res["secret"] == new_secret + + +def test_refresh_token(admin: KeycloakAdmin): + """Test refresh token on connection even if it is expired. + + :param admin: Keycloak admin + :type admin: KeycloakAdmin + """ + assert admin.connection.token is not None + admin.user_logout(admin.get_user_id(admin.connection.username)) + admin.connection.refresh_token() diff --git a/tests/test_keycloak_openid.py b/tests/test_keycloak_openid.py index 8d3120b..dd3067a 100644 --- a/tests/test_keycloak_openid.py +++ b/tests/test_keycloak_openid.py @@ -307,15 +307,13 @@ def test_decode_token(oid_with_credentials: Tuple[KeycloakOpenID, str, str]): """ oid, username, password = oid_with_credentials token = oid.token(username=username, password=password) + decoded_access_token = oid.decode_token(token=token["access_token"]) + decoded_access_token_2 = oid.decode_token(token=token["access_token"], validate=False) + decoded_refresh_token = oid.decode_token(token=token["refresh_token"], validate=False) - assert ( - oid.decode_token( - token=token["access_token"], - key="-----BEGIN PUBLIC KEY-----\n" + oid.public_key() + "\n-----END PUBLIC KEY-----", - options={"verify_aud": False}, - )["preferred_username"] - == username - ) + assert decoded_access_token == decoded_access_token_2 + assert decoded_access_token["preferred_username"] == username, decoded_access_token + assert decoded_refresh_token["typ"] == "Refresh", decoded_refresh_token def test_load_authorization_config(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str]): @@ -354,20 +352,17 @@ def test_get_policies(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, str oid.load_authorization_config(path="tests/data/authz_settings.json") assert oid.get_policies(token=token["access_token"]) is None - key = "-----BEGIN PUBLIC KEY-----\n" + oid.public_key() + "\n-----END PUBLIC KEY-----" orig_client_id = oid.client_id oid.client_id = "account" - assert oid.get_policies(token=token["access_token"], method_token_info="decode", key=key) == [] + assert oid.get_policies(token=token["access_token"], method_token_info="decode") == [] policy = Policy(name="test", type="role", logic="POSITIVE", decision_strategy="UNANIMOUS") policy.add_role(role="account/view-profile") oid.authorization.policies["test"] = policy assert [ - str(x) - for x in oid.get_policies(token=token["access_token"], method_token_info="decode", key=key) + str(x) for x in oid.get_policies(token=token["access_token"], method_token_info="decode") ] == ["Policy: test (role)"] assert [ - repr(x) - for x in oid.get_policies(token=token["access_token"], method_token_info="decode", key=key) + repr(x) for x in oid.get_policies(token=token["access_token"], method_token_info="decode") ] == [""] oid.client_id = orig_client_id @@ -392,12 +387,9 @@ def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, oid.load_authorization_config(path="tests/data/authz_settings.json") assert oid.get_permissions(token=token["access_token"]) is None - key = "-----BEGIN PUBLIC KEY-----\n" + oid.public_key() + "\n-----END PUBLIC KEY-----" orig_client_id = oid.client_id oid.client_id = "account" - assert ( - oid.get_permissions(token=token["access_token"], method_token_info="decode", key=key) == [] - ) + assert oid.get_permissions(token=token["access_token"], method_token_info="decode") == [] policy = Policy(name="test", type="role", logic="POSITIVE", decision_strategy="UNANIMOUS") policy.add_role(role="account/view-profile") policy.add_permission( @@ -408,15 +400,11 @@ def test_get_permissions(oid_with_credentials_authz: Tuple[KeycloakOpenID, str, oid.authorization.policies["test"] = policy assert [ str(x) - for x in oid.get_permissions( - token=token["access_token"], method_token_info="decode", key=key - ) + for x in oid.get_permissions(token=token["access_token"], method_token_info="decode") ] == ["Permission: test-perm (resource)"] assert [ repr(x) - for x in oid.get_permissions( - token=token["access_token"], method_token_info="decode", key=key - ) + for x in oid.get_permissions(token=token["access_token"], method_token_info="decode") ] == [""] oid.client_id = orig_client_id @@ -471,7 +459,31 @@ def test_has_uma_access( == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=set())" ) assert ( - str(oid.has_uma_access(token=admin.token["access_token"], permissions="Default Resource")) + str( + oid.has_uma_access( + token=admin.connection.token["access_token"], permissions="Default Resource" + ) + ) == "AuthStatus(is_authorized=False, is_logged_in=False, missing_permissions=" + "{'Default Resource'})" ) + + +def test_device(oid_with_credentials_device: Tuple[KeycloakOpenID, str, str]): + """Test device authorization flow. + + :param oid_with_credentials_device: Keycloak OpenID client with pre-configured user + credentials and device authorization flow enabled + :type oid_with_credentials_device: Tuple[KeycloakOpenID, str, str] + """ + oid, _, _ = oid_with_credentials_device + res = oid.device() + assert res == { + "device_code": mock.ANY, + "user_code": mock.ANY, + "verification_uri": f"http://localhost:8081/realms/{oid.realm_name}/device", + "verification_uri_complete": f"http://localhost:8081/realms/{oid.realm_name}/" + + f"device?user_code={res['user_code']}", + "expires_in": 600, + "interval": 5, + } diff --git a/tox.ini b/tox.ini index 503ec2c..ce43b2b 100644 --- a/tox.ini +++ b/tox.ini @@ -24,8 +24,6 @@ commands = allowlist_externals = black, poetry, isort [testenv:docs] -commands_pre = - poetry install --no-root --sync -E docs commands = sphinx-build -T -E -W -b html -d _build/doctrees -D language=en ./docs/source _build/html allowlist_externals = sphinx-build, poetry